diff --git a/.gitignore b/.gitignore index dc122d7..c2710d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,9 @@ -/data -/work -/logs /.idea -/target .DS_Store /.settings /.classpath /.project /.gradle -/build +build *~ /*.iml \ No newline at end of file diff --git a/CREDITS.txt b/CREDITS.txt deleted file mode 100644 index 6e66a0c..0000000 --- a/CREDITS.txt +++ /dev/null @@ -1,5 +0,0 @@ -This is a Java 8 version of the project - -https://github.com/gredler/jdk9-png-writer-backport - -by Daniel Gredler \ No newline at end of file diff --git a/barcode/NOTICE.txt b/barcode/NOTICE.txt new file mode 100644 index 0000000..91af19a --- /dev/null +++ b/barcode/NOTICE.txt @@ -0,0 +1,7 @@ +This work is based on Okapi Barcode Library + +https://github.com/woo-j/OkapiBarcode + +(Apache License 2.0) + +as of 2016 \ No newline at end of file diff --git a/barcode/build.gradle b/barcode/build.gradle new file mode 100644 index 0000000..f125b34 --- /dev/null +++ b/barcode/build.gradle @@ -0,0 +1,7 @@ +dependencies { + testImplementation project(':io-vector') + testImplementation "org.junit.jupiter:junit-jupiter-params:5.7.0" + testImplementation "junit:junit:4.12" + testImplementation "com.google.zxing:javase:${project.property('zxing.version')}" + testImplementation "org.reflections:reflections:${project.property('reflections.version')}" +} diff --git a/barcode/src/main/java/module-info.java b/barcode/src/main/java/module-info.java new file mode 100644 index 0000000..1c65a9d --- /dev/null +++ b/barcode/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module org.xbib.graphics.barcode { + exports org.xbib.graphics.barcode; + exports org.xbib.graphics.barcode.util; + exports org.xbib.graphics.barcode.render; + requires transitive java.desktop; +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/AustraliaPost.java b/barcode/src/main/java/org/xbib/graphics/barcode/AustraliaPost.java new file mode 100755 index 0000000..8ee8234 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/AustraliaPost.java @@ -0,0 +1,363 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; +import java.awt.geom.Rectangle2D; + +/** + * Implements the Australia Post 4-State barcode. + */ +public class AustraliaPost extends Symbol { + + private static final char[] CHARACTER_SET = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ' ', '#' + }; + + private static final String[] N_ENCODING_TABLE = { + "00", "01", "02", "10", "11", "12", "20", "21", "22", "30" + }; + + private static final String[] C_ENCODING_TABLE = { + "222", "300", "301", "302", "310", "311", "312", "320", "321", "322", + "000", "001", "002", "010", "011", "012", "020", "021", "022", "100", "101", "102", "110", + "111", "112", "120", "121", "122", "200", "201", "202", "210", "211", "212", "220", "221", + "023", "030", "031", "032", "033", "103", "113", "123", "130", "131", "132", "133", "203", + "213", "223", "230", "231", "232", "233", "303", "313", "323", "330", "331", "332", "333", + "003", "013" + }; + + private static final String[] BAR_VALUE_TABLE = { + "000", "001", "002", "003", "010", "011", "012", "013", "020", "021", + "022", "023", "030", "031", "032", "033", "100", "101", "102", "103", "110", "111", "112", + "113", "120", "121", "122", "123", "130", "131", "132", "133", "200", "201", "202", "203", + "210", "211", "212", "213", "220", "221", "222", "223", "230", "231", "232", "233", "300", + "301", "302", "303", "310", "311", "312", "313", "320", "321", "322", "323", "330", "331", + "332", "333" + }; + private ausMode mode; + + ; + + public AustraliaPost() { + mode = ausMode.AUSPOST; + } + + /** + * Specify encoding of Australia Post Standard Customer Barcode, + * Customer Barcode 2 or Customer Barcode 3 (37-bar, 52-bar and 67-bar + * symbols) depending on input data length. Valid data characters are 0-9, + * A-Z, a-z, space and hash (#). A Format Control Code (FCC) is added and + * should not be included in the input data. + *

+ * Input data should include a 8-digit Deliver Point ID + * (DPID) optionally followed by customer information as shown below. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Permitted Australia Post input data

Input Length

Required Input Format

Symbol Length

FCC

Encoding Table

8

99999999

37-bar

11

None

13

99999999AAAAA

52-bar

59

C

16

9999999999999999

52-bar

59

N

18

99999999AAAAAAAAAA

67-bar

62

C

23

99999999999999999999999

67-bar

62

N

+ */ + public void setPostMode() { + mode = ausMode.AUSPOST; + } + + /** + * Specify encoding of a Reply Paid version of the Australia Post + * 4-State Barcode (FCC 45) which requires an 8-digit DPID input. + */ + public void setReplyMode() { + mode = ausMode.AUSREPLY; + } + + /** + * Specify encoding of a Routing version of the Australia Post 4-State + * Barcode (FCC 87) which requires an 8-digit DPID input. + */ + public void setRouteMode() { + mode = ausMode.AUSROUTE; + } + + /** + * Specify encoding of a Redirection version of the Australia Post 4-State + * Barcode (FCC 92) which requires an 8-digit DPID input. + */ + public void setRedirectMode() { + mode = ausMode.AUSREDIRECT; + } + + @Override + public boolean encode() { + String formatControlCode = "00"; + String deliveryPointId; + StringBuilder barStateValues; + StringBuilder zeroPaddedInput = new StringBuilder(); + int i; + + switch (mode) { + case AUSPOST: + switch (content.length()) { + case 8: + formatControlCode = "11"; + break; + case 13: + formatControlCode = "59"; + break; + case 16: + formatControlCode = "59"; + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + break; + case 18: + formatControlCode = "62"; + break; + case 23: + formatControlCode = "62"; + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + break; + default: + errorMsg.append("Auspost input is wrong length"); + return false; + } + break; + case AUSREPLY: + if (content.length() > 8) { + errorMsg.append("Auspost input is too long"); + return false; + } else { + formatControlCode = "45"; + } + break; + case AUSROUTE: + if (content.length() > 8) { + errorMsg.append("Auspost input is too long"); + return false; + } else { + formatControlCode = "87"; + } + break; + case AUSREDIRECT: + if (content.length() > 8) { + errorMsg.append("Auspost input is too long"); + return false; + } else { + formatControlCode = "92"; + } + break; + } + + encodeInfo.append("FCC: ").append(formatControlCode).append('\n'); + + if (mode != ausMode.AUSPOST) { + for (i = content.length(); i < 8; i++) { + zeroPaddedInput.append("0"); + } + } + zeroPaddedInput.append(content); + + if (!(content.matches("[0-9A-Za-z #]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + + /* Verify that the first 8 characters are numbers */ + deliveryPointId = zeroPaddedInput.substring(0, 8); + + if (!(deliveryPointId.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in DPID"); + return false; + } + + encodeInfo.append("DPID: ").append(deliveryPointId).append('\n'); + + /* Start */ + barStateValues = new StringBuilder("13"); + + /* Encode the FCC */ + for (i = 0; i < 2; i++) { + barStateValues.append(N_ENCODING_TABLE[formatControlCode.charAt(i) - '0']); + } + + /* Delivery Point Identifier (DPID) */ + for (i = 0; i < 8; i++) { + barStateValues.append(N_ENCODING_TABLE[deliveryPointId.charAt(i) - '0']); + } + + /* Customer Information */ + switch (zeroPaddedInput.length()) { + case 13: + case 18: + for (i = 8; i < zeroPaddedInput.length(); i++) { + barStateValues.append(C_ENCODING_TABLE[positionOf(zeroPaddedInput.charAt(i), CHARACTER_SET)]); + } + break; + case 16: + case 23: + for (i = 8; i < zeroPaddedInput.length(); i++) { + barStateValues.append(N_ENCODING_TABLE[positionOf(zeroPaddedInput.charAt(i), CHARACTER_SET)]); + } + break; + } + + /* Filler bar */ + switch (barStateValues.length()) { + case 22: + case 37: + case 52: + barStateValues.append("3"); + break; + } + + /* Reed Solomon error correction */ + barStateValues.append(calcReedSolomon(barStateValues.toString())); + + /* Stop character */ + barStateValues.append("13"); + + encodeInfo.append("Total length: ").append(barStateValues.length()).append('\n'); + encodeInfo.append("Encoding: "); + for (i = 0; i < barStateValues.length(); i++) { + switch (barStateValues.charAt(i)) { + case '1': + encodeInfo.append("A"); + break; + case '2': + encodeInfo.append("D"); + break; + case '0': + encodeInfo.append("F"); + break; + case '3': + encodeInfo.append("T"); + break; + } + } + encodeInfo.append("\n"); + + readable = new StringBuilder(); + pattern = new String[1]; + pattern[0] = barStateValues.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + private String calcReedSolomon(String oldBarStateValues) { + ReedSolomon rs = new ReedSolomon(); + StringBuilder newBarStateValues = new StringBuilder(); + + /* Adds Reed-Solomon error correction to auspost */ + + int barStateCount; + int tripleValueCount = 0; + int[] tripleValue = new int[31]; + + for (barStateCount = 2; barStateCount < oldBarStateValues.length(); barStateCount += 3, tripleValueCount++) { + tripleValue[tripleValueCount] = barStateToDecimal(oldBarStateValues.charAt(barStateCount), 4) + + barStateToDecimal(oldBarStateValues.charAt(barStateCount + 1), 2) + + barStateToDecimal(oldBarStateValues.charAt(barStateCount + 2), 0); + } + + rs.init_gf(0x43); + rs.init_code(4, 1); + rs.encode(tripleValueCount, tripleValue); + + for (barStateCount = 4; barStateCount > 0; barStateCount--) { + newBarStateValues.append(BAR_VALUE_TABLE[rs.getResult(barStateCount - 1)]); + } + + return newBarStateValues.toString(); + } + + private int barStateToDecimal(char oldBarStateValues, int shift) { + return (oldBarStateValues - '0') << shift; + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + getRectangles().clear(); + x = 0; + w = 1; + y = 0; + h = 0; + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + switch (pattern[0].charAt(xBlock)) { + case '1': + y = 0; + h = 5; + break; + case '2': + y = 3; + h = 5; + break; + case '0': + y = 0; + h = 8; + break; + case '3': + y = 3; + h = 2; + break; + } + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + x += 2; + } + symbolWidth = pattern[0].length() * 3; + symbolHeight = 8; + } + + private enum ausMode {AUSPOST, AUSREPLY, AUSROUTE, AUSREDIRECT} +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/AztecCode.java b/barcode/src/main/java/org/xbib/graphics/barcode/AztecCode.java new file mode 100755 index 0000000..bb81d3d --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/AztecCode.java @@ -0,0 +1,1989 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; + +/** + * Implements Aztec Code bar code symbology according to ISO/IEC 24778:2008. + * Aztec Code can encode 8-bit ISO 8859-1 (Latin-1) data (except 0x00 Null + * characters) up to a maximum length of approximately 3800 numeric characters, + * 3000 alphabetic characters or 1900 bytes of data in a two-dimensional matrix + * symbol. + */ +public class AztecCode extends Symbol { + + private static final int[] COMPACT_AZTEC_MAP = { //27 x 27 data grid + 609, 608, 411, 413, 415, 417, 419, 421, 423, 425, 427, 429, 431, 433, + 435, 437, 439, 441, 443, 445, 447, 449, 451, 453, 455, 457, 459, + 607, 606, 410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, + 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, + 605, 604, 409, 408, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, + 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 460, 461, + 603, 602, 407, 406, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, + 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 462, 463, + 601, 600, 405, 404, 241, 240, 107, 109, 111, 113, 115, 117, 119, 121, + 123, 125, 127, 129, 131, 133, 135, 137, 139, 284, 285, 464, 465, + 599, 598, 403, 402, 239, 238, 106, 108, 110, 112, 114, 116, 118, 120, + 122, 124, 126, 128, 130, 132, 134, 136, 138, 286, 287, 466, 467, + 597, 596, 401, 400, 237, 236, 105, 104, 3, 5, 7, 9, 11, 13, 15, 17, 19, + 21, 23, 25, 27, 140, 141, 288, 289, 468, 469, + 595, 594, 399, 398, 235, 234, 103, 102, 2, 4, 6, 8, 10, 12, 14, 16, 18, + 20, 22, 24, 26, 142, 143, 290, 291, 470, 471, + 593, 592, 397, 396, 233, 232, 101, 100, 1, 1, 2000, 2001, 2002, 2003, + 2004, 2005, 2006, 0, 1, 28, 29, 144, 145, 292, 293, 472, 473, + 591, 590, 395, 394, 231, 230, 99, 98, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 30, 31, 146, 147, 294, 295, 474, 475, + 589, 588, 393, 392, 229, 228, 97, 96, 2027, 1, 0, 0, 0, 0, 0, 0, 0, 1, + 2007, 32, 33, 148, 149, 296, 297, 476, 477, + 587, 586, 391, 390, 227, 226, 95, 94, 2026, 1, 0, 1, 1, 1, 1, 1, 0, 1, + 2008, 34, 35, 150, 151, 298, 299, 478, 479, + 585, 584, 389, 388, 225, 224, 93, 92, 2025, 1, 0, 1, 0, 0, 0, 1, 0, 1, + 2009, 36, 37, 152, 153, 300, 301, 480, 481, + 583, 582, 387, 386, 223, 222, 91, 90, 2024, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 2010, 38, 39, 154, 155, 302, 303, 482, 483, + 581, 580, 385, 384, 221, 220, 89, 88, 2023, 1, 0, 1, 0, 0, 0, 1, 0, 1, + 2011, 40, 41, 156, 157, 304, 305, 484, 485, + 579, 578, 383, 382, 219, 218, 87, 86, 2022, 1, 0, 1, 1, 1, 1, 1, 0, 1, + 2012, 42, 43, 158, 159, 306, 307, 486, 487, + 577, 576, 381, 380, 217, 216, 85, 84, 2021, 1, 0, 0, 0, 0, 0, 0, 0, 1, + 2013, 44, 45, 160, 161, 308, 309, 488, 489, + 575, 574, 379, 378, 215, 214, 83, 82, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 46, 47, 162, 163, 310, 311, 490, 491, + 573, 572, 377, 376, 213, 212, 81, 80, 0, 0, 2020, 2019, 2018, 2017, 2016, + 2015, 2014, 0, 0, 48, 49, 164, 165, 312, 313, 492, 493, + 571, 570, 375, 374, 211, 210, 78, 76, 74, 72, 70, 68, 66, 64, 62, 60, 58, + 56, 54, 50, 51, 166, 167, 314, 315, 494, 495, + 569, 568, 373, 372, 209, 208, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, + 57, 55, 52, 53, 168, 169, 316, 317, 496, 497, + 567, 566, 371, 370, 206, 204, 202, 200, 198, 196, 194, 192, 190, 188, 186, + 184, 182, 180, 178, 176, 174, 170, 171, 318, 319, 498, 499, + 565, 564, 369, 368, 207, 205, 203, 201, 199, 197, 195, 193, 191, 189, 187, + 185, 183, 181, 179, 177, 175, 172, 173, 320, 321, 500, 501, + 563, 562, 366, 364, 362, 360, 358, 356, 354, 352, 350, 348, 346, 344, 342, + 340, 338, 336, 334, 332, 330, 328, 326, 322, 323, 502, 503, + 561, 560, 367, 365, 363, 361, 359, 357, 355, 353, 351, 349, 347, 345, 343, + 341, 339, 337, 335, 333, 331, 329, 327, 324, 325, 504, 505, + 558, 556, 554, 552, 550, 548, 546, 544, 542, 540, 538, 536, 534, 532, 530, + 528, 526, 524, 522, 520, 518, 516, 514, 512, 510, 506, 507, + 559, 557, 555, 553, 551, 549, 547, 545, 543, 541, 539, 537, 535, 533, 531, + 529, 527, 525, 523, 521, 519, 517, 515, 513, 511, 508, 509 + }; + private static final int[][] AZTEC_MAP = new int[151][151]; + private static final int[] AztecCodeSet = { /* From Table 2 */ + 32, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 4, 4, 4, 4, 4, 23, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 24, 8, 24, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, + 8, 8, 8, 8, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 4, 8, 4, 4, 4, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 8, 4, 8, 4, 4 + }; + private static final int[] AztecSymbolChar = { /* From Table 2 */ + 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 300, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 15, 16, 17, 18, 19, 1, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 301, 18, 302, 20, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 21, 22, + 23, 24, 25, 26, 20, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 27, 21, 28, 22, 23, 24, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 29, 25, 30, 26, 27 + }; + /* Problem characters are: + 300: Carriage Return (ASCII 13) + 301: Comma (ASCII 44) + 302: Full Stop (ASCII 46) + */ + private static final String[] pentbit = { + "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", "01000", "01001", + "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001", "10010", "10011", "10100", "10101", + "10110", "10111", "11000", "11001", "11010", "11011", "11100", "11101", "11110", "11111" + }; + private static final String[] quadbit = { + "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", + "1010", "1011", "1100", "1101", "1110", "1111" + }; + private static final String[] tribit = { + "000", "001", "010", "011", "100", "101", "110", "111" + }; + private static final int[] AztecSizes = { /* Codewords per symbol */ + 21, 48, 60, 88, 120, 156, 196, 240, 230, 272, 316, 364, 416, 470, 528, 588, 652, 720, 790, + 864, 940, 1020, 920, 992, 1066, 1144, 1224, 1306, 1392, 1480, 1570, 1664 + }; + private static final int[] AztecCompactSizes = { + 17, 40, 51, 76 + }; + private static final int[] Aztec10DataSizes = { /* Data bits per symbol maximum with 10% error correction */ + 96, 246, 408, 616, 840, 1104, 1392, 1704, 2040, 2420, 2820, 3250, 3720, 4200, 4730, + 5270, 5840, 6450, 7080, 7750, 8430, 9150, 9900, 10680, 11484, 12324, 13188, 14076, + 15000, 15948, 16920, 17940 + }; + private static final int[] Aztec23DataSizes = { /* Data bits per symbol maximum with 23% error correction */ + 84, 204, 352, 520, 720, 944, 1184, 1456, 1750, 2070, 2410, 2780, 3180, 3590, 4040, + 4500, 5000, 5520, 6060, 6630, 7210, 7830, 8472, 9132, 9816, 10536, 11280, 12036, + 12828, 13644, 14472, 15348 + }; + private static final int[] Aztec36DataSizes = { /* Data bits per symbol maximum with 36% error correction */ + 66, 168, 288, 432, 592, 776, 984, 1208, 1450, 1720, 2000, 2300, 2640, 2980, 3350, + 3740, 4150, 4580, 5030, 5500, 5990, 6500, 7032, 7584, 8160, 8760, 9372, 9996, 10656, + 11340, 12024, 12744 + }; + private static final int[] Aztec50DataSizes = { /* Data bits per symbol maximum with 50% error correction */ + 48, 126, 216, 328, 456, 600, 760, 936, 1120, 1330, 1550, 1790, 2050, 2320, 2610, + 2910, 3230, 3570, 3920, 4290, 4670, 5070, 5484, 5916, 6360, 6828, 7308, 7800, 8316, + 8844, 9384, 9948 + }; + private static final int[] AztecCompact10DataSizes = { + 78, 198, 336, 520 + }; + private static final int[] AztecCompact23DataSizes = { + 66, 168, 288, 440 + }; + private static final int[] AztecCompact36DataSizes = { + 48, 138, 232, 360 + }; + private static final int[] AztecCompact50DataSizes = { + 36, 102, 176, 280 + }; + private static final int[] AztecOffset = { + 66, 64, 62, 60, 57, 55, 53, 51, 49, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 25, 23, 21, + 19, 17, 15, 13, 10, 8, 6, 4, 2, 0 + }; + private static final int[] AztecCompactOffset = { + 6, 4, 2, 0 + }; + private StringBuilder binaryString; + private int preferredSize = 0; + private int preferredEccLevel = -1; + + /** + * + */ + public AztecCode() { + int layer, start, length, n, i; + int x, y; + + for (x = 0; x < 151; x++) { + for (y = 0; y < 151; y++) { + AZTEC_MAP[x][y] = 0; + } + } + + for (layer = 1; layer < 33; layer++) { + start = (112 * (layer - 1)) + (16 * (layer - 1) * (layer - 1)) + 2; + length = 28 + ((layer - 1) * 4) + (layer * 4); + /* Top */ + i = 0; + x = 64 - ((layer - 1) * 2); + y = 63 - ((layer - 1) * 2); + for (n = start; n < (start + length); n += 2) { + AZTEC_MAP[avoidReferenceGrid(x + i)][avoidReferenceGrid(y)] = n; + AZTEC_MAP[avoidReferenceGrid(x + i)][avoidReferenceGrid(y - 1)] = n + 1; + i++; + } + /* Right */ + i = 0; + x = 78 + ((layer - 1) * 2); + y = 64 - ((layer - 1) * 2); + for (n = start + length; n < (start + (length * 2)); n += 2) { + AZTEC_MAP[avoidReferenceGrid(x)][avoidReferenceGrid(y + i)] = n; + AZTEC_MAP[avoidReferenceGrid(x + 1)][avoidReferenceGrid(y + i)] = n + 1; + i++; + } + /* Bottom */ + i = 0; + x = 77 + ((layer - 1) * 2); + y = 78 + ((layer - 1) * 2); + for (n = start + (length * 2); n < (start + (length * 3)); n += 2) { + AZTEC_MAP[avoidReferenceGrid(x - i)][avoidReferenceGrid(y)] = n; + AZTEC_MAP[avoidReferenceGrid(x - i)][avoidReferenceGrid(y + 1)] = n + 1; + i++; + } + /* Left */ + i = 0; + x = 63 - ((layer - 1) * 2); + y = 77 + ((layer - 1) * 2); + for (n = start + (length * 3); n < (start + (length * 4)); n += 2) { + AZTEC_MAP[avoidReferenceGrid(x)][avoidReferenceGrid(y - i)] = n; + AZTEC_MAP[avoidReferenceGrid(x - 1)][avoidReferenceGrid(y - i)] = n + 1; + i++; + } + } + + /* Central finder pattern */ + for (y = 69; y <= 81; y++) { + for (x = 69; x <= 81; x++) { + AZTEC_MAP[x][y] = 1; + } + } + for (y = 70; y <= 80; y++) { + for (x = 70; x <= 80; x++) { + AZTEC_MAP[x][y] = 0; + } + } + for (y = 71; y <= 79; y++) { + for (x = 71; x <= 79; x++) { + AZTEC_MAP[x][y] = 1; + } + } + for (y = 72; y <= 78; y++) { + for (x = 72; x <= 78; x++) { + AZTEC_MAP[x][y] = 0; + } + } + for (y = 73; y <= 77; y++) { + for (x = 73; x <= 77; x++) { + AZTEC_MAP[x][y] = 1; + } + } + for (y = 74; y <= 76; y++) { + for (x = 74; x <= 76; x++) { + AZTEC_MAP[x][y] = 0; + } + } + + /* Guide bars */ + for (y = 11; y < 151; y += 16) { + for (x = 1; x < 151; x += 2) { + AZTEC_MAP[x][y] = 1; + AZTEC_MAP[y][x] = 1; + } + } + + /* Descriptor */ + for (i = 0; i < 10; i++) { /* Top */ + + AZTEC_MAP[avoidReferenceGrid(66 + i)][avoidReferenceGrid(64)] = 20000 + i; + } + for (i = 0; i < 10; i++) { /* Right */ + + AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(66 + i)] = 20010 + i; + } + for (i = 0; i < 10; i++) { /* Bottom */ + + AZTEC_MAP[avoidReferenceGrid(75 - i)][avoidReferenceGrid(77)] = 20020 + i; + } + for (i = 0; i < 10; i++) { /* Left */ + + AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(75 - i)] = 20030 + i; + } + + /* Orientation */ + AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(64)] = 1; + AZTEC_MAP[avoidReferenceGrid(65)][avoidReferenceGrid(64)] = 1; + AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(65)] = 1; + AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(64)] = 1; + AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(65)] = 1; + AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(76)] = 1; + } + + /** + * Sets a preferred symbol size. This value may be ignored if data string is + * too large to fit in the specified symbol size. Values correspond to + * symbol sizes as shown in the following table: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Available Aztec Code symbol sizes

+ * Input

+ * Symbol Size

+ * Input

+ * Symbol Size

+ * 1

+ * 15 x 15*

+ * 19

+ * 79 x 79

+ * 2

+ * 19 x 19*

+ * 20

+ * 83 x 83

+ * 3

+ * 23 x 23*

+ * 21

+ * 87 x 87

+ * 4

+ * 27 x 27*

+ * 22

+ * 91 x 91

+ * 5

+ * 19 x 19

+ * 23

+ * 95 x 95

+ * 6

+ * 23 x 23

+ * 24

+ * 101 x 101

+ * 7

+ * 27 x 27

+ * 25

+ * 105 x 105

+ * 8

+ * 31 x 31

+ * 26

+ * 109 x 109

+ * 9

+ * 37 x 37

+ * 27

+ * 113 x 113

+ * 10

+ * 41 x 41

+ * 28

+ * 117 x 117

+ * 11

+ * 45 x 45

+ * 29

+ * 121 x 121

+ * 12

+ * 49 x 49

+ * 30

+ * 125 x 125

+ * 13

+ * 53 x 53

+ * 31

+ * 131 x 131

+ * 14

+ * 57 x 57

+ * 32

+ * 135 x 135

+ * 15

+ * 61 x 61

+ * 33

+ * 139 x 139

+ * 16

+ * 67 x 67

+ * 34

+ * 143 x 143

+ * 17

+ * 71 x 71

+ * 35

+ * 147 x 147

+ * 18

+ * 75 x 75

+ * 36

+ * 151 x 151

+ * + * @param size An integer in the range 1 - 36 + */ + public void setPreferredSize(int size) { + preferredSize = size; + } + + /** + * Sets the preferred minimum amount of symbol space dedicated to error + * correction. This value will be ignored if a symbol size has been set by + * setPreferredSize Valid options are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Error correction options

+ * Mode

+ * Error Correction Capacity

+ * 1

+ * >10% + 3 codewords

+ * 2

+ * >23% + 3 codewords

+ * 3

+ * >36% + 3 codewords

+ * 4

+ * >50% + 3 codewords

+ * + * @param eccLevel An integer in the range 1 - 4 + */ + public void setPreferredEccLevel(int eccLevel) { + preferredEccLevel = eccLevel; + } + + private int avoidReferenceGrid(int input) { + int output; + + output = input; + if (output > 10) { + output++; + } + if (output > 26) { + output++; + } + if (output > 42) { + output++; + } + if (output > 58) { + output++; + } + if (output > 74) { + output++; + } + if (output > 90) { + output++; + } + if (output > 106) { + output++; + } + if (output > 122) { + output++; + } + if (output > 138) { + output++; + } + + return output; + } + + @Override + public boolean encode() { + int i, ecc_level, data_length, layers, data_maxsize; + int adjustment_size, codeword_size; + int j, count, adjusted_length, padbits, remainder; + StringBuilder adjusted_string = new StringBuilder(); + StringBuilder bit_pattern = new StringBuilder(); + int comp_loop = 4; + int data_blocks, ecc_blocks, total_bits; + boolean compact; + ReedSolomon rs = new ReedSolomon(); + ReedSolomon rs2 = new ReedSolomon(); + StringBuilder descriptor = new StringBuilder(); + int[] desc_data = new int[4]; + int[] desc_ecc = new int[6]; + int y, x, weight; + StringBuilder bin; + int t; + boolean done; + + if (readerInit) { + comp_loop = 1; + } + + eciProcess(); // Get ECI mode + + if ((inputDataType == DataType.GS1) && (readerInit)) { + errorMsg.append("Cannot encode in GS1 and Reader Initialisation mode at the same time"); + return false; + } + + if (!generateAztecBinary()) { + errorMsg.append("Input too long or too many extended ASCII characters"); + return false; + } + + // Set the error correction level + if ((preferredEccLevel <= 0) || (preferredEccLevel > 4)) { + ecc_level = 2; + } else { + ecc_level = preferredEccLevel; + } + + data_length = binaryString.length(); + + layers = 0; /* Keep compiler happy! */ + + data_maxsize = 0; /* Keep compiler happy! */ + + adjustment_size = 0; + + if ((preferredSize < 0) || (preferredSize > 36)) { + preferredSize = 0; + } + + if (preferredSize == 0) { /* The size of the symbol can be determined by Okapi */ + + do { + /* Decide what size symbol to use - the smallest that fits the data */ + compact = false; /* 1 = Aztec Compact, 0 = Normal Aztec */ + + layers = 0; + + switch (ecc_level) { + /* For each level of error correction work out the smallest symbol which + the data will fit in */ + case 1: + for (i = 32; i > 0; i--) { + if ((data_length + adjustment_size) < Aztec10DataSizes[i - 1]) { + layers = i; + compact = false; + data_maxsize = Aztec10DataSizes[i - 1]; + } + } + for (i = comp_loop; i > 0; i--) { + if ((data_length + adjustment_size) < AztecCompact10DataSizes[i - 1]) { + layers = i; + compact = true; + data_maxsize = AztecCompact10DataSizes[i - 1]; + } + } + break; + case 2: + for (i = 32; i > 0; i--) { + if ((data_length + adjustment_size) < Aztec23DataSizes[i - 1]) { + layers = i; + compact = false; + data_maxsize = Aztec23DataSizes[i - 1]; + } + } + for (i = comp_loop; i > 0; i--) { + if ((data_length + adjustment_size) < AztecCompact23DataSizes[i - 1]) { + layers = i; + compact = true; + data_maxsize = AztecCompact23DataSizes[i - 1]; + } + } + break; + case 3: + for (i = 32; i > 0; i--) { + if ((data_length + adjustment_size) < Aztec36DataSizes[i - 1]) { + layers = i; + compact = false; + data_maxsize = Aztec36DataSizes[i - 1]; + } + } + for (i = comp_loop; i > 0; i--) { + if ((data_length + adjustment_size) < AztecCompact36DataSizes[i - 1]) { + layers = i; + compact = true; + data_maxsize = AztecCompact36DataSizes[i - 1]; + } + } + break; + case 4: + for (i = 32; i > 0; i--) { + if ((data_length + adjustment_size) < Aztec50DataSizes[i - 1]) { + layers = i; + compact = false; + data_maxsize = Aztec50DataSizes[i - 1]; + } + } + for (i = comp_loop; i > 0; i--) { + if ((data_length + adjustment_size) < AztecCompact50DataSizes[i - 1]) { + layers = i; + compact = true; + data_maxsize = AztecCompact50DataSizes[i - 1]; + } + } + break; + } + + if (layers == 0) { /* Couldn't find a symbol which fits the data */ + + errorMsg.append("Input too long (too many bits for selected ECC)"); + return false; + } + + /* Determine codeword bitlength - Table 3 */ + codeword_size = 6; /* if (layers <= 2) */ + + if ((layers >= 3) && (layers <= 8)) { + codeword_size = 8; + } + if ((layers >= 9) && (layers <= 22)) { + codeword_size = 10; + } + if (layers >= 23) { + codeword_size = 12; + } + + j = 0; + i = 0; + + do { + if ((j + 1) % codeword_size == 0) { + /* Last bit of codeword */ + done = false; + count = 0; + + /* Discover how many '1's in current codeword */ + for (t = 0; t < (codeword_size - 1); t++) { + if (binaryString.charAt((i - (codeword_size - 1)) + t) == '1') { + count++; + } + } + + if (count == (codeword_size - 1)) { + adjusted_string.append('0'); + j++; + done = true; + } + + if (count == 0) { + adjusted_string.append('1'); + j++; + done = true; + } + + if (!done) { + adjusted_string.append(binaryString.charAt(i)); + j++; + i++; + } + } else { + adjusted_string.append(binaryString.charAt(i)); + j++; + i++; + } + } while (i < data_length); + + adjusted_length = adjusted_string.length(); + adjustment_size = adjusted_length - data_length; + + /* Add padding */ + remainder = adjusted_length % codeword_size; + + padbits = codeword_size - remainder; + if (padbits == codeword_size) { + padbits = 0; + } + + for (i = 0; i < padbits; i++) { + adjusted_string.append("1"); + } + adjusted_length = adjusted_string.length(); + + count = 0; + for (i = (adjusted_length - codeword_size); i < adjusted_length; i++) { + if (adjusted_string.charAt(i) == '1') { + count++; + } + } + if (count == codeword_size) { + adjusted_string = new StringBuilder(adjusted_string.substring(0, adjusted_length - 1) + '0'); + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < (adjusted_length / codeword_size); i++) { + int l = 0, m = (1 << (codeword_size - 1)); + for (j = 0; j < codeword_size; j++) { + if (adjusted_string.charAt((i * codeword_size) + j) == '1') { + l += m; + } + m = m >> 1; + } + encodeInfo.append(Integer.toString(l)).append(" "); + } + encodeInfo.append("\n"); + + } while (adjusted_length > data_maxsize); + /* This loop will only repeat on the rare occasions when the rule about not having all 1s or all 0s + means that the binary string has had to be lengthened beyond the maximum number of bits that can + be encoded in a symbol of the selected size */ + } else { + /* The size of the symbol has been specified by the user */ + compact = false; + if ((readerInit) && ((preferredSize >= 2) && (preferredSize <= 4))) { + preferredSize = 5; + } + if ((preferredSize >= 1) && (preferredSize <= 4)) { + compact = true; + layers = preferredSize; + } + if ((preferredSize >= 5) && (preferredSize <= 36)) { + layers = preferredSize - 4; + } + + /* Determine codeword bitlength - Table 3 */ + codeword_size = 6; + if ((layers >= 3) && (layers <= 8)) { + codeword_size = 8; + } + if ((layers >= 9) && (layers <= 22)) { + codeword_size = 10; + } + if (layers >= 23) { + codeword_size = 12; + } + j = 0; + i = 0; + do { + if (((j + 1) % codeword_size) == 0) { + /* Last bit of codeword */ + done = false; + count = 0; + + /* Discover how many '1's in current codeword */ + for (t = 0; t < (codeword_size - 1); t++) { + if (binaryString.charAt((i - (codeword_size - 1)) + t) == '1') { + count++; + } + } + + if (count == (codeword_size - 1)) { + adjusted_string.append('0'); + j++; + done = true; + } + + if (count == 0) { + adjusted_string.append('1'); + j++; + done = true; + } + + if (!done) { + adjusted_string.append(binaryString.charAt(i)); + j++; + i++; + } + } else { + adjusted_string.append(binaryString.charAt(i)); + j++; + i++; + } + } while (i < binaryString.length()); + + adjusted_length = adjusted_string.length(); + remainder = adjusted_length % codeword_size; + padbits = codeword_size - remainder; + + if (padbits == codeword_size) { + padbits = 0; + } + for (i = 0; i < padbits; i++) { + adjusted_string.append("1"); + } + + adjusted_length = adjusted_string.length(); + count = 0; + + for (i = (adjusted_length - codeword_size); i < adjusted_length; i++) { + if (adjusted_string.charAt(i) == '1') { + count++; + } + } + + if (count == codeword_size) { + adjusted_string = new StringBuilder(adjusted_string.substring(0, adjusted_length - 1) + '0'); + } + + /* Check if the data actually fits into the selected symbol size */ + if (compact) { + data_maxsize = codeword_size * (AztecCompactSizes[layers - 1] - 3); + } else { + data_maxsize = codeword_size * (AztecSizes[layers - 1] - 3); + } + + if (adjusted_length > data_maxsize) { + errorMsg.append("Data too long for specified Aztec Code symbol size"); + return false; + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < (adjusted_length / codeword_size); i++) { + int l = 0, m = (1 << (codeword_size - 1)); + for (j = 0; j < codeword_size; j++) { + if (adjusted_string.charAt((i * codeword_size) + j) == '1') { + l += m; + } + m = m >> 1; + } + encodeInfo.append(Integer.toString(l)).append(" "); + } + encodeInfo.append("\n"); + } + + if (readerInit && (layers > 22)) { + errorMsg.append("Data too long for reader initialisation symbol"); + return false; + } + + data_blocks = adjusted_length / codeword_size; + + if (compact) { + ecc_blocks = AztecCompactSizes[layers - 1] - data_blocks; + } else { + ecc_blocks = AztecSizes[layers - 1] - data_blocks; + } + + encodeInfo.append("Compact Mode: "); + if (compact) { + encodeInfo.append("TRUE\n"); + } else { + encodeInfo.append("FALSE\n"); + } + encodeInfo.append("Layers: ").append(layers).append('\n'); + encodeInfo.append("Codeword Length: ").append(codeword_size).append(" bits\n"); + encodeInfo.append("Data Codewords: ").append(data_blocks).append('\n'); + encodeInfo.append("ECC Codewords: ").append(ecc_blocks).append('\n'); + + int[] data_part = new int[data_blocks + 3]; + int[] ecc_part = new int[ecc_blocks + 3]; + + /* Split into codewords and calculate reed-colomon error correction codes */ + switch (codeword_size) { + case 6: + for (i = 0; i < data_blocks; i++) { + for (weight = 0; weight < 6; weight++) { + if (adjusted_string.charAt((i * codeword_size) + weight) == '1') { + data_part[i] += (32 >> weight); + } + } + } + rs.init_gf(0x43); + rs.init_code(ecc_blocks, 1); + rs.encode(data_blocks, data_part); + for (i = 0; i < ecc_blocks; i++) { + ecc_part[i] = rs.getResult(i); + } + for (i = (ecc_blocks - 1); i >= 0; i--) { + for (weight = 0x20; weight > 0; weight = weight >> 1) { + if ((ecc_part[i] & weight) != 0) { + adjusted_string.append("1"); + } else { + adjusted_string.append("0"); + } + } + } + break; + case 8: + for (i = 0; i < data_blocks; i++) { + for (weight = 0; weight < 8; weight++) { + if (adjusted_string.charAt((i * codeword_size) + weight) == '1') { + data_part[i] += (128 >> weight); + } + } + } + rs.init_gf(0x12d); + rs.init_code(ecc_blocks, 1); + rs.encode(data_blocks, data_part); + for (i = 0; i < ecc_blocks; i++) { + ecc_part[i] = rs.getResult(i); + } + for (i = (ecc_blocks - 1); i >= 0; i--) { + for (weight = 0x80; weight > 0; weight = weight >> 1) { + if ((ecc_part[i] & weight) != 0) { + adjusted_string.append("1"); + } else { + adjusted_string.append("0"); + } + } + } + break; + case 10: + for (i = 0; i < data_blocks; i++) { + for (weight = 0; weight < 10; weight++) { + if (adjusted_string.charAt((i * codeword_size) + weight) == '1') { + data_part[i] += (512 >> weight); + } + } + } + rs.init_gf(0x409); + rs.init_code(ecc_blocks, 1); + rs.encode(data_blocks, data_part); + for (i = 0; i < ecc_blocks; i++) { + ecc_part[i] = rs.getResult(i); + } + for (i = (ecc_blocks - 1); i >= 0; i--) { + for (weight = 0x200; weight > 0; weight = weight >> 1) { + if ((ecc_part[i] & weight) != 0) { + adjusted_string.append("1"); + } else { + adjusted_string.append("0"); + } + } + } + break; + case 12: + for (i = 0; i < data_blocks; i++) { + for (weight = 0; weight < 12; weight++) { + if (adjusted_string.charAt((i * codeword_size) + weight) == '1') { + data_part[i] += (2048 >> weight); + } + } + } + rs.init_gf(0x1069); + rs.init_code(ecc_blocks, 1); + rs.encode(data_blocks, data_part); + for (i = 0; i < ecc_blocks; i++) { + ecc_part[i] = rs.getResult(i); + } + for (i = (ecc_blocks - 1); i >= 0; i--) { + for (weight = 0x800; weight > 0; weight = weight >> 1) { + if ((ecc_part[i] & weight) != 0) { + adjusted_string.append("1"); + } else { + adjusted_string.append("0"); + } + } + } + break; + } + + /* Invert the data so that actual data is on the outside and reed-solomon on the inside */ + total_bits = (data_blocks + ecc_blocks) * codeword_size; + for (i = 0; i < total_bits; i++) { + bit_pattern.append(adjusted_string.charAt(total_bits - i - 1)); + } + + if (compact) { + /* The first 2 bits represent the number of layers minus 1 */ + if (((layers - 1) & 0x02) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + if (((layers - 1) & 0x01) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + /* The next 6 bits represent the number of data blocks minus 1 */ + if (readerInit) { + descriptor.append('1'); + } else { + if (((data_blocks - 1) & 0x20) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + } + for (i = 0x10; i > 0; i = i >> 1) { + if (((data_blocks - 1) & i) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + } + encodeInfo.append("Mode Message: ").append(descriptor).append("\n"); + j = 2; + } else { + /* The first 5 bits represent the number of layers minus 1 */ + for (i = 0x10; i > 0; i = i >> 1) { + if (((layers - 1) & i) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + } + + /* The next 11 bits represent the number of data blocks minus 1 */ + if (readerInit) { + descriptor.append('1'); + } else { + if (((data_blocks - 1) & 0x400) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + } + for (i = 0x200; i > 0; i = i >> 1) { + if (((data_blocks - 1) & i) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + } + + encodeInfo.append("Mode Message: ").append(descriptor).append("\n"); + j = 4; + } + + /* Split into 4-bit codewords */ + for (i = 0; i < j; i++) { + for (weight = 0; weight < 4; weight++) { + if (descriptor.charAt((i * 4) + weight) == '1') { + desc_data[i] += (8 >> weight); + } + } + } + + /* Add reed-solomon error correction with Galois field GF(16) and prime modulus + x^4 + x + 1 (section 7.2.3)*/ + rs2.init_gf(0x13); + if (compact) { + rs2.init_code(5, 1); + rs2.encode(2, desc_data); + for (j = 0; j < 5; j++) { + desc_ecc[j] = rs2.getResult(j); + } + for (i = 0; i < 5; i++) { + for (weight = 0x08; weight > 0; weight = weight >> 1) { + if ((desc_ecc[4 - i] & weight) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + } + } + } else { + rs2.init_code(6, 1); + rs2.encode(4, desc_data); + for (j = 0; j < 6; j++) { + desc_ecc[j] = rs2.getResult(j); + } + for (i = 0; i < 6; i++) { + for (weight = 0x08; weight > 0; weight = weight >> 1) { + if ((desc_ecc[5 - i] & weight) != 0) { + descriptor.append('1'); + } else { + descriptor.append('0'); + } + } + } + } + + readable = new StringBuilder(); + + /* Plot all of the data into the symbol in pre-defined spiral pattern */ + if (compact) { + + rowCount = 27 - (2 * AztecCompactOffset[layers - 1]); + rowHeight = new int[rowCount]; + rowHeight[0] = -1; + pattern = new String[rowCount]; + bin = new StringBuilder(); + for (y = AztecCompactOffset[layers - 1]; y < (27 - AztecCompactOffset[layers - 1]); y++) { + for (x = AztecCompactOffset[layers - 1]; x < (27 - AztecCompactOffset[layers - 1]); x++) { + j = COMPACT_AZTEC_MAP[(y * 27) + x]; + + if (j == 0) { + bin.append("0"); + } + if (j == 1) { + bin.append("1"); + } + + if (j >= 2) { + if ((j - 2) < bit_pattern.length()) { + bin.append(bit_pattern.charAt(j - 2)); + } else { + if (j > 2000) { + bin.append(descriptor.charAt(j - 2000)); + } else { + bin.append("0"); + } + } + } + } + rowHeight[y - AztecCompactOffset[layers - 1]] = 1; + pattern[y - AztecCompactOffset[layers - 1]] = bin2pat(bin.toString()); + bin = new StringBuilder(); + } + + } else { + rowCount = 151 - (2 * AztecOffset[layers - 1]); + rowHeight = new int[rowCount]; + rowHeight[0] = -1; + pattern = new String[rowCount]; + bin = new StringBuilder(); + for (y = AztecOffset[layers - 1]; y < (151 - AztecOffset[layers - 1]); y++) { + for (x = AztecOffset[layers - 1]; x < (151 - AztecOffset[layers - 1]); x++) { + j = AZTEC_MAP[x][y]; + if (j == 1) { + bin.append("1"); + } + if (j == 0) { + bin.append("0"); + } + if (j >= 2) { + if ((j - 2) < bit_pattern.length()) { + bin.append(bit_pattern.charAt(j - 2)); + } else { + if (j > 20000) { + bin.append(descriptor.charAt(j - 20000)); + } else { + bin.append("0"); + } + } + } + } + rowHeight[y - AztecOffset[layers - 1]] = 1; + pattern[y - AztecOffset[layers - 1]] = bin2pat(bin.toString()); + bin = new StringBuilder(); + } + } + + plotSymbol(); + return true; + } + + private boolean generateAztecBinary() { + /* Encode input data into a binary string */ + int i, j, k, bytes; + int curtable, newtable, lasttable, chartype, maplength, blocks; + int[] charmap = new int[2 * inputBytes.length]; + int[] typemap = new int[2 * inputBytes.length]; + int[] blockType = new int[inputBytes.length + 1]; + int[] blockLength = new int[inputBytes.length + 1]; + int weight; + + /* Lookup input string in encoding table */ + maplength = 0; + + if (inputDataType == DataType.GS1) { + /* Add FNC1 to beginning of GS1 messages */ + charmap[maplength] = 0; // FLG + typemap[maplength++] = 8; // PUNC + charmap[maplength] = 400; // (0) + typemap[maplength++] = 8; // PUNC + } + + if (eciMode != 3) { + int flagNumber; + + charmap[maplength] = 0; // FLG + typemap[maplength++] = 8; // PUNC + + flagNumber = 6; + + if (eciMode < 100000) { + flagNumber = 5; + } + + if (eciMode < 10000) { + flagNumber = 4; + } + + if (eciMode < 1000) { + flagNumber = 3; + } + + if (eciMode < 100) { + flagNumber = 2; + } + + if (eciMode < 10) { + flagNumber = 1; + } + + charmap[maplength] = 400 + flagNumber; + typemap[maplength++] = 8; // PUNC + } + + for (i = 0; i < inputBytes.length; i++) { + if ((inputDataType == DataType.GS1) && ((inputBytes[i] & 0xFF) == '[')) { + /* FNC1 represented by FLG(0) */ + charmap[maplength] = 0; // FLG + typemap[maplength++] = 8; // PUNC + charmap[maplength] = 400; // (0) + typemap[maplength++] = 8; // PUNC + } else { + if (((inputBytes[i] & 0xFF) > 0x7F) || ((inputBytes[i] & 0xFF) == 0x00)) { + charmap[maplength] = (inputBytes[i] & 0xFF); + typemap[maplength++] = 32; //BINARY + } else { + charmap[maplength] = AztecSymbolChar[(inputBytes[i] & 0xFF)]; + typemap[maplength++] = AztecCodeSet[(inputBytes[i] & 0xFF)]; + } + } + } + + /* Look for double character encoding possibilities */ + i = 0; + do { + if (((charmap[i] == 300) && (charmap[i + 1] == 11)) && ((typemap[i] == 8 /*PUNC */) && (typemap[i + 1] == 8 /*PUNC*/))) { + /* CR LF combination */ + charmap[i] = 2; + typemap[i] = 8; // PUNC + if ((i + 1) != maplength) { + for (j = i + 1; j < maplength; j++) { + charmap[j] = charmap[j + 1]; + typemap[j] = typemap[j + 1]; + } + } + maplength--; + } + + if (((charmap[i] == 302) && (charmap[i + 1] == 1)) && ((typemap[i] == 24) && (typemap[i + 1] == 23))) { + /* . SP combination */ + charmap[i] = 3; + typemap[i] = 8; // PUNC; + if ((i + 1) != maplength) { + for (j = i + 1; j < maplength; j++) { + charmap[j] = charmap[j + 1]; + typemap[j] = typemap[j + 1]; + } + } + maplength--; + } + + if (((charmap[i] == 301) && (charmap[i + 1] == 1)) && ((typemap[i] == 24) && (typemap[i + 1] == 23))) { + /* , SP combination */ + charmap[i] = 4; + typemap[i] = 8; //PUNC; + if ((i + 1) != maplength) { + for (j = i + 1; j < maplength; j++) { + charmap[j] = charmap[j + 1]; + typemap[j] = typemap[j + 1]; + } + } + maplength--; + } + + if (((charmap[i] == 21) && (charmap[i + 1] == 1)) && ((typemap[i] == 8) && (typemap[i + 1] == 23))) { + /* : SP combination */ + charmap[i] = 5; + typemap[i] = 8; //PUNC; + if ((i + 1) != maplength) { + for (j = i + 1; j < maplength; j++) { + charmap[j] = charmap[j + 1]; + typemap[j] = typemap[j + 1]; + } + } + maplength--; + } + + i++; + } while (i < (maplength - 1)); + + /* look for blocks of characters which use the same table */ + blocks = 1; + blockType[0] = typemap[0]; + blockLength[0] = 1; + for (i = 1; i < maplength; i++) { + if (typemap[i] == typemap[i - 1]) { + blockLength[blocks - 1]++; + } else { + blocks++; + blockType[blocks - 1] = typemap[i]; + blockLength[blocks - 1] = 1; + } + } + + if ((blockType[0] & 1) != 0) { + blockType[0] = 1; + } + if ((blockType[0] & 2) != 0) { + blockType[0] = 2; + } + if ((blockType[0] & 4) != 0) { + blockType[0] = 4; + } + if ((blockType[0] & 8) != 0) { + blockType[0] = 8; + } + + if (blocks > 1) { + + /* look for adjacent blocks which can use the same table (left to right search) */ + for (i = 1; i < blocks; i++) { + if ((blockType[i] & blockType[i - 1]) != 0) { + blockType[i] = (blockType[i] & blockType[i - 1]); + } + } + + if ((blockType[blocks - 1] & 1) != 0) { + blockType[blocks - 1] = 1; + } + if ((blockType[blocks - 1] & 2) != 0) { + blockType[blocks - 1] = 2; + } + if ((blockType[blocks - 1] & 4) != 0) { + blockType[blocks - 1] = 4; + } + if ((blockType[blocks - 1] & 8) != 0) { + blockType[blocks - 1] = 8; + } + + /* look for adjacent blocks which can use the same table (right to left search) */ + for (i = blocks - 2; i > 0; i--) { + if ((blockType[i] & blockType[i + 1]) != 0) { + blockType[i] = (blockType[i] & blockType[i + 1]); + } + } + + /* determine the encoding table for characters which do not fit with adjacent blocks */ + for (i = 1; i < blocks; i++) { + if ((blockType[i] & 8) != 0) { + blockType[i] = 8; + } + if ((blockType[i] & 4) != 0) { + blockType[i] = 4; + } + if ((blockType[i] & 2) != 0) { + blockType[i] = 2; + } + if ((blockType[i] & 1) != 0) { + blockType[i] = 1; + } + } + + /* if less than 4 characters are preceeded and followed by binary blocks + then it is more efficient to also encode these in binary + */ + + /* Combine blocks of the same type */ + i = 0; + do { + if (blockType[i] == blockType[i + 1]) { + blockLength[i] += blockLength[i + 1]; + for (j = i + 1; j < blocks - 1; j++) { + blockType[j] = blockType[j + 1]; + blockLength[j] = blockLength[j + 1]; + } + blocks--; + } else { + i++; + } + } while (i < blocks - 1); + } + + /* Put the adjusted block data back into typemap */ + j = 0; + for (i = 0; i < blocks; i++) { + if ((blockLength[i] < 3) && (blockType[i] != 32)) { /* Shift character(s) needed */ + + for (k = 0; k < blockLength[i]; k++) { + typemap[j + k] = blockType[i] + 64; + } + } else { /* Latch character (or byte mode) needed */ + + for (k = 0; k < blockLength[i]; k++) { + typemap[j + k] = blockType[i]; + } + } + j += blockLength[i]; + } + + /* Don't shift an initial capital letter */ + if (typemap[0] == 65) { + typemap[0] = 1; + } + + /* Problem characters (those that appear in different tables with different values) can now be resolved into their tables */ + for (i = 0; i < maplength; i++) { + if ((charmap[i] >= 300) && (charmap[i] < 400)) { + curtable = typemap[i]; + if (curtable > 64) { + curtable -= 64; + } + switch (charmap[i]) { + case 300: + /* Carriage Return */ + switch (curtable) { + case 8: + charmap[i] = 1; + break; // PUNC + case 4: + charmap[i] = 14; + break; // PUNC + } + break; + case 301: + /* Comma */ + switch (curtable) { + case 8: + charmap[i] = 17; + break; // PUNC + case 16: + charmap[i] = 12; + break; // DIGIT + } + break; + case 302: + /* Full Stop */ + switch (curtable) { + case 8: + charmap[i] = 19; + break; // PUNC + case 16: + charmap[i] = 13; + break; // DIGIT + } + break; + } + } + } + + binaryString = new StringBuilder(); + + encodeInfo.append("Encoding: "); + + curtable = 1; /* start with 1 table */ + + lasttable = 1; + for (i = 0; i < maplength; i++) { + newtable = curtable; + if ((typemap[i] != curtable) && (charmap[i] < 400)) { + /* Change table */ + if (curtable == 32) { + /* If ending binary mode the current table is the same as when entering binary mode */ + curtable = lasttable; + newtable = lasttable; + } + if (typemap[i] > 64) { + /* Shift character */ + switch (typemap[i]) { + case (64 + 1): + /* To UPPER */ + switch (curtable) { + case 2: + /* US */ + binaryString.append(pentbit[28]); + encodeInfo.append("US "); + break; + case 4: + /* UL */ + binaryString.append(pentbit[29]); + encodeInfo.append("UL "); + newtable = 1; + break; + case 8: + /* UL */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + newtable = 1; + break; + case 16: + /* US */ + binaryString.append(quadbit[15]); + encodeInfo.append("US "); + break; + } + break; + case (64 + 2): + /* To LOWER */ + switch (curtable) { + case 1: + /* LL */ + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + case 4: + /* LL */ + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + case 8: + /* UL LL */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + case 16: + /* UL LL */ + binaryString.append(quadbit[14]); + encodeInfo.append("UL "); + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + } + break; + case (64 + 4): + /* To MIXED */ + switch (curtable) { + case 1: + /* ML */ + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + case 2: + /* ML */ + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + case 8: + /* UL ML */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + case 16: + /* UL ML */ + binaryString.append(quadbit[14]); + encodeInfo.append("UL "); + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + } + break; + case (64 + 8): + /* To PUNC */ + switch (curtable) { + case 1: + /* PS */ + binaryString.append(pentbit[0]); + encodeInfo.append("PS "); + break; + case 2: + /* PS */ + binaryString.append(pentbit[0]); + encodeInfo.append("PS "); + break; + case 4: + /* PS */ + binaryString.append(pentbit[0]); + encodeInfo.append("PS "); + break; + case 16: + /* PS */ + binaryString.append(quadbit[0]); + encodeInfo.append("PS "); + break; + } + break; + case (64 + 16): + /* To DIGIT */ + switch (curtable) { + case 1: + /* DL */ + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + case 2: + /* DL */ + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + case 4: + /* UL DL */ + binaryString.append(pentbit[29]); + encodeInfo.append("UL "); + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + case 8: + /* UL DL */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + } + break; + } + } else { + /* Latch character */ + switch (typemap[i]) { + case 1: + /* To UPPER */ + switch (curtable) { + case 2: + /* ML UL */ + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + binaryString.append(pentbit[29]); + encodeInfo.append("UL "); + newtable = 1; + break; + case 4: + /* UL */ + binaryString.append(pentbit[29]); + encodeInfo.append("UL "); + newtable = 1; + break; + case 8: + /* UL */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + newtable = 1; + break; + case 16: + /* UL */ + binaryString.append(quadbit[14]); + encodeInfo.append("UL "); + newtable = 1; + break; + } + break; + case 2: + /* To LOWER */ + switch (curtable) { + case 1: + /* LL */ + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + case 4: + /* LL */ + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + case 8: + /* UL LL */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + case 16: + /* UL LL */ + binaryString.append(quadbit[14]); + encodeInfo.append("UL "); + binaryString.append(pentbit[28]); + encodeInfo.append("LL "); + newtable = 2; + break; + } + break; + case 4: + /* To MIXED */ + switch (curtable) { + case 1: + /* ML */ + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + case 2: + /* ML */ + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + case 8: + /* UL ML */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + case 16: + /* UL ML */ + binaryString.append(quadbit[14]); + encodeInfo.append("UL "); + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + newtable = 4; + break; + } + break; + case 8: + /* To PUNC */ + switch (curtable) { + case 1: + /* ML PL */ + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + binaryString.append(pentbit[30]); + encodeInfo.append("PL "); + newtable = 8; + break; + case 2: + /* ML PL */ + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + binaryString.append(pentbit[30]); + encodeInfo.append("PL "); + newtable = 8; + break; + case 4: + /* PL */ + binaryString.append(pentbit[30]); + encodeInfo.append("PL "); + newtable = 8; + break; + case 16: + /* UL ML PL */ + binaryString.append(quadbit[14]); + encodeInfo.append("UL "); + binaryString.append(pentbit[29]); + encodeInfo.append("ML "); + binaryString.append(pentbit[30]); + encodeInfo.append("PL "); + newtable = 8; + break; + } + break; + case 16: + /* To DIGIT */ + switch (curtable) { + case 1: + /* DL */ + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + case 2: + /* DL */ + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + case 4: + /* UL DL */ + binaryString.append(pentbit[29]); + encodeInfo.append("UL "); + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + case 8: + /* UL DL */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + binaryString.append(pentbit[30]); + encodeInfo.append("DL "); + newtable = 16; + break; + } + break; + case 32: + /* To BINARY */ + lasttable = curtable; + switch (curtable) { + case 1: + /* BS */ + binaryString.append(pentbit[31]); + encodeInfo.append("BS "); + newtable = 32; + break; + case 2: + /* BS */ + binaryString.append(pentbit[31]); + encodeInfo.append("BS "); + newtable = 32; + break; + case 4: + /* BS */ + binaryString.append(pentbit[31]); + encodeInfo.append("BS "); + newtable = 32; + break; + case 8: + /* UL BS */ + binaryString.append(pentbit[31]); + encodeInfo.append("UL "); + binaryString.append(pentbit[31]); + encodeInfo.append("BS "); + lasttable = 1; + newtable = 32; + break; + case 16: + /* UL BS */ + binaryString.append(quadbit[14]); + encodeInfo.append("UL "); + binaryString.append(pentbit[31]); + encodeInfo.append("BS "); + lasttable = 1; + newtable = 32; + break; + } + + bytes = 0; + do { + bytes++; + } while (typemap[i + (bytes - 1)] == 32); + bytes--; + + if (bytes > 2079) { + errorMsg.append("Input too long"); + return false; + } + + if (bytes > 31) { /* Put 00000 followed by 11-bit number of bytes less 31 */ + + binaryString.append("00000"); + for (weight = 0x400; weight > 0; weight = weight >> 1) { + if (((bytes - 31) & weight) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } else { /* Put 5-bit number of bytes */ + + for (weight = 0x10; weight > 0; weight = weight >> 1) { + if ((bytes & weight) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + + break; + } + } + } + /* Add data to the binary string */ + curtable = newtable; + chartype = typemap[i]; + if (chartype > 64) { + chartype -= 64; + } + switch (chartype) { + case 1: + case 2: + case 4: + case 8: + if (charmap[i] >= 400) { + encodeInfo.append("FLG(").append(Integer.toString(charmap[i] - 400)).append(") "); + binaryString.append(tribit[charmap[i] - 400]); + if (charmap[i] != 400) { + /* ECI */ + binaryString.append(eciToBinary()); + } + } else { + binaryString.append(pentbit[charmap[i]]); + encodeInfo.append(Integer.toString(charmap[i])).append(" "); + } + break; + case 16: + binaryString.append(quadbit[charmap[i]]); + encodeInfo.append(Integer.toString(charmap[i])); + break; + case 32: + for (weight = 0x80; weight > 0; weight = weight >> 1) { + if ((charmap[i] & weight) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + encodeInfo.append(Integer.toString(charmap[i])); + break; + } + + } + encodeInfo.append("\n"); + return true; + } + + private String eciToBinary() { + StringBuilder binary = new StringBuilder(); + String eciNumber = Integer.toString(eciMode); + int i; + + for (i = 0; i < eciNumber.length(); i++) { + binary.append(quadbit[(eciNumber.charAt(i) - '0') + 2]); + encodeInfo.append(Character.toString(eciNumber.charAt(i))).append(" "); + } + + return binary.toString(); + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/AztecRune.java b/barcode/src/main/java/org/xbib/graphics/barcode/AztecRune.java new file mode 100755 index 0000000..2077831 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/AztecRune.java @@ -0,0 +1,168 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; + +/** + * Implements Aztec Runes bar code symbology. + * According to ISO/IEC 24778:2008 Annex A + * Aztec Runes is a fixed-size matrix symbology which can encode whole + * integer values between 0 and 255. + */ +public class AztecRune extends Symbol { + + private int[] bitPlacementMap = { + 1, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 29, 1, 0, 0, 0, 0, 0, 0, 0, 1, 9, + 28, 1, 0, 1, 1, 1, 1, 1, 0, 1, 10, + 27, 1, 0, 1, 0, 0, 0, 1, 0, 1, 11, + 26, 1, 0, 1, 0, 1, 0, 1, 0, 1, 12, + 25, 1, 0, 1, 0, 0, 0, 1, 0, 1, 13, + 24, 1, 0, 1, 1, 1, 1, 1, 0, 1, 14, + 23, 1, 0, 0, 0, 0, 0, 0, 0, 1, 15, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 22, 21, 20, 19, 18, 17, 16, 0, 0 + }; + + @Override + public boolean encode() { + int decimalValue = 0; + int i; + int row; + int column; + StringBuilder binaryDataStream; + StringBuilder reversedBinaryDataStream; + int[] dataCodeword = new int[3]; + int[] errorCorrectionCodeword = new int[6]; + ReedSolomon rs = new ReedSolomon(); + StringBuilder rowBinary; + + if (content.length() > 3) { + errorMsg.append("Input too large"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid input data"); + return false; + } + + switch (content.length()) { + case 3: + decimalValue = 100 * (content.charAt(0) - '0'); + decimalValue += 10 * (content.charAt(1) - '0'); + decimalValue += (content.charAt(2) - '0'); + break; + case 2: + decimalValue = 10 * (content.charAt(0) - '0'); + decimalValue += (content.charAt(1) - '0'); + break; + case 1: + decimalValue = (content.charAt(0) - '0'); + break; + } + + if (decimalValue > 255) { + errorMsg.append("Input too large"); + return false; + } + + binaryDataStream = new StringBuilder(); + for (i = 0x80; i > 0; i = i >> 1) { + if ((decimalValue & i) != 0) { + binaryDataStream.append("1"); + } else { + binaryDataStream.append("0"); + } + } + + dataCodeword[0] = 0; + dataCodeword[1] = 0; + + for (i = 0; i < 2; i++) { + if (binaryDataStream.charAt(i * 4) == '1') { + dataCodeword[i] += 8; + } + if (binaryDataStream.charAt((i * 4) + 1) == '1') { + dataCodeword[i] += 4; + } + if (binaryDataStream.charAt((i * 4) + 2) == '1') { + dataCodeword[i] += 2; + } + if (binaryDataStream.charAt((i * 4) + 3) == '1') { + dataCodeword[i] += 1; + } + } + + rs.init_gf(0x13); + rs.init_code(5, 1); + rs.encode(2, dataCodeword); + + for (i = 0; i < 5; i++) { + errorCorrectionCodeword[i] = rs.getResult(i); + } + + for (i = 0; i < 5; i++) { + if ((errorCorrectionCodeword[4 - i] & 0x08) != 0) { + binaryDataStream.append('1'); + } else { + binaryDataStream.append('0'); + } + if ((errorCorrectionCodeword[4 - i] & 0x04) != 0) { + binaryDataStream.append('1'); + } else { + binaryDataStream.append('0'); + } + if ((errorCorrectionCodeword[4 - i] & 0x02) != 0) { + binaryDataStream.append('1'); + } else { + binaryDataStream.append('0'); + } + if ((errorCorrectionCodeword[4 - i] & 0x01) != 0) { + binaryDataStream.append('1'); + } else { + binaryDataStream.append('0'); + } + } + + reversedBinaryDataStream = new StringBuilder(); + for (i = 0; i < binaryDataStream.length(); i++) { + if ((i & 1) == 0) { + if (binaryDataStream.charAt(i) == '0') { + reversedBinaryDataStream.append("1"); + } else { + reversedBinaryDataStream.append("0"); + } + } else { + reversedBinaryDataStream.append(binaryDataStream.charAt(i)); + } + } + + encodeInfo.append("Binary: ").append(reversedBinaryDataStream).append("\n"); + + rowBinary = new StringBuilder(); + readable = new StringBuilder(); + pattern = new String[11]; + rowCount = 11; + rowHeight = new int[11]; + for (row = 0; row < 11; row++) { + for (column = 0; column < 11; column++) { + if (bitPlacementMap[(row * 11) + column] == 1) { + rowBinary.append("1"); + } + if (bitPlacementMap[(row * 11) + column] == 0) { + rowBinary.append("0"); + } + if (bitPlacementMap[(row * 11) + column] >= 2) { + rowBinary.append(reversedBinaryDataStream.charAt(bitPlacementMap[(row * 11) + column] - 2)); + } + } + pattern[row] = bin2pat(rowBinary.toString()); + rowHeight[row] = 1; + rowBinary = new StringBuilder(); + } + + plotSymbol(); + return true; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/ChannelCode.java b/barcode/src/main/java/org/xbib/graphics/barcode/ChannelCode.java new file mode 100755 index 0000000..ddd7301 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/ChannelCode.java @@ -0,0 +1,155 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Channel Code according to ANSI/AIM BC12-1998. + * Channel code encodes whole integer values between 0 and 7,742,862. + */ +public class ChannelCode extends Symbol { + private int[] space = new int[11]; + private int[] bar = new int[11]; + private double currentValue; + private double targetValue; + private String horizontalSpacing; + private int requestedNumberOfChannels = 0; + + /** + * Set the number of channels used to encode data. This setting will be + * ignored if the value to be encoded requires more channels. + * + * @param channels Number of channels in range 3 - 8 + */ + public void setNumberOfChannels(int channels) { + if ((channels >= 3) && (channels <= 7)) { + requestedNumberOfChannels = channels; + } + } + + @Override + @SuppressWarnings("fallthrough") + public boolean encode() { + int numberOfChannels; + int i; + int leadingZeroCount; + + targetValue = 0; + horizontalSpacing = ""; + + if (content.length() > 7) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if ((requestedNumberOfChannels <= 2) || (requestedNumberOfChannels > 8)) { + numberOfChannels = 3; + } else { + numberOfChannels = requestedNumberOfChannels; + } + + for (i = 0; i < content.length(); i++) { + targetValue *= 10; + targetValue += Character.getNumericValue(content.charAt(i)); + } + + switch (numberOfChannels) { + case 3: + if (targetValue > 26) { + numberOfChannels++; + } + case 4: + if (targetValue > 292) { + numberOfChannels++; + } + case 5: + if (targetValue > 3493) { + numberOfChannels++; + } + case 6: + if (targetValue > 44072) { + numberOfChannels++; + } + case 7: + if (targetValue > 576688) { + numberOfChannels++; + } + case 8: + if (targetValue > 7742862) { + numberOfChannels++; + } + } + + if (numberOfChannels == 9) { + errorMsg.append("Value out of range"); + return false; + } + + encodeInfo.append("Channels Used: ").append(numberOfChannels).append('\n'); + + for (i = 0; i < 11; i++) { + bar[i] = 0; + space[i] = 0; + } + + bar[0] = space[1] = bar[1] = space[2] = bar[2] = 1; + currentValue = 0; + nextSpace(numberOfChannels, 3, numberOfChannels, numberOfChannels); + + leadingZeroCount = numberOfChannels - 1 - content.length(); + + readable = new StringBuilder(); + for (i = 0; i < leadingZeroCount; i++) { + readable.append("0"); + } + readable.append(content); + + pattern = new String[1]; + pattern[0] = horizontalSpacing; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + private void nextSpace(int channels, int i, int maxSpace, int maxBar) { + int s; + + for (s = (i < channels + 2) ? 1 : maxSpace; s <= maxSpace; s++) { + space[i] = s; + nextBar(channels, i, maxBar, maxSpace + 1 - s); + } + } + + private void nextBar(int channels, int i, int maxBar, int maxSpace) { + int b; + + b = (space[i] + bar[i - 1] + space[i - 1] + bar[i - 2] > 4) ? 1 : 2; + if (i < channels + 2) { + for (; b <= maxBar; b++) { + bar[i] = b; + nextSpace(channels, i + 1, maxSpace, maxBar + 1 - b); + } + } else if (b <= maxBar) { + bar[i] = maxBar; + checkIfDone(); + currentValue++; + } + } + + private void checkIfDone() { + int i; + + if (currentValue == targetValue) { + /* Target reached - save the generated pattern */ + horizontalSpacing = "11110"; + for (i = 0; i < 11; i++) { + horizontalSpacing += (char) (space[i] + '0'); + horizontalSpacing += (char) (bar[i] + '0'); + } + } + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Codabar.java b/barcode/src/main/java/org/xbib/graphics/barcode/Codabar.java new file mode 100755 index 0000000..87aa226 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Codabar.java @@ -0,0 +1,86 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Codabar barcode symbology according to BS EN 798:1996. + * Also known as NW-7, Monarch, ABC Codabar, USD-4, Ames Code and Code 27. + * Codabar can encode any length string starting and ending with the letters + * A-D and containing between these letters the numbers 0-9, dash (-), dollar + * ($), colon (:), slash (/), full stop (.) or plus (+). No check digit is + * generated. + */ +public class Codabar extends Symbol { + + private static final String[] CODABAR_TABLE = { + "11111221", "11112211", "11121121", "22111111", "11211211", + "21111211", "12111121", "12112111", "12211111", "21121111", + "11122111", "11221111", "21112121", "21211121", "21212111", + "11212121", "11221211", "12121121", "11121221", "11122211" + }; + + private static final char[] CHARACTER_SET = { + '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', + '-', '$', ':', '/', '.', + '+', 'A', 'B', 'C', 'D' + }; + + /** + * Ratio of wide bar width to narrow bar width. + */ + private double moduleWidthRatio = 2; + + /** + * Returns the ratio of wide bar width to narrow bar width. + * + * @return the ratio of wide bar width to narrow bar width + */ + public double getModuleWidthRatio() { + return moduleWidthRatio; + } + + /** + * Sets the ratio of wide bar width to narrow bar width. Valid values are usually + * between {@code 2} and {@code 3}. The default value is {@code 2}. + * + * @param moduleWidthRatio the ratio of wide bar width to narrow bar width + */ + public void setModuleWidthRatio(double moduleWidthRatio) { + this.moduleWidthRatio = moduleWidthRatio; + } + + @Override + public boolean encode() { + if (!(content.matches("[A-D]{1}[0-9:/\\$\\.\\+\u002D]+[A-D]{1}"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + StringBuilder horizontalSpacing = new StringBuilder(); + + int l = content.length(); + for (int i = 0; i < l; i++) { + horizontalSpacing.append(CODABAR_TABLE[positionOf(content.charAt(i), CHARACTER_SET)]); + } + + readable = new StringBuilder(content); + pattern = new String[]{horizontalSpacing.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + plotSymbol(); + return true; + } + + @Override + protected double getModuleWidth(int originalWidth) { + if (originalWidth == 1) { + return 1; + } else { + return moduleWidthRatio; + } + } + + @Override + protected int[] getCodewords() { + return getPatternAsCodewords(8); + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/CodablockF.java b/barcode/src/main/java/org/xbib/graphics/barcode/CodablockF.java new file mode 100755 index 0000000..a3df7e4 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/CodablockF.java @@ -0,0 +1,852 @@ +package org.xbib.graphics.barcode; + +import java.awt.geom.Rectangle2D; +import java.io.UnsupportedEncodingException; + +/** + * Implements Codablock-F according to AIM Europe "Uniform Symbology Specification - Codablock F", 1995. + * Codablock F is a multi-row symbology using Code 128 encoding. It can + * encode any 8-bit ISO 8859-1 (Latin-1) data up to approximately 1000 + * alpha-numeric characters or 2000 numeric digits in length. + */ +public class CodablockF extends Symbol { + + /* Annex A Table A.1 */ + private String[] C128Table = {"212222", "222122", "222221", "121223", "121322", "131222", "122213", + "122312", "132212", "221213", "221312", "231212", "112232", "122132", "122231", "113222", + "123122", "123221", "223211", "221132", "221231", "213212", "223112", "312131", "311222", + "321122", "321221", "312212", "322112", "322211", "212123", "212321", "232121", "111323", + "131123", "131321", "112313", "132113", "132311", "211313", "231113", "231311", "112133", + "112331", "132131", "113123", "113321", "133121", "313121", "211331", "231131", "213113", + "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111", "314111", + "221411", "431111", "111224", "111422", "121124", "121421", "141122", "141221", "112214", + "112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112", + "134111", "111242", "121142", "121241", "114212", "124112", "124211", "411212", "421112", + "421211", "212141", "214121", "412121", "111143", "111341", "131141", "114113", "114311", + "411113", "411311", "113141", "114131", "311141", "411131", "211412", "211214", "211232", + "2331112"}; + private final int[][] blockmatrix = new int[44][62]; + private int columns_needed; + private int[] source; + private int rows_needed; + private cfMode final_mode; + private final cfMode[] subset_selector = new cfMode[44]; + + @Override + public boolean encode() { + int input_length, i, j, k; + int min_module_height; + Mode last_mode, this_mode; + double estimate_codelength; + StringBuilder row_pattern; + int[] row_indicator = new int[44]; + int[] row_check = new int[44]; + int k1_sum, k2_sum; + int k1_check, k2_check; + + input_length = content.length(); + final_mode = cfMode.MODEA; + + if (input_length > 5450) { + errorMsg.append("Input data too long"); + return false; + } + + if (!content.matches("[\u0000-\u00FF]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + try { + inputBytes = content.getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Character encoding error"); + return false; + } + + source = new int[input_length + 1]; + for (i = 0; i < input_length; i++) { + source[i] = inputBytes[i] & 0xFF; + } + source[input_length] = 0x00; + + /* Make a guess at how many characters will be needed to encode the data */ + estimate_codelength = 0.0; + last_mode = Mode.AORB; /* Codablock always starts with Code A */ + for (i = 0; i < input_length; i++) { + this_mode = findSubset(source[i]); + if (this_mode != last_mode) { + estimate_codelength += 1.0; + } + if (this_mode != Mode.ABORC) { + estimate_codelength += 1.0; + } else { + estimate_codelength += 0.5; + } + if (source[i] > 127) { + estimate_codelength += 1.0; + } + last_mode = this_mode; + } + + /* Decide symbol size based on the above guess */ + rows_needed = (int) (0.5 + Math.sqrt((estimate_codelength + 2) / 1.45)); + if (rows_needed < 2) { + rows_needed = 2; + } + if (rows_needed > 44) { + rows_needed = 44; + } + columns_needed = (int) (estimate_codelength + 2) / rows_needed; + if (columns_needed < 4) { + columns_needed = 4; + } + if (columns_needed > 62) { + errorMsg.append("Input data too long"); + return false; + } + + /* Encode the data */ + if (!(data_encode_blockf())) { + return false; + } + + /* Add check digits - Annex F */ + k1_sum = 0; + k2_sum = 0; + for (i = 0; i < input_length; i++) { + if ((inputDataType == DataType.GS1) && source[i] == '[') { + k1_sum += (i + 1) * 29; /* GS */ + k2_sum += i * 29; + } else { + k1_sum += (i + 1) * source[i]; + k2_sum += i * source[i]; + } + } + k1_check = k1_sum % 86; + k2_check = k2_sum % 86; + if ((final_mode == cfMode.MODEA) || (final_mode == cfMode.MODEB)) { + k1_check = k1_check + 64; + if (k1_check > 95) { + k1_check -= 96; + } + k2_check = k2_check + 64; + if (k2_check > 95) { + k2_check -= 96; + } + } + blockmatrix[rows_needed - 1][columns_needed - 2] = k1_check; + blockmatrix[rows_needed - 1][columns_needed - 1] = k2_check; + + /* Calculate row height (4.6.1.a) */ + min_module_height = (int) (0.55 * (columns_needed + 3)) + 3; + if (min_module_height < 8) { + min_module_height = 8; + } + + /* Encode the Row Indicator in the First Row of the Symbol - Table D2 */ + if (subset_selector[0] == cfMode.MODEC) { + /* Code C */ + row_indicator[0] = rows_needed - 2; + } else { + /* Code A or B */ + row_indicator[0] = rows_needed + 62; + + if (row_indicator[0] > 95) { + row_indicator[0] -= 95; + } + } + + /* Encode the Row Indicator in the Second and Subsequent Rows of the Symbol - Table D3 */ + for (i = 1; i < rows_needed; i++) { + /* Note that the second row is row number 1 because counting starts from 0 */ + if (subset_selector[i] == cfMode.MODEC) { + /* Code C */ + row_indicator[i] = i + 42; + } else { + /* Code A or B */ + if (i < 6) + row_indicator[i] = i + 10; + else + row_indicator[i] = i + 20; + } + } + + /* Calculate row check digits - Annex E */ + for (i = 0; i < rows_needed; i++) { + k = 103; + switch (subset_selector[i]) { + case MODEA: + k += 98; + break; + case MODEB: + k += 100; + break; + case MODEC: + k += 99; + break; + } + k += 2 * row_indicator[i]; + for (j = 0; j < columns_needed; j++) { + k += (j + 3) * blockmatrix[i][j]; + } + row_check[i] = k % 103; + } + + readable = new StringBuilder(); + rowCount = rows_needed; + pattern = new String[rowCount]; + rowHeight = new int[rowCount]; + + encodeInfo.append("Grid size: ").append(columns_needed).append(" X ").append(rows_needed).append('\n'); + encodeInfo.append("K1 Check Digit: ").append(k1_check).append("\n"); + encodeInfo.append("K2 Check Digit: ").append(k2_check).append("\n"); + + /* Resolve the data into patterns and place in symbol structure */ + encodeInfo.append("Encoding: "); + for (i = 0; i < rows_needed; i++) { + + row_pattern = new StringBuilder(); + /* Start character */ + row_pattern.append(C128Table[103]); /* Always Start A */ + + switch (subset_selector[i]) { + case MODEA: + row_pattern.append(C128Table[98]); + encodeInfo.append("MODEA "); + break; + case MODEB: + row_pattern.append(C128Table[100]); + encodeInfo.append("MODEB "); + break; + case MODEC: + row_pattern.append(C128Table[99]); + encodeInfo.append("MODEC "); + break; + } + row_pattern.append(C128Table[row_indicator[i]]); + encodeInfo.append(Integer.toString(row_indicator[i])).append(" "); + + for (j = 0; j < columns_needed; j++) { + row_pattern.append(C128Table[blockmatrix[i][j]]); + encodeInfo.append(Integer.toString(blockmatrix[i][j])).append(" "); + } + + row_pattern.append(C128Table[row_check[i]]); + encodeInfo.append("(").append(Integer.toString(row_check[i])).append(") "); + + /* Stop character */ + row_pattern.append(C128Table[106]); + + /* Write the information into the symbol */ + pattern[i] = row_pattern.toString(); + rowHeight[i] = 15; + } + encodeInfo.append("\n"); + + symbolHeight = rows_needed * 15; + plotSymbol(); + return true; + } + + private Mode findSubset(int letter) { + Mode mode; + + if (letter <= 31) { + mode = Mode.SHIFTA; + } else if ((letter >= 48) && (letter <= 57)) { + mode = Mode.ABORC; + } else if (letter <= 95) { + mode = Mode.AORB; + } else if (letter <= 127) { + mode = Mode.SHIFTB; + } else if (letter <= 159) { + mode = Mode.SHIFTA; + } else if (letter <= 223) { + mode = Mode.AORB; + } else { + mode = Mode.SHIFTB; + } + + return mode; + } + + private boolean data_encode_blockf() { + int i, j, input_position, current_row; + int column_position, c; + cfMode current_mode; + boolean done, exit_status; + + exit_status = false; + current_row = 0; + current_mode = cfMode.MODEA; + column_position = 0; + input_position = 0; + c = 0; + + do { + done = false; + /* 'done' ensures that the instructions are followed in the correct order for each input character */ + + if (column_position == 0) { + /* The Beginning of a row */ + c = columns_needed; + current_mode = character_subset_select(input_position); + subset_selector[current_row] = current_mode; + if ((current_row == 0) && (inputDataType == DataType.GS1)) { + /* Section 4.4.7.1 */ + blockmatrix[current_row][column_position] = 102; /* FNC1 */ + column_position++; + c--; + } + } + + if ((inputDataType == DataType.GS1) && (source[input_position] == '[')) { + blockmatrix[current_row][column_position] = 102; /* FNC1 */ + column_position++; + c--; + input_position++; + done = true; + } + + if (!done) { + if (c <= 2) { + /* Annex B section 1 rule 1 */ + /* Ensure that there is sufficient encodation capacity to continue (using the rules of Annex B.2). */ + switch (current_mode) { + case MODEA: /* Table B1 applies */ + if (findSubset(source[input_position]) == Mode.ABORC) { + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + done = true; + } + + if ((findSubset(source[input_position]) == Mode.SHIFTB) && (c == 1)) { + /* Needs two symbols */ + blockmatrix[current_row][column_position] = 100; /* Code B */ + column_position++; + c--; + done = true; + } + + if ((source[input_position] >= 244) && (!done)) { + /* Needs three symbols */ + blockmatrix[current_row][column_position] = 100; /* Code B */ + column_position++; + c--; + if (c == 1) { + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + } + done = true; + } + + if ((source[input_position] >= 128) && (!done) && c == 1) { + /* Needs two symbols */ + blockmatrix[current_row][column_position] = 100; /* Code B */ + column_position++; + c--; + done = true; + } + break; + case MODEB: /* Table B2 applies */ + if (findSubset(source[input_position]) == Mode.ABORC) { + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + done = true; + } + + if ((findSubset(source[input_position]) == Mode.SHIFTA) && (c == 1)) { + /* Needs two symbols */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + done = true; + } + + if (((source[input_position] >= 128) + && (source[input_position] <= 159)) && (!done)) { + /* Needs three symbols */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + if (c == 1) { + blockmatrix[current_row][column_position] = 100; /* Code B */ + column_position++; + c--; + } + done = true; + } + + if ((source[input_position] >= 160) && (!done) && c == 1) { + /* Needs two symbols */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + done = true; + } + break; + case MODEC: /* Table B3 applies */ + if ((findSubset(source[input_position]) != Mode.ABORC) && (c == 1)) { + /* Needs two symbols */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + done = true; + } + + if (((findSubset(source[input_position]) == Mode.ABORC) + && (findSubset(source[input_position + 1]) != Mode.ABORC)) + && (c == 1)) { + /* Needs two symbols */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + done = true; + } + + if (source[input_position] >= 128) { + /* Needs three symbols */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + if (c == 1) { + blockmatrix[current_row][column_position] = 100; /* Code B */ + column_position++; + c--; + } + } + break; + } + } + } + + if (!done) { + if (((findSubset(source[input_position]) == Mode.AORB) + || (findSubset(source[input_position]) == Mode.SHIFTA)) + && (current_mode == cfMode.MODEA)) { + /* Annex B section 1 rule 2 */ + /* If in Code Subset A and the next data character can be encoded in Subset A encode the next + character. */ + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 101; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + done = true; + } + } + + if (!done) { + if (((findSubset(source[input_position]) == Mode.AORB) + || (findSubset(source[input_position]) == Mode.SHIFTB)) + && (current_mode == cfMode.MODEB)) { + /* Annex B section 1 rule 3 */ + /* If in Code Subset B and the next data character can be encoded in subset B, encode the next + character. */ + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 100; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + done = true; + } + } + + if (!done) { + if (((findSubset(source[input_position]) == Mode.ABORC) + && (findSubset(source[input_position + 1]) == Mode.ABORC)) + && (current_mode == cfMode.MODEC)) { + /* Annex B section 1 rule 4 */ + /* If in Code Subset C and the next data are 2 digits, encode them. */ + blockmatrix[current_row][column_position] + = ((source[input_position] - '0') * 10) + + (source[input_position + 1] - '0'); + column_position++; + c--; + input_position += 2; + done = true; + } + } + + if (!done) { + if (((current_mode == cfMode.MODEA) || (current_mode == cfMode.MODEB)) + && ((findSubset(source[input_position]) == Mode.ABORC) + || ((inputDataType == DataType.GS1) && (source[input_position] == '[')))) { + /* Count the number of numeric digits */ + /* If 4 or more numeric data characters occur together when in subsets A or B: + a. If there is an even number of numeric data characters, insert a Code C character before the + first numeric digit to change to subset C. + b. If there is an odd number of numeric data characters, insert a Code Set C character immedi- + ately after the first numeric digit to change to subset C. */ + i = 0; + j = 0; + do { + i++; + if ((inputDataType == DataType.GS1) && (source[input_position + j] == '[')) { + i++; + } + j++; + } while ((findSubset(source[input_position + j]) == Mode.ABORC) + || ((inputDataType == DataType.GS1) && (source[input_position + j] == '['))); + i--; + + if (i >= 4) { + /* Annex B section 1 rule 5 */ + if ((i % 2) == 1) { + /* Annex B section 1 rule 5a */ + blockmatrix[current_row][column_position] = 99; /* Code C */ + column_position++; + c--; + blockmatrix[current_row][column_position] = ((source[input_position] - '0') * 10) + + (source[input_position + 1] - '0'); + column_position++; + c--; + input_position += 2; + current_mode = cfMode.MODEC; + } else { + /* Annex B section 1 rule 5b */ + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + } + done = true; + } else { + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + done = true; + } + } + } + + if (!done) { + if ((current_mode == cfMode.MODEB) && (findSubset(source[input_position]) == Mode.SHIFTA)) { + /* Annex B section 1 rule 6 */ + /* When in subset B and an ASCII control character occurs in the data: + a. If there is a lower case character immediately following the control character, insert a Shift + character before the control character. + b. Otherwise, insert a Code A character before the control character to change to subset A. */ + if ((source[input_position + 1] >= 96) && (source[input_position + 1] <= 127)) { + /* Annex B section 1 rule 6a */ + blockmatrix[current_row][column_position] = 98; /* Shift */ + column_position++; + c--; + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 100; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + } else { + /* Annex B section 1 rule 6b */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 100; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + current_mode = cfMode.MODEA; + } + done = true; + } + } + + if (!done) { + if ((current_mode == cfMode.MODEA) && (findSubset(source[input_position]) == Mode.SHIFTB)) { + /* Annex B section 1 rule 7 */ + /* When in subset A and a lower case character occurs in the data: + a. If following that character, a control character occurs in the data before the occurrence of + another lower case character, insert a Shift character before the lower case character. + b. Otherwise, insert a Code B character before the lower case character to change to subset B. */ + if ((findSubset(source[input_position + 1]) == Mode.SHIFTA) + && (findSubset(source[input_position + 2]) == Mode.SHIFTB)) { + /* Annex B section 1 rule 7a */ + blockmatrix[current_row][column_position] = 98; /* Shift */ + column_position++; + c--; + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 101; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + } else { + /* Annex B section 1 rule 7b */ + blockmatrix[current_row][column_position] = 100; /* Code B */ + column_position++; + c--; + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 101; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + current_mode = cfMode.MODEB; + } + done = true; + } + } + + if (!done) { + if ((current_mode == cfMode.MODEC) && ((findSubset(source[input_position]) != Mode.ABORC) + || (findSubset(source[input_position + 1]) != Mode.ABORC))) { + /* Annex B section 1 rule 8 */ + /* When in subset C and a non-numeric character (or a single digit) occurs in the data, insert a Code + A or Code B character before that character, following rules 8a and 8b to determine between code + subsets A and B. + a. If an ASCII control character (eg NUL) occurs in the data before any lower case character, use + Code A. + b. Otherwise use Code B. */ + if (findSubset(source[input_position]) == Mode.SHIFTA) { + /* Annex B section 1 rule 8a */ + blockmatrix[current_row][column_position] = 101; /* Code A */ + column_position++; + c--; + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 101; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + current_mode = cfMode.MODEA; + } else { + /* Annex B section 1 rule 8b */ + blockmatrix[current_row][column_position] = 100; /* Code B */ + column_position++; + c--; + if (source[input_position] >= 128) { + /* Extended ASCII character */ + blockmatrix[current_row][column_position] = 100; /* FNC4 */ + column_position++; + c--; + } + blockmatrix[current_row][column_position] = a3_convert(source[input_position]); + column_position++; + c--; + input_position++; + current_mode = cfMode.MODEB; + } + } + } + + if (input_position == content.length()) { + /* End of data - Annex B rule 5a */ + if (c == 1) { + if (current_mode == cfMode.MODEA) { + blockmatrix[current_row][column_position] = 100; /* Code B */ + current_mode = cfMode.MODEB; + } else { + blockmatrix[current_row][column_position] = 101; /* Code A */ + current_mode = cfMode.MODEA; + } + column_position++; + c--; + } + + if (c == 0) { + /* Another row is needed */ + column_position = 0; + c = columns_needed; + current_row++; + subset_selector[current_row] = cfMode.MODEA; + current_mode = cfMode.MODEA; + } + + if (c > 2) { + /* Fill up the last row */ + do { + if (current_mode == cfMode.MODEA) { + blockmatrix[current_row][column_position] = 100; /* Code B */ + current_mode = cfMode.MODEB; + } else { + blockmatrix[current_row][column_position] = 101; /* Code A */ + current_mode = cfMode.MODEA; + } + column_position++; + c--; + } while (c > 2); + } + + /* If (c == 2) { do nothing } */ + + exit_status = true; + final_mode = current_mode; + } else { + if (c <= 0) { + /* Start new row - Annex B rule 5b */ + column_position = 0; + current_row++; + if (current_row > 43) { + return false; + } + } + } + + } while (!exit_status); + + if (current_row == 0) { + /* fill up the first row */ + for (c = column_position; c <= columns_needed; c++) { + if (current_mode == cfMode.MODEA) { + blockmatrix[current_row][c] = 100; /* Code B */ + current_mode = cfMode.MODEB; + } else { + blockmatrix[current_row][c] = 101; /* Code A */ + current_mode = cfMode.MODEA; + } + } + current_row++; + /* add a second row */ + subset_selector[current_row] = cfMode.MODEA; + current_mode = cfMode.MODEA; + for (c = 0; c <= columns_needed - 2; c++) { + if (current_mode == cfMode.MODEA) { + blockmatrix[current_row][c] = 100; /* Code B */ + current_mode = cfMode.MODEB; + } else { + blockmatrix[current_row][c] = 101; /* Code A */ + current_mode = cfMode.MODEA; + } + } + } + rows_needed = current_row + 1; + + return true; + } + + private cfMode character_subset_select(int input_position) { + /* Section 4.5.2 - Determining the Character Subset Selector in a Row */ + + if ((source[input_position] >= '0') && (source[input_position + 1] <= '9')) { + /* Rule 1 */ + return cfMode.MODEC; + } + + if ((source[input_position] >= 128) && (source[input_position] <= 160)) { + /* Rule 2 (i) */ + return cfMode.MODEA; + } + + if ((source[input_position] >= 0) && (source[input_position] <= 31)) { + /* Rule 3 */ + return cfMode.MODEA; + } + + /* Rule 4 */ + return cfMode.MODEB; + } + + private int a3_convert(int source) { + /* Annex A section 3 */ + if (source < 32) { + return source + 64; + } + if (source <= 127) { + return source - 32; + } + if (source <= 159) { + return (source - 128) + 64; + } + /* if source >= 160 */ + return (source - 128) - 32; + } + + @Override + protected void plotSymbol() { + int xBlock, yBlock; + int x, y, w, h; + boolean black; + getRectangles().clear(); + y = 1; + h = 1; + for (yBlock = 0; yBlock < rowCount; yBlock++) { + black = true; + x = 0; + for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) { + if (black) { + black = false; + w = pattern[yBlock].charAt(xBlock) - '0'; + if (rowHeight[yBlock] == -1) { + h = defaultHeight; + } else { + h = rowHeight[yBlock]; + } + if (w != 0 && h != 0) { + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + } + if ((x + w) > symbolWidth) { + symbolWidth = x + w; + } + } else { + black = true; + } + x += (double) (pattern[yBlock].charAt(xBlock) - '0'); + } + y += h; + if ((y + h) > symbolHeight) { + symbolHeight = y + h; + } + /* Add bars between rows */ + if (yBlock != (rowCount - 1)) { + Rectangle2D.Double rect = new Rectangle2D.Double(11, y - 1, (symbolWidth - 24), 2); + getRectangles().add(rect); + } + } + Rectangle2D.Double top = new Rectangle2D.Double(0, 0, symbolWidth, 2); + getRectangles().add(top); + Rectangle2D.Double bottom = new Rectangle2D.Double(0, y - 1, symbolWidth, 2); + getRectangles().add(bottom); + symbolHeight += 2; + mergeVerticalBlocks(); + } + + private enum Mode { + SHIFTA, LATCHA, SHIFTB, LATCHB, SHIFTC, LATCHC, AORB, ABORC, CANDB, CANDBB + } + + private enum cfMode { + MODEA, MODEB, MODEC + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code11.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code11.java new file mode 100755 index 0000000..b4e3dd1 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code11.java @@ -0,0 +1,208 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Code 11 bar code symbology. + * Code 11 can encode any length string consisting of the digits 0-9 and the + * dash character (-). One or two modulo-11 check digits are calculated. + */ +public class Code11 extends Symbol { + + private static final String[] CODE_11_TABLE = { + "111121", "211121", "121121", "221111", "112121", "212111", + "122111", "111221", "211211", "211111", "112111" + }; + + private static final char[] CHARACTER_SET = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' + }; + + /** + * Ratio of wide bar width to narrow bar width. + */ + private double moduleWidthRatio = 2; + + /** + * The number of check digits to calculate ({@code 1} or {@code 2}). + */ + private int checkDigitCount = 2; + + /** + * Optional start delimiter to be shown in the human-readable text. + */ + private Character startDelimiter; + + /** + * Optional stop delimiter to be shown in the human-readable text. + */ + private Character stopDelimiter; + + private static int getCheckDigitC(int[] weight, int length) { + int countC = 0; + int weightC = 1; + for (int i = length - 1; i >= 0; i--) { + countC += (weightC * weight[i]); + weightC++; + if (weightC > 10) { + weightC = 1; + } + } + return countC % 11; + } + + private static int getCheckDigitK(int[] weight, int length) { + int countK = 0; + int weightK = 1; + for (int i = length - 1; i >= 0; i--) { + countK += (weightK * weight[i]); + weightK++; + if (weightK > 9) { + weightK = 1; + } + } + return countK % 11; + } + + /** + * Returns the ratio of wide bar width to narrow bar width. + * + * @return the ratio of wide bar width to narrow bar width + */ + public double getModuleWidthRatio() { + return moduleWidthRatio; + } + + /** + * Sets the ratio of wide bar width to narrow bar width. Valid values are usually + * between {@code 2} and {@code 3}. The default value is {@code 2}. + * + * @param moduleWidthRatio the ratio of wide bar width to narrow bar width + */ + public void setModuleWidthRatio(double moduleWidthRatio) { + this.moduleWidthRatio = moduleWidthRatio; + } + + /** + * Returns the number of check digits to calculate (1 or 2). + * + * @return the number of check digits to calculate + */ + public int getCheckDigitCount() { + return checkDigitCount; + } + + /** + * Sets the number of check digits to calculate ({@code 1} or {@code 2}). The default value is {@code 2}. + * + * @param checkDigitCount the number of check digits to calculate + */ + public void setCheckDigitCount(int checkDigitCount) { + if (checkDigitCount < 1 || checkDigitCount > 2) { + throw new IllegalArgumentException("Check digit count must be 1 or 2."); + } + this.checkDigitCount = checkDigitCount; + } + + /** + * Returns the optional start delimiter to be shown in the human-readable text. + * + * @return the optional start delimiter to be shown in the human-readable text + */ + public Character getStartDelimiter() { + return startDelimiter; + } + + /** + * Sets an optional start delimiter to be shown in the human-readable text (defaults to null). + * + * @param startDelimiter an optional start delimiter to be shown in the human-readable text + */ + public void setStartDelimiter(Character startDelimiter) { + this.startDelimiter = startDelimiter; + } + + /** + * Returns the optional stop delimiter to be shown in the human-readable text. + * + * @return the optional stop delimiter to be shown in the human-readable text + */ + public Character getStopDelimiter() { + return stopDelimiter; + } + + /** + * Sets an optional stop delimiter to be shown in the human-readable text (defaults to null). + * + * @param stopDelimiter an optional stop delimiter to be shown in the human-readable text + */ + public void setStopDelimiter(Character stopDelimiter) { + this.stopDelimiter = stopDelimiter; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean encode() { + + if (!(content.matches("[0-9-]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + StringBuilder horizontalSpacing = new StringBuilder("112211"); + String humanReadable = content; + int length = content.length(); + int[] weight = new int[length + 1]; + + for (int i = 0; i < length; i++) { + char c = content.charAt(i); + weight[i] = positionOf(c, CHARACTER_SET); + horizontalSpacing.append(CODE_11_TABLE[weight[i]]); + } + + int checkDigitC = getCheckDigitC(weight, length); + horizontalSpacing.append(CODE_11_TABLE[checkDigitC]); + humanReadable += CHARACTER_SET[checkDigitC]; + encodeInfo.append("Check Digit C: ").append(checkDigitC).append("\n"); + + if (checkDigitCount == 2) { + weight[length] = checkDigitC; + int checkDigitK = getCheckDigitK(weight, length + 1); + horizontalSpacing.append(CODE_11_TABLE[checkDigitK]); + humanReadable += CHARACTER_SET[checkDigitK]; + encodeInfo.append("Check Digit K: ").append(checkDigitK).append("\n"); + } + + horizontalSpacing.append("112211"); + + readable = new StringBuilder(humanReadable); + if (startDelimiter != null) { + readable = new StringBuilder(startDelimiter).append(readable); + } + if (stopDelimiter != null) { + readable.append(stopDelimiter); + } + + pattern = new String[]{horizontalSpacing.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + + plotSymbol(); + + return true; + } + + @Override + protected double getModuleWidth(int originalWidth) { + if (originalWidth == 1) { + return 1; + } else { + return moduleWidthRatio; + } + } + + @Override + protected int[] getCodewords() { + return getPatternAsCodewords(6); + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code128.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code128.java new file mode 100755 index 0000000..3272c45 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code128.java @@ -0,0 +1,843 @@ +package org.xbib.graphics.barcode; + +import java.io.UnsupportedEncodingException; + +/** + * Implements Code 128 bar code symbology according to ISO/IEC 15417:2007. + * Code 128 supports encoding of 8-bit ISO 8859-1 (Latin-1) characters. + * Setting GS1 mode allows encoding in GS1-128 (also known as UPC/EAN-128). + */ +public class Code128 extends Symbol { + private String[] code128Table = { + "212222", "222122", "222221", "121223", "121322", "131222", "122213", + "122312", "132212", "221213", "221312", "231212", "112232", "122132", + "122231", "113222", "123122", "123221", "223211", "221132", "221231", + "213212", "223112", "312131", "311222", "321122", "321221", "312212", + "322112", "322211", "212123", "212321", "232121", "111323", "131123", + "131321", "112313", "132113", "132311", "211313", "231113", "231311", + "112133", "112331", "132131", "113123", "113321", "133121", "313121", + "211331", "231131", "213113", "213311", "213131", "311123", "311321", + "331121", "312113", "312311", "332111", "314111", "221411", "431111", + "111224", "111422", "121124", "121421", "141122", "141221", "112214", + "112412", "122114", "122411", "142112", "142211", "241211", "221114", + "413111", "241112", "134111", "111242", "121142", "121241", "114212", + "124112", "124211", "411212", "421112", "421211", "212141", "214121", + "412121", "111143", "111341", "131141", "114113", "114311", "411113", + "411311", "113141", "114131", "311141", "411131", "211412", "211214", + "211232", "2331112" + }; + private Mode[] mode_type = new Mode[200]; + private int[] mode_length = new int[200]; + private int index_point = 0, read = 0; + private boolean modeCSupression; + private Composite compositeMode; + + public Code128() { + modeCSupression = false; + compositeMode = Composite.OFF; + } + + /** + * Allow the use of subset C (numeric compression) in encoding (default). + */ + public void useModeC() { + modeCSupression = false; + } + + ; + + /** + * Disallow the use of subset C (numeric compression) in encoding. + * Numeric values will be encoded using subset B. + */ + public void stopModeC() { + modeCSupression = true; + } + + protected void setCca() { + compositeMode = Composite.CCA; + } + + protected void setCcb() { + compositeMode = Composite.CCB; + } + + protected void setCcc() { + compositeMode = Composite.CCC; + } + + public void unsetCc() { + compositeMode = Composite.OFF; + } + + @Override + public boolean encode() { + int sourcelen = content.length(); + int i, j, k; + int input_point = 0; + Mode mode, last_mode; + Mode last_set, current_set; + double glyph_count; + int bar_characters = 0, total_sum = 0; + FMode f_state = FMode.LATCHN; + int[] values = new int[200]; + int c; + StringBuilder dest = new StringBuilder(); + int[] inputData; + int c_count; + int linkage_flag = 0; + + if (!content.matches("[\u0000-\u00FF]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + try { + inputBytes = content.getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Character encoding error"); + return false; + } + + inputData = new int[sourcelen]; + for (i = 0; i < sourcelen; i++) { + inputData[i] = inputBytes[i] & 0xFF; + } + + FMode[] fset = new FMode[200]; + Mode[] set = new Mode[200]; /* set[] = Calculated mode for each character */ + + if (sourcelen > 170) { + errorMsg.append("Input data too long"); + return false; + } + + /* Detect extended ASCII characters */ + for (i = 0; i < sourcelen; i++) { + if (inputData[i] >= 128) { + fset[i] = FMode.SHIFTF; + } else { + fset[i] = FMode.LATCHN; + } + } + + /* Decide when to latch to extended mode - Annex E note 3 */ + j = 0; + for (i = 0; i < sourcelen; i++) { + if (fset[i] == FMode.SHIFTF) { + j++; + } else { + j = 0; + } + + if (j >= 5) { + for (k = i; k > (i - 5); k--) { + fset[k] = FMode.LATCHF; + } + } + + if ((j >= 3) && (i == (sourcelen - 1))) { + for (k = i; k > (i - 3); k--) { + fset[k] = FMode.LATCHF; + } + } + } + + /* Decide if it is worth reverting to 646 encodation for a few characters as described in 4.3.4.2 (d) */ + for (i = 1; i < sourcelen; i++) { + if ((fset[i - 1] == FMode.LATCHF) && (fset[i] == FMode.LATCHN)) { + /* Detected a change from 8859-1 to 646 - count how long for */ + for (j = 0; (fset[i + j] == FMode.LATCHN) && ((i + j) < sourcelen); j++) ; + if ((j < 5) || ((j < 3) && ((i + j) == (sourcelen - 1)))) { + /* Uses the same figures recommended by Annex E note 3 */ + /* Change to shifting back rather than latching back */ + for (k = 0; k < j; k++) { + fset[i + k] = FMode.SHIFTN; + } + } + } + } + + /* Decide on mode using same system as PDF417 and rules of ISO 15417 Annex E */ + mode = findSubset(inputData[input_point]); + mode_type[0] = mode; + mode_length[0] = 1; + + if (inputDataType == DataType.GS1) { + mode = Mode.ABORC; + } + + if ((modeCSupression) && (mode == Mode.ABORC)) { + mode = Mode.AORB; + } + + for (i = 1; i < sourcelen; i++) { + last_mode = mode; + mode = findSubset(inputData[i]); + if ((inputDataType == DataType.GS1) && inputData[i] == '[') { + mode = Mode.ABORC; + } + if ((modeCSupression) && (mode == Mode.ABORC)) { + mode = Mode.AORB; + } + if (mode == last_mode) { + mode_length[index_point]++; + } else { + index_point++; + mode_type[index_point] = mode; + mode_length[index_point] = 1; + } + } + index_point++; + + reduceSubsetChanges(); + + + if (inputDataType == DataType.GS1) { + /* Put set data into set[] */ + read = 0; + for (i = 0; i < index_point; i++) { + for (j = 0; j < mode_length[i]; j++) { + set[read] = mode_type[i]; + read++; + } + } + + /* Resolve odd length LATCHC blocks */ + c_count = 0; + for (i = 0; i < read; i++) { + if (set[i] == Mode.LATCHC) { + if (inputData[i] == '[') { + if ((c_count & 1) != 0) { + if ((i - c_count) != 0) { + set[i - c_count] = Mode.LATCHB; + } else { + set[i - 1] = Mode.LATCHB; + } + } + c_count = 0; + } else { + c_count++; + } + } else { + if ((c_count & 1) != 0) { + if ((i - c_count) != 0) { + set[i - c_count] = Mode.LATCHB; + } else { + set[i - 1] = Mode.LATCHB; + } + } + c_count = 0; + } + } + if ((c_count & 1) != 0) { + if ((i - c_count) != 0) { + set[i - c_count] = Mode.LATCHB; + } else { + set[i - 1] = Mode.LATCHB; + } + } + for (i = 1; i < read - 1; i++) { + if ((set[i] == Mode.LATCHC) && ((set[i - 1] == Mode.LATCHB) + && (set[i + 1] == Mode.LATCHB))) { + set[i] = Mode.LATCHB; + } + } + } else { + /* Resolve odd length LATCHC blocks */ + + if ((mode_type[0] == Mode.LATCHC) && ((mode_length[0] & 1) != 0)) { + /* Rule 2 */ + mode_length[1]++; + mode_length[0]--; + if (index_point == 1) { + mode_length[1] = 1; + mode_type[1] = Mode.LATCHB; + index_point = 2; + } + } + if (index_point > 1) { + for (i = 1; i < index_point; i++) { + if ((mode_type[i] == Mode.LATCHC) && ((mode_length[i] & 1) != 0)) { + /* Rule 3b */ + mode_length[i - 1]++; + mode_length[i]--; + } + } + } + + /* Put set data into set[] */ + for (i = 0; i < index_point; i++) { + for (j = 0; j < mode_length[i]; j++) { + set[read] = mode_type[i]; + read++; + } + } + } + + /* Adjust for strings which start with shift characters - make them latch instead */ + if (set[0] == Mode.SHIFTA) { + i = 0; + do { + set[i] = Mode.LATCHA; + i++; + } while (set[i] == Mode.SHIFTA); + } + + if (set[0] == Mode.SHIFTB) { + i = 0; + do { + set[i] = Mode.LATCHB; + i++; + } while (set[i] == Mode.SHIFTB); + } + + /* Now we can calculate how long the barcode is going to be - and stop it from + being too long */ + last_set = Mode.NULL; + glyph_count = 0.0; + for (i = 0; i < sourcelen; i++) { + if ((set[i] == Mode.SHIFTA) || (set[i] == Mode.SHIFTB)) { + glyph_count += 1.0; + } + if ((fset[i] == FMode.SHIFTF) || (fset[i] == FMode.SHIFTN)) { + glyph_count += 1.0; + } + if (((set[i] == Mode.LATCHA) || (set[i] == Mode.LATCHB)) || (set[i] == Mode.LATCHC)) { + if (set[i] != last_set) { + last_set = set[i]; + glyph_count += 1.0; + } + } + if (i == 0) { + if (fset[i] == FMode.LATCHF) { + glyph_count += 2.0; + } + } else { + if ((fset[i] == FMode.LATCHF) && (fset[i - 1] != FMode.LATCHF)) { + glyph_count += 2.0; + } + if ((fset[i] != FMode.LATCHF) && (fset[i - 1] == FMode.LATCHF)) { + glyph_count += 2.0; + } + } + + if (set[i] == Mode.LATCHC) { + if ((inputDataType == DataType.GS1) && (inputData[i] == '[')) { + glyph_count += 1.0; + } else { + glyph_count += 0.5; + } + } else { + glyph_count += 1.0; + } + } + if (glyph_count > 80.0) { + errorMsg.append("Input data too long"); + return false; + } + + encodeInfo.append("Encoding: "); + + /* So now we know what start character to use - we can get on with it! */ + if (readerInit) { + /* Reader Initialisation mode */ + switch (set[0]) { + case LATCHA: /* Start A */ + dest.append(code128Table[103]); + values[0] = 103; + current_set = Mode.LATCHA; + dest.append(code128Table[96]); /* FNC3 */ + values[1] = 96; + bar_characters++; + encodeInfo.append("STARTA FNC3 "); + break; + case LATCHB: /* Start B */ + dest.append(code128Table[104]); + values[0] = 104; + current_set = Mode.LATCHB; + dest.append(code128Table[96]); /* FNC3 */ + values[1] = 96; + bar_characters++; + encodeInfo.append("STARTB FNC3 "); + break; + default: /* Start C */ + dest.append(code128Table[104]); /* Start B */ + values[0] = 105; + dest.append(code128Table[96]); /* FNC3 */ + values[1] = 96; + dest.append(code128Table[99]); /* Code C */ + values[2] = 99; + bar_characters += 2; + current_set = Mode.LATCHC; + encodeInfo.append("STARTB FNC3 CODEC "); + break; + } + } else { + /* Normal mode */ + switch (set[0]) { + case LATCHA: + /* Start A */ + dest.append(code128Table[103]); + values[0] = 103; + current_set = Mode.LATCHA; + encodeInfo.append("STARTA "); + break; + case LATCHB: + /* Start B */ + dest.append(code128Table[104]); + values[0] = 104; + current_set = Mode.LATCHB; + encodeInfo.append("STARTB "); + break; + default: + /* Start C */ + dest.append(code128Table[105]); + values[0] = 105; + current_set = Mode.LATCHC; + encodeInfo.append("STARTC "); + break; + } + } + bar_characters++; + + if (inputDataType == DataType.GS1) { + dest.append(code128Table[102]); + values[1] = 102; + bar_characters++; + encodeInfo.append("FNC1 "); + } + + if (fset[0] == FMode.LATCHF) { + switch (current_set) { + case LATCHA: + dest.append(code128Table[101]); + dest.append(code128Table[101]); + values[bar_characters] = 101; + values[bar_characters + 1] = 101; + encodeInfo.append("FNC4 FNC4 "); + break; + case LATCHB: + dest.append(code128Table[100]); + dest.append(code128Table[100]); + values[bar_characters] = 100; + values[bar_characters + 1] = 100; + encodeInfo.append("FNC4 FNC4 "); + break; + } + bar_characters += 2; + f_state = FMode.LATCHF; + } + + /* Encode the data */ + read = 0; + do { + + if ((read != 0) && (set[read] != current_set)) { /* Latch different code set */ + switch (set[read]) { + case LATCHA: + dest.append(code128Table[101]); + values[bar_characters] = 101; + bar_characters++; + current_set = Mode.LATCHA; + encodeInfo.append("CODEA "); + break; + case LATCHB: + dest.append(code128Table[100]); + values[bar_characters] = 100; + bar_characters++; + current_set = Mode.LATCHB; + encodeInfo.append("CODEB "); + break; + case LATCHC: + dest.append(code128Table[99]); + values[bar_characters] = 99; + bar_characters++; + current_set = Mode.LATCHC; + encodeInfo.append("CODEC "); + break; + } + } + + if (read != 0) { + if ((fset[read] == FMode.LATCHF) && (f_state == FMode.LATCHN)) { + /* Latch beginning of extended mode */ + switch (current_set) { + case LATCHA: + dest.append(code128Table[101]); + dest.append(code128Table[101]); + values[bar_characters] = 101; + values[bar_characters + 1] = 101; + encodeInfo.append("FNC4 FNC4 "); + break; + case LATCHB: + dest.append(code128Table[100]); + dest.append(code128Table[100]); + values[bar_characters] = 100; + values[bar_characters + 1] = 100; + encodeInfo.append("FNC4 FNC4 "); + break; + } + bar_characters += 2; + f_state = FMode.LATCHN; + } + if ((fset[read] == FMode.LATCHN) && (f_state == FMode.LATCHF)) { + /* Latch end of extended mode */ + switch (current_set) { + case LATCHA: + dest.append(code128Table[101]); + dest.append(code128Table[101]); + values[bar_characters] = 101; + values[bar_characters + 1] = 101; + encodeInfo.append("FNC4 FNC4 "); + break; + case LATCHB: + dest.append(code128Table[100]); + dest.append(code128Table[100]); + values[bar_characters] = 100; + values[bar_characters + 1] = 100; + encodeInfo.append("FNC4 FNC4 "); + break; + } + bar_characters += 2; + f_state = FMode.LATCHN; + } + } + + if ((fset[read] == FMode.SHIFTF) || (fset[read] == FMode.SHIFTN)) { + /* Shift to or from extended mode */ + switch (current_set) { + case LATCHA: + dest.append(code128Table[101]); /* FNC 4 */ + values[bar_characters] = 101; + encodeInfo.append("FNC4 "); + break; + case LATCHB: + dest.append(code128Table[100]); /* FNC 4 */ + values[bar_characters] = 100; + encodeInfo.append("FNC4 "); + break; + } + bar_characters++; + } + + if ((set[read] == Mode.SHIFTA) || (set[read] == Mode.SHIFTB)) { + /* Insert shift character */ + dest.append(code128Table[98]); + values[bar_characters] = 98; + encodeInfo.append("SHFT "); + bar_characters++; + } + + if (!((inputDataType == DataType.GS1) && (inputData[read] == '['))) { + /* Encode data characters */ + c = inputData[read]; + switch (set[read]) { + case SHIFTA: + case LATCHA: + if (c > 127) { + if (c < 160) { + dest.append(code128Table[(c - 128) + 64]); + values[bar_characters] = (c - 128) + 64; + } else { + dest.append(code128Table[(c - 128) - 32]); + values[bar_characters] = (c - 128) - 32; + } + } else { + if (c < 32) { + dest.append(code128Table[c + 64]); + values[bar_characters] = c + 64; + } else { + dest.append(code128Table[c - 32]); + values[bar_characters] = c - 32; + } + } + encodeInfo.append(Integer.toString(values[bar_characters])).append(" "); + bar_characters++; + read++; + break; + case SHIFTB: + case LATCHB: + if (c > 127) { + dest.append(code128Table[c - 32 - 128]); + values[bar_characters] = c - 32 - 128; + } else { + dest.append(code128Table[c - 32]); + values[bar_characters] = c - 32; + } + encodeInfo.append(Integer.toString(values[bar_characters])).append(" "); + bar_characters++; + read++; + break; + case LATCHC: + int weight; + int d = inputData[read + 1]; + + weight = (10 * (c - '0')) + (d - '0'); + dest.append(code128Table[weight]); + values[bar_characters] = weight; + encodeInfo.append(Integer.toString(values[bar_characters])).append(" "); + bar_characters++; + read += 2; + break; + } + } else { + // FNC1 + dest.append(code128Table[102]); + values[bar_characters] = 102; + bar_characters++; + read++; + encodeInfo.append("FNC1 "); + } + + } while (read < sourcelen); + + encodeInfo.append("\n"); + + /* "...note that the linkage flag is an extra code set character between + the last data character and the Symbol Check Character" (GS1 Specification) */ + + /* Linkage flags in GS1-128 are determined by ISO/IEC 24723 section 7.4 */ + + switch (compositeMode) { + case CCA: + case CCB: + /* CC-A or CC-B 2D component */ + switch (set[sourcelen - 1]) { + case LATCHA: + linkage_flag = 100; + break; + case LATCHB: + linkage_flag = 99; + break; + case LATCHC: + linkage_flag = 101; + break; + } + encodeInfo.append("Linkage flag: ").append(linkage_flag).append('\n'); + break; + case CCC: + /* CC-C 2D component */ + switch (set[sourcelen - 1]) { + case LATCHA: + linkage_flag = 99; + break; + case LATCHB: + linkage_flag = 101; + break; + case LATCHC: + linkage_flag = 100; + break; + } + encodeInfo.append("Linkage flag: ").append(linkage_flag).append('\n'); + break; + default: + break; + } + + if (linkage_flag != 0) { + dest.append(code128Table[linkage_flag]); + values[bar_characters] = linkage_flag; + bar_characters++; + } + + /* check digit calculation */ + for (i = 0; i < bar_characters; i++) { + if (i > 0) { + values[i] *= i; + } + total_sum += values[i]; + } + dest.append(code128Table[total_sum % 103]); + encodeInfo.append("Data Codewords: ").append(bar_characters).append('\n'); + encodeInfo.append("Check Digit: ").append(total_sum % 103).append('\n'); + + /* Stop character */ + dest.append(code128Table[106]); + + if (!(inputDataType == DataType.GS1)) { + readable = new StringBuilder(content); + } + + if (inputDataType == DataType.HIBC) { + readable.append("*").append(content).append("*"); + } + + if (compositeMode == Composite.OFF) { + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + } else { + /* Add the separator pattern for composite symbols */ + pattern = new String[2]; + pattern[0] = "0" + dest; + pattern[1] = dest.toString(); + rowCount = 2; + rowHeight = new int[2]; + rowHeight[0] = 1; + rowHeight[1] = -1; + } + plotSymbol(); + return true; + } + + private Mode findSubset(int letter) { + Mode mode; + + if (letter <= 31) { + mode = Mode.SHIFTA; + } else if ((letter >= 48) && (letter <= 57)) { + mode = Mode.ABORC; + } else if (letter <= 95) { + mode = Mode.AORB; + } else if (letter <= 127) { + mode = Mode.SHIFTB; + } else if (letter <= 159) { + mode = Mode.SHIFTA; + } else if (letter <= 223) { + mode = Mode.AORB; + } else { + mode = Mode.SHIFTB; + } + + return mode; + } + + private void reduceSubsetChanges() { /* Implements rules from ISO 15417 Annex E */ + int i, length; + Mode current, last, next; + + for (i = 0; i < index_point; i++) { + current = mode_type[i]; + length = mode_length[i]; + if (i != 0) { + last = mode_type[i - 1]; + } else { + last = Mode.NULL; + } + if (i != index_point - 1) { + next = mode_type[i + 1]; + } else { + next = Mode.NULL; + } + + if (i == 0) { /* first block */ + if ((index_point == 1) && ((length == 2) && (current == Mode.ABORC))) { /* Rule 1a */ + mode_type[i] = Mode.LATCHC; + } + if (current == Mode.ABORC) { + if (length >= 4) { /* Rule 1b */ + mode_type[i] = Mode.LATCHC; + } else { + mode_type[i] = Mode.AORB; + current = Mode.AORB; + } + } + if (current == Mode.SHIFTA) { /* Rule 1c */ + mode_type[i] = Mode.LATCHA; + } + if ((current == Mode.AORB) && (next == Mode.SHIFTA)) { /* Rule 1c */ + mode_type[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if (current == Mode.AORB) { /* Rule 1d */ + mode_type[i] = Mode.LATCHB; + } + } else { + if ((current == Mode.ABORC) && (length >= 4)) { /* Rule 3 */ + mode_type[i] = Mode.LATCHC; + current = Mode.LATCHC; + } + if (current == Mode.ABORC) { + mode_type[i] = Mode.AORB; + current = Mode.AORB; + } + if ((current == Mode.AORB) && (last == Mode.LATCHA)) { + mode_type[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.AORB) && (last == Mode.LATCHB)) { + mode_type[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.AORB) && (next == Mode.SHIFTA)) { + mode_type[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.AORB) && (next == Mode.SHIFTB)) { + mode_type[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if (current == Mode.AORB) { + mode_type[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.SHIFTA) && (length > 1)) { /* Rule 4 */ + mode_type[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.SHIFTB) && (length > 1)) { /* Rule 5 */ + mode_type[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.SHIFTA) && (last == Mode.LATCHA)) { + mode_type[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.SHIFTB) && (last == Mode.LATCHB)) { + mode_type[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.SHIFTA) && (last == Mode.LATCHC)) { + mode_type[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.SHIFTB) && (last == Mode.LATCHC)) { + mode_type[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + } /* Rule 2 is implemented elsewhere, Rule 6 is implied */ + } + + combineSubsetBlocks(); + + } + + private void combineSubsetBlocks() { + int i, j; + + /* bring together same type blocks */ + if (index_point > 1) { + i = 1; + while (i < index_point) { + if (mode_type[i - 1] == mode_type[i]) { + /* bring together */ + mode_length[i - 1] = mode_length[i - 1] + mode_length[i]; + j = i + 1; + + /* decreace the list */ + while (j < index_point) { + mode_length[j - 1] = mode_length[j]; + mode_type[j - 1] = mode_type[j]; + j++; + } + index_point--; + i--; + } + i++; + } + } + } + + private enum Mode { + NULL, SHIFTA, LATCHA, SHIFTB, LATCHB, SHIFTC, LATCHC, AORB, ABORC + } + + private enum FMode { + SHIFTN, LATCHN, SHIFTF, LATCHF + } + + private enum Composite {OFF, CCA, CCB, CCC} +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code16k.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code16k.java new file mode 100755 index 0000000..eb2ae61 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code16k.java @@ -0,0 +1,769 @@ +package org.xbib.graphics.barcode; + +import java.awt.geom.Rectangle2D; +import java.io.UnsupportedEncodingException; + +/** + * Implements Code 16K symbology + * According to BS EN 12323:2005 + * Encodes using a stacked symbology based on Code 128. Supports encoding + * of any 8-bit ISO 8859-1 (Latin-1) data with a maximum data capacity of 77 + * alpha-numeric characters or 154 numerical digits. + */ +public class Code16k extends Symbol { + + /* EN 12323 Table 1 - "Code 16K" character encodations */ + private static final String[] C_16_K_TABLE = { + "212222", "222122", "222221", "121223", "121322", "131222", "122213", + "122312", "132212", "221213", "221312", "231212", "112232", "122132", + "122231", "113222", "123122", "123221", "223211", "221132", "221231", + "213212", "223112", "312131", "311222", "321122", "321221", "312212", + "322112", "322211", "212123", "212321", "232121", "111323", "131123", + "131321", "112313", "132113", "132311", "211313", "231113", "231311", + "112133", "112331", "132131", "113123", "113321", "133121", "313121", + "211331", "231131", "213113", "213311", "213131", "311123", "311321", + "331121", "312113", "312311", "332111", "314111", "221411", "431111", + "111224", "111422", "121124", "121421", "141122", "141221", "112214", + "112412", "122114", "122411", "142112", "142211", "241211", "221114", + "413111", "241112", "134111", "111242", "121142", "121241", "114212", + "124112", "124211", "411212", "421112", "421211", "212141", "214121", + "412121", "111143", "111341", "131141", "114113", "114311", "411113", + "411311", "113141", "114131", "311141", "411131", "211412", "211214", + "211232", "211133" + }; + /* EN 12323 Table 3 and Table 4 - Start patterns and stop patterns */ + private static final String[] C_16_K_START_STOP = { + "3211", "2221", "2122", "1411", "1132", "1231", "1114", "3112" + }; + /* EN 12323 Table 5 - Start and stop values defining row numbers */ + private static final int[] C_16_K_START_VALUES = { + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7 + }; + private static final int[] C_16_K_STOP_VALUES = { + 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 0, 1, 2, 3 + }; + private final Mode[] block_mode = new Mode[170]; /* RENAME block_mode */ + private final int[] block_length = new int[170]; /* RENAME block_length */ + + @Override + public boolean encode() { + StringBuilder width_pattern; + int current_row, rows_needed, first_check, second_check; + int indexchaine, pads_needed; + char[] set, fset; + Mode mode; + char last_set, current_set; + int i, j, k, m, read; + int[] values; + int bar_characters; + double glyph_count; + int first_sum, second_sum; + int input_length; + int c_count; + boolean f_state; + int[] inputData; + + if (!content.matches("[\u0000-\u00FF]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + try { + inputBytes = content.getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Character encoding error"); + return false; + } + + input_length = content.length(); + inputData = new int[input_length]; + for (i = 0; i < input_length; i++) { + inputData[i] = inputBytes[i] & 0xFF; + } + + bar_characters = 0; + set = new char[160]; + fset = new char[160]; + values = new int[160]; + + if (input_length > 157) { + errorMsg.append("Input too long"); + return false; + } + + /* Detect extended ASCII characters */ + for (i = 0; i < input_length; i++) { + if (inputData[i] >= 128) { + fset[i] = 'f'; + } else { + fset[i] = ' '; + } + } + + /* Decide when to latch to extended mode */ + for (i = 0; i < input_length; i++) { + j = 0; + if (fset[i] == 'f') { + do { + j++; + } while (fset[i + j] == 'f'); + if ((j >= 5) || ((j >= 3) && ((i + j) == (input_length - 1)))) { + for (k = 0; k <= j; k++) { + fset[i + k] = 'F'; + } + } + } + } + + /* Decide if it is worth reverting to 646 encodation for a few characters */ + if (input_length > 1) { + for (i = 1; i < input_length; i++) { + if ((fset[i - 1] == 'F') && (fset[i] == ' ')) { + /* Detected a change from 8859-1 to 646 - count how long for */ + for (j = 0; (fset[i + j] == ' ') && ((i + j) < input_length); j++) + ; + if (j < 5) { + /* Change to shifting back rather than latching back */ + for (k = 0; k < j; k++) { + fset[i + k] = 'n'; + } + } + } + } + } + + /* Detect mode A, B and C characters */ + int block_count = 0; + indexchaine = 0; + + mode = findSubset(inputData[indexchaine]); + if ((inputDataType == DataType.GS1) && (inputData[indexchaine] == '[')) { + mode = Mode.ABORC; + } /* FNC1 */ + + for (i = 0; i < 160; i++) { + block_length[i] = 0; + } + + do { + block_mode[block_count] = mode; + while ((block_mode[block_count] == mode) && (indexchaine < input_length)) { + block_length[block_count]++; + indexchaine++; + if (indexchaine < input_length) { + mode = findSubset(inputData[indexchaine]); + if ((inputDataType == DataType.GS1) && (inputData[indexchaine] == '[')) { + mode = Mode.ABORC; + } /* FNC1 */ + } + } + block_count++; + } while (indexchaine < input_length); + + reduceSubsetChanges(block_count); + + + /* Put set data into set[] */ + read = 0; + for (i = 0; i < block_count; i++) { + for (j = 0; j < block_length[i]; j++) { + switch (block_mode[i]) { + case SHIFTA: + set[read] = 'a'; + break; + case LATCHA: + set[read] = 'A'; + break; + case SHIFTB: + set[read] = 'b'; + break; + case LATCHB: + set[read] = 'B'; + break; + case LATCHC: + set[read] = 'C'; + break; + } + read++; + } + } + + /* Adjust for strings which start with shift characters - make them latch instead */ + if (set[0] == 'a') { + i = 0; + do { + set[i] = 'A'; + i++; + } while (set[i] == 'a'); + } + + if (set[0] == 'b') { + i = 0; + do { + set[i] = 'B'; + i++; + } while (set[i] == 'b'); + } + + /* Watch out for odd-length Mode C blocks */ + c_count = 0; + for (i = 0; i < read; i++) { + if (set[i] == 'C') { + if (inputData[i] == '[') { + if ((c_count & 1) != 0) { + if ((i - c_count) != 0) { + set[i - c_count] = 'B'; + } else { + set[i - 1] = 'B'; + } + } + c_count = 0; + } else { + c_count++; + } + } else { + if ((c_count & 1) != 0) { + if ((i - c_count) != 0) { + set[i - c_count] = 'B'; + } else { + set[i - 1] = 'B'; + } + } + c_count = 0; + } + } + if ((c_count & 1) != 0) { + if ((i - c_count) != 0) { + set[i - c_count] = 'B'; + } else { + set[i - 1] = 'B'; + } + } + for (i = 1; i < read - 1; i++) { + if ((set[i] == 'C') && ((set[i - 1] == 'B') && (set[i + 1] == 'B'))) { + set[i] = 'B'; + } + } + + /* Make sure the data will fit in the symbol */ + last_set = ' '; + glyph_count = 0.0; + for (i = 0; i < input_length; i++) { + if ((set[i] == 'a') || (set[i] == 'b')) { + glyph_count = glyph_count + 1.0; + } + if ((fset[i] == 'f') || (fset[i] == 'n')) { + glyph_count = glyph_count + 1.0; + } + if (((set[i] == 'A') || (set[i] == 'B')) || (set[i] == 'C')) { + if (set[i] != last_set) { + last_set = set[i]; + glyph_count = glyph_count + 1.0; + } + } + if (i == 0) { + if ((set[i] == 'B') && (set[1] == 'C')) { + glyph_count = glyph_count - 1.0; + } + if ((set[i] == 'B') && (set[1] == 'B') && set[2] == 'C') { + glyph_count = glyph_count - 1.0; + } + if (fset[i] == 'F') { + glyph_count = glyph_count + 2.0; + } + } else { + if ((fset[i] == 'F') && (fset[i - 1] != 'F')) { + glyph_count = glyph_count + 2.0; + } + if ((fset[i] != 'F') && (fset[i - 1] == 'F')) { + glyph_count = glyph_count + 2.0; + } + } + + if ((set[i] == 'C') && (!((inputDataType == DataType.GS1) && (content.charAt(i) == '[')))) { + glyph_count = glyph_count + 0.5; + } else { + glyph_count = glyph_count + 1.0; + } + } + + if ((inputDataType == DataType.GS1) && (set[0] != 'A')) { + /* FNC1 can be integrated with mode character */ + glyph_count--; + } + + if (glyph_count > 77.0) { + errorMsg.append("Input too long"); + return false; + } + + /* Calculate how tall the symbol will be */ + glyph_count = glyph_count + 2.0; + i = (int) glyph_count; + rows_needed = (i / 5); + if (i % 5 > 0) { + rows_needed++; + } + + if (rows_needed == 1) { + rows_needed = 2; + } + + /* start with the mode character - Table 2 */ + m = 0; + switch (set[0]) { + case 'A': + m = 0; + break; + case 'B': + m = 1; + break; + case 'C': + m = 2; + break; + } + + if (readerInit) { + if (m == 2) { + m = 5; + } + if (inputDataType == DataType.GS1) { + errorMsg.append("Cannot use both GS1 mode and Reader Initialisation"); + return false; + } else { + if ((set[0] == 'B') && (set[1] == 'C')) { + m = 6; + } + } + values[bar_characters] = (7 * (rows_needed - 2)) + m; /* see 4.3.4.2 */ + values[bar_characters + 1] = 96; /* FNC3 */ + bar_characters += 2; + } else { + if (inputDataType == DataType.GS1) { + /* Integrate FNC1 */ + switch (set[0]) { + case 'B': + m = 3; + break; + case 'C': + m = 4; + break; + } + } else { + if ((set[0] == 'B') && (set[1] == 'C')) { + m = 5; + } + if (((set[0] == 'B') && (set[1] == 'B')) && (set[2] == 'C')) { + m = 6; + } + } + } + values[bar_characters] = (7 * (rows_needed - 2)) + m; /* see 4.3.4.2 */ + bar_characters++; + //} + current_set = set[0]; + f_state = false; + /* f_state remembers if we are in Extended ASCII mode (value 1) or + in ISO/IEC 646 mode (value 0) */ + if (fset[0] == 'F') { + switch (current_set) { + case 'A': + values[bar_characters] = 101; + values[bar_characters + 1] = 101; + break; + case 'B': + values[bar_characters] = 100; + values[bar_characters + 1] = 100; + break; + } + bar_characters += 2; + f_state = true; + } + + read = 0; + + /* Encode the data */ + do { + + if ((read != 0) && (set[read] != set[read - 1])) { /* Latch different code set */ + switch (set[read]) { + case 'A': + values[bar_characters] = 101; + bar_characters++; + current_set = 'A'; + break; + case 'B': + values[bar_characters] = 100; + bar_characters++; + current_set = 'B'; + break; + case 'C': + if (!((read == 1) && (set[0] == 'B'))) { /* Not Mode C/Shift B */ + if (!((read == 2) && ((set[0] == 'B') && (set[1] == 'B')))) { + /* Not Mode C/Double Shift B */ + values[bar_characters] = 99; + bar_characters++; + } + } + current_set = 'C'; + break; + } + } + if (read != 0) { + if ((fset[read] == 'F') && !f_state) { + /* Latch beginning of extended mode */ + switch (current_set) { + case 'A': + values[bar_characters] = 101; + values[bar_characters + 1] = 101; + break; + case 'B': + values[bar_characters] = 100; + values[bar_characters + 1] = 100; + break; + } + bar_characters += 2; + f_state = true; + } + if ((fset[read] == ' ') && f_state) { + /* Latch end of extended mode */ + switch (current_set) { + case 'A': + values[bar_characters] = 101; + values[bar_characters + 1] = 101; + break; + case 'B': + values[bar_characters] = 100; + values[bar_characters + 1] = 100; + break; + } + bar_characters += 2; + f_state = false; + } + } + + if ((fset[i] == 'f') || (fset[i] == 'n')) { + /* Shift extended mode */ + switch (current_set) { + case 'A': + values[bar_characters] = 101; /* FNC 4 */ + break; + case 'B': + values[bar_characters] = 100; /* FNC 4 */ + break; + } + bar_characters++; + } + + if ((set[i] == 'a') || (set[i] == 'b')) { + /* Insert shift character */ + values[bar_characters] = 98; + bar_characters++; + } + + if (!((inputDataType == DataType.GS1) && (inputData[read] == '['))) { + switch (set[read]) { /* Encode data characters */ + case 'A': + case 'a': + getValueSubsetA(inputData[read], values, bar_characters); + bar_characters++; + read++; + break; + case 'B': + case 'b': + getValueSubsetB(inputData[read], values, bar_characters); + bar_characters++; + read++; + break; + case 'C': + getValueSubsetC(inputData[read], inputData[read + 1], values, bar_characters); + bar_characters++; + read += 2; + break; + } + } else { + values[bar_characters] = 102; + bar_characters++; + read++; + } + + } while (read < input_length); + + pads_needed = 5 - ((bar_characters + 2) % 5); + if (pads_needed == 5) { + pads_needed = 0; + } + if ((bar_characters + pads_needed) < 8) { + pads_needed += 8 - (bar_characters + pads_needed); + } + for (i = 0; i < pads_needed; i++) { + values[bar_characters] = 106; + bar_characters++; + } + + /* Calculate check digits */ + first_sum = 0; + second_sum = 0; + for (i = 0; i < bar_characters; i++) { + first_sum += (i + 2) * values[i]; + second_sum += (i + 1) * values[i]; + } + first_check = first_sum % 107; + second_sum += first_check * (bar_characters + 1); + second_check = second_sum % 107; + values[bar_characters] = first_check; + values[bar_characters + 1] = second_check; + readable = new StringBuilder(); + pattern = new String[rows_needed]; + rowCount = rows_needed; + rowHeight = new int[rows_needed]; + encodeInfo.append("Symbol Rows: ").append(rows_needed).append("\n"); + encodeInfo.append("First Check Digit: ").append(first_check).append("\n"); + encodeInfo.append("Second Check Digit: ").append(second_check).append("\n"); + encodeInfo.append("Codewords: "); + for (current_row = 0; current_row < rows_needed; current_row++) { + width_pattern = new StringBuilder(); + width_pattern.append(C_16_K_START_STOP[C_16_K_START_VALUES[current_row]]); + width_pattern.append("1"); + for (i = 0; i < 5; i++) { + width_pattern.append(C_16_K_TABLE[values[(current_row * 5) + i]]); + encodeInfo.append(values[(current_row * 5) + i]).append(" "); + } + width_pattern.append(C_16_K_START_STOP[C_16_K_STOP_VALUES[current_row]]); + + pattern[current_row] = width_pattern.toString(); + rowHeight[current_row] = 10; + } + encodeInfo.append("\n"); + plotSymbol(); + return true; + } + + private void getValueSubsetA(int source, int[] values, int bar_chars) { + if (source > 127) { + if (source < 160) { + values[bar_chars] = source + 64 - 128; + } else { + values[bar_chars] = source - 32 - 128; + } + } else { + if (source < 32) { + values[bar_chars] = source + 64; + } else { + values[bar_chars] = source - 32; + } + } + } + + private void getValueSubsetB(int source, int[] values, int bar_chars) { + if (source > 127) { + values[bar_chars] = source - 32 - 128; + } else { + values[bar_chars] = source - 32; + } + } + + private void getValueSubsetC(int source_a, int source_b, int[] values, int bar_chars) { + int weight; + + weight = (10 * Character.getNumericValue(source_a)) + Character.getNumericValue(source_b); + values[bar_chars] = weight; + } + + private Mode findSubset(int letter) { + Mode mode; + + if (letter <= 31) { + mode = Mode.SHIFTA; + } else if ((letter >= 48) && (letter <= 57)) { + mode = Mode.ABORC; + } else if (letter <= 95) { + mode = Mode.AORB; + } else if (letter <= 127) { + mode = Mode.SHIFTB; + } else if (letter <= 159) { + mode = Mode.SHIFTA; + } else if (letter <= 223) { + mode = Mode.AORB; + } else { + mode = Mode.SHIFTB; + } + + return mode; + } + + private void reduceSubsetChanges(int block_count) { /* Implements rules from ISO 15417 Annex E */ + int i, length; + Mode current, last, next; + + for (i = 0; i < block_count; i++) { + current = block_mode[i]; + length = block_length[i]; + if (i != 0) { + last = block_mode[i - 1]; + } else { + last = Mode.NULL; + } + if (i != block_count - 1) { + next = block_mode[i + 1]; + } else { + next = Mode.NULL; + } + + if (i == 0) { /* first block */ + if ((block_count == 1) && ((length == 2) && (current == Mode.ABORC))) { /* Rule 1a */ + block_mode[i] = Mode.LATCHC; + } + if (current == Mode.ABORC) { + if (length >= 4) { /* Rule 1b */ + block_mode[i] = Mode.LATCHC; + } else { + block_mode[i] = Mode.AORB; + current = Mode.AORB; + } + } + if (current == Mode.SHIFTA) { /* Rule 1c */ + block_mode[i] = Mode.LATCHA; + } + if ((current == Mode.AORB) && (next == Mode.SHIFTA)) { /* Rule 1c */ + block_mode[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if (current == Mode.AORB) { /* Rule 1d */ + block_mode[i] = Mode.LATCHB; + } + } else { + if ((current == Mode.ABORC) && (length >= 4)) { /* Rule 3 */ + block_mode[i] = Mode.LATCHC; + current = Mode.LATCHC; + } + if (current == Mode.ABORC) { + block_mode[i] = Mode.AORB; + current = Mode.AORB; + } + if ((current == Mode.AORB) && (last == Mode.LATCHA)) { + block_mode[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.AORB) && (last == Mode.LATCHB)) { + block_mode[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.AORB) && (next == Mode.SHIFTA)) { + block_mode[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.AORB) && (next == Mode.SHIFTB)) { + block_mode[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if (current == Mode.AORB) { + block_mode[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.SHIFTA) && (length > 1)) { /* Rule 4 */ + block_mode[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.SHIFTB) && (length > 1)) { /* Rule 5 */ + block_mode[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.SHIFTA) && (last == Mode.LATCHA)) { + block_mode[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.SHIFTB) && (last == Mode.LATCHB)) { + block_mode[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + if ((current == Mode.SHIFTA) && (last == Mode.LATCHC)) { + block_mode[i] = Mode.LATCHA; + current = Mode.LATCHA; + } + if ((current == Mode.SHIFTB) && (last == Mode.LATCHC)) { + block_mode[i] = Mode.LATCHB; + current = Mode.LATCHB; + } + } + } + combineSubsetBlocks(block_count); + } + + private void combineSubsetBlocks(int block_count) { + int i, j; + if (block_count > 1) { + i = 1; + while (i < block_count) { + if (block_mode[i - 1] == block_mode[i]) { + block_length[i - 1] = block_length[i - 1] + block_length[i]; + j = i + 1; + while (j < block_count) { + block_length[j - 1] = block_length[j]; + block_mode[j - 1] = block_mode[j]; + j++; + } + block_count = block_count - 1; + i--; + } + i++; + } + } + } + + @Override + protected void plotSymbol() { + int xBlock, yBlock; + int x, y, w, h; + boolean black; + getRectangles().clear(); + y = 1; + h = 1; + for (yBlock = 0; yBlock < rowCount; yBlock++) { + black = true; + x = 15; + for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) { + if (black) { + black = false; + w = pattern[yBlock].charAt(xBlock) - '0'; + if (rowHeight[yBlock] == -1) { + h = defaultHeight; + } else { + h = rowHeight[yBlock]; + } + if (w != 0 && h != 0) { + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + } + if ((x + w) > symbolWidth) { + symbolWidth = x + w; + } + } else { + black = true; + } + x += (double) (pattern[yBlock].charAt(xBlock) - '0'); + } + y += h; + if ((y + h) > symbolHeight) { + symbolHeight = y + h; + } + /* Add bars between rows */ + if (yBlock != (rowCount - 1)) { + Rectangle2D.Double rect = new Rectangle2D.Double(15, y - 1, (symbolWidth - 15), 2); + getRectangles().add(rect); + } + } + Rectangle2D.Double top = new Rectangle2D.Double(0, 0, (symbolWidth + 15), 2); + getRectangles().add(top); + Rectangle2D.Double bottom = new Rectangle2D.Double(0, y - 1, (symbolWidth + 15), 2); + getRectangles().add(bottom); + symbolWidth += 30; + symbolHeight += 2; + mergeVerticalBlocks(); + } + + private enum Mode { + NULL, SHIFTA, LATCHA, SHIFTB, LATCHB, SHIFTC, LATCHC, AORB, ABORC, CANDB, CANDBB + } + +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code2Of5.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code2Of5.java new file mode 100755 index 0000000..6dba07d --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code2Of5.java @@ -0,0 +1,493 @@ +package org.xbib.graphics.barcode; + +import static org.xbib.graphics.barcode.HumanReadableLocation.NONE; +import static org.xbib.graphics.barcode.HumanReadableLocation.TOP; +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Rectangle2D; + +/** + * Implements the Code 2 of 5 family of barcode standards. + */ +public class Code2Of5 extends Symbol { + + private static final String[] C25_MATRIX_TABLE = { + "113311", "311131", "131131", "331111", "113131", "313111", "133111", "111331", "311311", "131311" + }; + private static final String[] C25_INDUSTRIAL_TABLE = { + "1111313111", "3111111131", "1131111131", "3131111111", "1111311131", "3111311111", "1131311111", "1111113131", "3111113111", "1131113111" + }; + private static final String[] C25_INTERLEAVED_TABLE = { + "11331", "31113", "13113", "33111", "11313", "31311", "13311", "11133", "31131", "13131" + }; + /** + * The 2-of-5 mode. + */ + private ToFMode mode = ToFMode.MATRIX; + /** + * Ratio of wide bar width to narrow bar width. + */ + private double moduleWidthRatio = 3; + + /** + * Returns the ratio of wide bar width to narrow bar width. + * + * @return the ratio of wide bar width to narrow bar width + */ + public double getModuleWidthRatio() { + return moduleWidthRatio; + } + + /** + * Sets the ratio of wide bar width to narrow bar width. Valid values are usually + * between {@code 2} and {@code 3}. The default value is {@code 3}. + * + * @param moduleWidthRatio the ratio of wide bar width to narrow bar width + */ + public void setModuleWidthRatio(double moduleWidthRatio) { + this.moduleWidthRatio = moduleWidthRatio; + } + + /** + * Select Standard Code 2 of 5 mode, also known as Code 2 of 5 Matrix. (default) + * Encodes any length numeric input (digits 0-9). + */ + public void setMatrixMode() { + mode = ToFMode.MATRIX; + } + + /** + * Select Industrial Code 2 of 5 which can encode any length numeric input + * (digits 0-9) and does not include a check digit. + */ + public void setIndustrialMode() { + mode = ToFMode.INDUSTRIAL; + } + + /** + * Select International Air Transport Agency variation of Code 2 of 5. + * Encodes any length numeric input (digits 0-9) and does not include + * a check digit. + */ + public void setIATAMode() { + mode = ToFMode.IATA; + } + + /** + * Select Code 2 of 5 Data Logic. Encodes any length numeric input + * (digits 0-9) and does not include a check digit. + */ + public void setDataLogicMode() { + mode = ToFMode.DATA_LOGIC; + } + + /** + * Select Interleaved Code 2 of 5. encodes pairs of numbers, and so can + * only encode an even number of digits (0-9). If an odd number of digits + * is entered a leading zero is added. No check digit is calculated. + */ + public void setInterleavedMode() { + mode = ToFMode.INTERLEAVED; + } + + /** + * Select ITF-14, also known as UPC Shipping Container Symbol or Case Code. + * Requires a 13 digit numeric input (digits 0-9). One modulo-10 check + * digit is calculated. + */ + public void setITF14Mode() { + mode = ToFMode.ITF14; + } + + /** + * Select Deutsche Post Leitcode. Requires a 13-digit numerical input. + * Check digit is calculated. + */ + public void setDPLeitMode() { + mode = ToFMode.DPLEIT; + } + + /** + * Select Deutsche Post Identcode. Requires an 11-digit numerical input. + * Check digit is calculated. + */ + public void setDPIdentMode() { + mode = ToFMode.DPIDENT; + } + + @Override + public boolean encode() { + boolean retval = false; + + switch (mode) { + case MATRIX: + retval = dataMatrixTof(); + break; + case INDUSTRIAL: + retval = industrialTof(); + break; + case IATA: + retval = iataTof(); + break; + case INTERLEAVED: + retval = interleavedTof(); + break; + case DATA_LOGIC: + retval = dataLogic(); + break; + case ITF14: + retval = itf14(); + break; + case DPLEIT: + retval = deutschePostLeitcode(); + break; + case DPIDENT: + retval = deutschePostIdentcode(); + break; + } + + if (retval) { + plotSymbol(); + } + + return retval; + } + + private boolean dataMatrixTof() { + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + StringBuilder dest = new StringBuilder("311111"); + + for (int i = 0; i < content.length(); i++) { + dest.append(C25_MATRIX_TABLE[Character.getNumericValue(content.charAt(i))]); + } + dest.append("31111"); + + readable = new StringBuilder(content); + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + private boolean industrialTof() { + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + StringBuilder dest = new StringBuilder("313111"); + readable = new StringBuilder(content); + for (int i = 0; i < readable.length(); i++) { + dest.append(C25_INDUSTRIAL_TABLE[Character.getNumericValue(readable.charAt(i))]); + } + dest.append("31113"); + + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + private boolean iataTof() { + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + StringBuilder dest = new StringBuilder("1111"); + readable = new StringBuilder(content); + for (int i = 0; i < readable.length(); i++) { + dest.append(C25_INDUSTRIAL_TABLE[Character.getNumericValue(readable.charAt(i))]); + } + dest.append("311"); + + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + private boolean dataLogic() { + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + StringBuilder dest = new StringBuilder("1111"); + readable = new StringBuilder(content); + + for (int i = 0; i < readable.length(); i++) { + dest.append(C25_MATRIX_TABLE[Character.getNumericValue(readable.charAt(i))]); + } + + dest.append("311"); + + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + private boolean interleavedTof() { + int i; + StringBuilder dest; + + if ((content.length() & 1) == 0) { + readable = new StringBuilder(content); + } else { + readable = new StringBuilder("0").append(content); + } + if (!(readable.toString().matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + dest = new StringBuilder("1111"); + + for (i = 0; i < readable.length(); i += 2) { + dest.append(interlace(i, i + 1)); + } + + dest.append("311"); + + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + private String interlace(int x, int y) { + char a = readable.charAt(x); + char b = readable.charAt(y); + + String one = C25_INTERLEAVED_TABLE[Character.getNumericValue(a)]; + String two = C25_INTERLEAVED_TABLE[Character.getNumericValue(b)]; + String f = ""; + + for (int i = 0; i < 5; i++) { + f += one.charAt(i); + f += two.charAt(i); + } + + return f; + } + + private boolean itf14() { + int i, count = 0; + int input_length = content.length(); + StringBuilder dest; + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (input_length > 13) { + errorMsg.append("Input data too long"); + return false; + } + + readable = new StringBuilder(); + for (i = input_length; i < 13; i++) { + readable.append("0"); + } + + readable.append(content); + for (i = 12; i >= 0; i--) { + count += readable.charAt(i) - '0'; + + if ((i & 1) == 0) { + count += 2 * (readable.charAt(i) - '0'); + } + } + + readable.append((char) (((10 - (count % 10)) % 10) + '0')); + encodeInfo.append("Check Digit: ").append((char) (((10 - (count % 10)) % 10) + '0')); + encodeInfo.append('\n'); + + dest = new StringBuilder("1111"); + + for (i = 0; i < readable.length(); i += 2) { + dest.append(interlace(i, i + 1)); + } + + dest.append("311"); + + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + private boolean deutschePostLeitcode() { + int i, count = 0; + int input_length = content.length(); + StringBuilder dest; + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (input_length > 13) { + errorMsg.append("Input data too long"); + return false; + } + + readable = new StringBuilder(); + for (i = input_length; i < 13; i++) { + readable.append("0"); + } + + readable.append(content); + + for (i = 12; i >= 0; i--) { + count += 4 * (readable.charAt(i) - '0'); + + if ((i & 1) != 0) { + count += 5 * (readable.charAt(i) - '0'); + } + } + + readable.append((char) (((10 - (count % 10)) % 10) + '0')); + encodeInfo.append("Check digit: ").append((char) (((10 - (count % 10)) % 10) + '0')); + encodeInfo.append('\n'); + + dest = new StringBuilder("1111"); + + for (i = 0; i < readable.length(); i += 2) { + dest.append(interlace(i, i + 1)); + } + + dest.append("311"); + + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + private boolean deutschePostIdentcode() { + int i, count = 0; + int input_length = content.length(); + StringBuilder dest; + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (input_length > 11) { + errorMsg.append("Input data too long"); + return false; + } + + readable = new StringBuilder(); + for (i = input_length; i < 11; i++) { + readable.append("0"); + } + + readable.append(content); + for (i = 10; i >= 0; i--) { + count += 4 * (readable.charAt(i) - '0'); + + if ((i & 1) != 0) { + count += 5 * (readable.charAt(i) - '0'); + } + } + + readable.append((char) (((10 - (count % 10)) % 10) + '0')); + encodeInfo.append("Check Digit: ").append((char) (((10 - (count % 10)) % 10) + '0')); + encodeInfo.append('\n'); + + dest = new StringBuilder("1111"); + + for (i = 0; i < readable.length(); i += 2) { + dest.append(interlace(i, i + 1)); + } + + dest.append("311"); + + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + return true; + } + + @Override + protected void plotSymbol() { + int xBlock; + getRectangles().clear(); + getTexts().clear(); + int baseY; + if (getHumanReadableLocation() == TOP) { + baseY = getTheoreticalHumanReadableHeight(); + } else { + baseY = 0; + } + double x = 0; + int y = baseY; + int h = 0; + boolean black = true; + int offset = 0; + if (mode == ToFMode.ITF14) { + offset = 20; + } + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + char c = pattern[0].charAt(xBlock); + double w = getModuleWidth(c - '0') * moduleWidth; + if (black) { + if (rowHeight[0] == -1) { + h = defaultHeight; + } else { + h = rowHeight[0]; + } + if (w != 0 && h != 0) { + Rectangle2D.Double rect = new Rectangle2D.Double(x + offset, y, w, h); + getRectangles().add(rect); + } + symbolWidth = (int) Math.ceil(x + w + (2 * offset)); + } + black = !black; + x += w; + } + symbolHeight = h; + if (mode == ToFMode.ITF14) { + Rectangle2D.Double topBar = new Rectangle2D.Double(0, baseY, symbolWidth, 4); + Rectangle2D.Double bottomBar = new Rectangle2D.Double(0, baseY + symbolHeight - 4, symbolWidth, 4); + Rectangle2D.Double leftBar = new Rectangle2D.Double(0, baseY, 4, symbolHeight); + Rectangle2D.Double rightBar = new Rectangle2D.Double(symbolWidth - 4, baseY, 4, symbolHeight); + getRectangles().add(topBar); + getRectangles().add(bottomBar); + getRectangles().add(leftBar); + getRectangles().add(rightBar); + } + if (getHumanReadableLocation() != NONE && readable.length() > 0) { + double baseline; + if (getHumanReadableLocation() == TOP) { + baseline = fontSize; + } else { + baseline = getHeight() + fontSize; + } + double centerX = getWidth() / 2.0; + getTexts().add(new TextBox(centerX, baseline, readable.toString())); + } + } + + @Override + protected double getModuleWidth(int originalWidth) { + if (originalWidth == 1) { + return 1; + } else { + return moduleWidthRatio; + } + } + + private enum ToFMode { + MATRIX, INDUSTRIAL, IATA, DATA_LOGIC, INTERLEAVED, ITF14, DPLEIT, DPIDENT + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code32.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code32.java new file mode 100755 index 0000000..6c35717 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code32.java @@ -0,0 +1,103 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Code 32, also known as Italian Pharmacode, A variation of Code + * 39 used by the Italian Ministry of Health ("Ministero della Sanità") + * Requires a numeric input up to 8 digits in length. Check digit is + * calculated. + */ +public class Code32 extends Symbol { + private char[] tabella = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', + 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z' + }; + + @Override + public boolean encode() { + int i, checksum, checkpart, checkdigit; + int pharmacode, remainder, devisor; + StringBuilder localstr; + StringBuilder risultante; + int[] codeword = new int[6]; + Code3Of9 c39 = new Code3Of9(); + + if (content.length() > 8) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + /* Add leading zeros as required */ + localstr = new StringBuilder(); + for (i = content.length(); i < 8; i++) { + localstr.append("0"); + } + localstr.append(content); + + /* Calculate the check digit */ + checksum = 0; + checkpart = 0; + for (i = 0; i < 4; i++) { + checkpart = Character.getNumericValue(localstr.charAt(i * 2)); + checksum += checkpart; + checkpart = 2 * Character.getNumericValue(localstr.charAt((i * 2) + 1)); + if (checkpart >= 10) { + checksum += (checkpart - 10) + 1; + } else { + checksum += checkpart; + } + } + + /* Add check digit to data string */ + checkdigit = checksum % 10; + localstr.append((char) (checkdigit + '0')); + encodeInfo.append("Check Digit: ").append((char) (checkdigit + '0')); + encodeInfo.append('\n'); + + /* Convert string into an integer value */ + pharmacode = 0; + for (i = 0; i < localstr.length(); i++) { + pharmacode *= 10; + pharmacode += Character.getNumericValue(localstr.charAt(i)); + } + + /* Convert from decimal to base-32 */ + devisor = 33554432; + for (i = 5; i >= 0; i--) { + codeword[i] = pharmacode / devisor; + remainder = pharmacode % devisor; + pharmacode = remainder; + devisor /= 32; + } + + /* Look up values in 'Tabella di conversione' */ + risultante = new StringBuilder(); + for (i = 5; i >= 0; i--) { + risultante.append(tabella[codeword[i]]); + } + + /* Plot the barcode using Code 39 */ + + readable = new StringBuilder("A").append(localstr); + pattern = new String[1]; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + encodeInfo.append("Code 39 Equivalent: ").append(risultante).append('\n'); + try { + c39.setContent(risultante.toString()); + } catch (Exception e) { + errorMsg.append(e.getMessage()); + return false; + } + + this.pattern[0] = c39.pattern[0]; + this.plotSymbol(); + return true; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code3Of9.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code3Of9.java new file mode 100755 index 0000000..7fb0378 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code3Of9.java @@ -0,0 +1,155 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Code 39 bar code symbology according to ISO/IEC 16388:2007. + * Input data can be of any length and supports the characters 0-9, A-Z, dash + * (-), full stop (.), space, asterisk (*), dollar ($), slash (/), plus (+) + * and percent (%). The standard does not require a check digit but a + * modulo-43 check digit can be added if required. + */ +public class Code3Of9 extends Symbol { + + private static final String[] CODE_39 = { + "1112212111", "2112111121", "1122111121", "2122111111", "1112211121", + "2112211111", "1122211111", "1112112121", "2112112111", "1122112111", + "2111121121", "1121121121", "2121121111", "1111221121", "2111221111", + "1121221111", "1111122121", "2111122111", "1121122111", "1111222111", + "2111111221", "1121111221", "2121111211", "1111211221", "2111211211", + "1121211211", "1111112221", "2111112211", "1121112211", "1111212211", + "2211111121", "1221111121", "2221111111", "1211211121", "2211211111", + "1221211111", "1211112121", "2211112111", "1221112111", "1212121111", + "1212111211", "1211121211", "1112121211" + }; + private static final char[] LOOKUP = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', + '%' + }; + private CheckDigit checkOption = CheckDigit.NONE; + /** + * Ratio of wide bar width to narrow bar width. + */ + private double moduleWidthRatio = 2; + + /** + * Returns the ratio of wide bar width to narrow bar width. + * + * @return the ratio of wide bar width to narrow bar width + */ + public double getModuleWidthRatio() { + return moduleWidthRatio; + } + + /** + * Sets the ratio of wide bar width to narrow bar width. Valid values are usually + * between {@code 2} and {@code 3}. The default value is {@code 2}. + * + * @param moduleWidthRatio the ratio of wide bar width to narrow bar width + */ + public void setModuleWidthRatio(double moduleWidthRatio) { + this.moduleWidthRatio = moduleWidthRatio; + } + + /** + * Select addition of optional Modulo-43 check digit or encoding without + * check digit. + * + * @param checkMode Check digit option. + */ + public void setCheckDigit(CheckDigit checkMode) { + checkOption = checkMode; + } + + @Override + public boolean encode() { + if (!(content.matches("[0-9A-Z\\. \\-$/+%]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + StringBuilder p = new StringBuilder(); + StringBuilder dest = new StringBuilder(); + int l = content.length(); + int charval; + char thischar; + int counter = 0; + char check_digit = ' '; + dest.append("1211212111"); + for (int i = 0; i < l; i++) { + thischar = content.charAt(i); + charval = positionOf(thischar, LOOKUP); + counter += charval; + p.append(CODE_39[charval]); + } + dest.append(p); + if (checkOption == CheckDigit.MOD43) { + counter = counter % 43; + if (counter < 10) { + check_digit = (char) (counter + '0'); + } else { + if (counter < 36) { + check_digit = (char) ((counter - 10) + 'A'); + } else { + switch (counter) { + case 36: + check_digit = '-'; + break; + case 37: + check_digit = '.'; + break; + case 38: + check_digit = ' '; + break; + case 39: + check_digit = '$'; + break; + case 40: + check_digit = '/'; + break; + case 41: + check_digit = '+'; + break; + default: + check_digit = 37; + break; + } + } + } + charval = positionOf(check_digit, LOOKUP); + p.append(CODE_39[charval]); + if (check_digit == ' ') { + check_digit = '_'; + } + } + dest.append("121121211"); + if (checkOption == CheckDigit.MOD43) { + readable = new StringBuilder("*").append(content).append(check_digit).append("*"); + encodeInfo.append("Check Digit: ").append(check_digit).append("\n"); + } else { + readable = new StringBuilder("*").append(content).append("*"); + } + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + plotSymbol(); + return true; + } + + @Override + protected double getModuleWidth(int originalWidth) { + if (originalWidth == 1) { + return 1; + } else { + return moduleWidthRatio; + } + } + + @Override + protected int[] getCodewords() { + return getPatternAsCodewords(10); + } + + public enum CheckDigit { + NONE, MOD43 + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code3Of9Extended.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code3Of9Extended.java new file mode 100755 index 0000000..30d9e63 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code3Of9Extended.java @@ -0,0 +1,79 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Code 3 of 9 Extended, also known as Code 39e and Code39+. + * Supports encoding of all characters in the 7-bit ASCII table. A + * modulo-43 check digit can be added if required. + */ +public class Code3Of9Extended extends Symbol { + + private final String[] ECode39 = { + "%U", "$A", "$B", "$C", "$D", "$E", "$F", "$G", "$H", "$I", "$J", "$K", + "$L", "$M", "$N", "$O", "$P", "$Q", "$R", "$S", "$T", "$U", "$V", "$W", + "$X", "$Y", "$Z", "%A", "%B", "%C", "%D", "%E", " ", "/A", "/B", "/C", + "/D", "/E", "/F", "/G", "/H", "/I", "/J", "/K", "/L", "-", ".", "/O", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "/Z", "%F", "%G", + "%H", "%I", "%J", "%V", "A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "%K", "%L", "%M", "%N", "%O", "%W", "+A", "+B", "+C", + "+D", "+E", "+F", "+G", "+H", "+I", "+J", "+K", "+L", "+M", "+N", "+O", + "+P", "+Q", "+R", "+S", "+T", "+U", "+V", "+W", "+X", "+Y", "+Z", "%P", + "%Q", "%R", "%S", "%T" + }; + private CheckDigit checkOption; + + public Code3Of9Extended() { + checkOption = CheckDigit.NONE; + } + + /** + * Select addition of optional Modulo-43 check digit or encoding without + * check digit. + * + * @param checkMode Check digit option + */ + public void setCheckDigit(CheckDigit checkMode) { + checkOption = checkMode; + } + + @Override + public boolean encode() { + StringBuilder buffer = new StringBuilder(); + int l = content.length(); + int asciicode; + Code3Of9 c = new Code3Of9(); + + if (checkOption == CheckDigit.MOD43) { + c.setCheckDigit(Code3Of9.CheckDigit.MOD43); + } + + if (!content.matches("[\u0000-\u007F]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + for (int i = 0; i < l; i++) { + asciicode = content.charAt(i); + buffer.append(ECode39[asciicode]); + } + + try { + c.setContent(buffer.toString()); + } catch (Exception e) { + errorMsg.append(e.getMessage()); + return false; + } + readable = new StringBuilder(content); + pattern = new String[1]; + pattern[0] = c.pattern[0]; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + public enum CheckDigit { + NONE, MOD43 + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code49.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code49.java new file mode 100755 index 0000000..ef55083 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code49.java @@ -0,0 +1,1345 @@ +package org.xbib.graphics.barcode; + +import java.awt.geom.Rectangle2D; + +/** + * Implements Code 49 according to ANSI/AIM-BC6-2000. + * Encoding supports full 7-bit ASCII input up to a maximum of 49 characters + * or 81 numeric digits. GS1 data encoding is also supported. + */ +public class Code49 extends Symbol { + + private final String[] c49_table7 = { + /* Table 7: Code 49 ASCII Chart */ + "! ", "!A", "!B", "!C", "!D", "!E", "!F", "!G", "!H", "!I", "!J", "!K", + "!L", "!M", "!N", "!O", "!P", "!Q", "!R", "!S", "!T", "!U", "!V", "!W", + "!X", "!Y", "!Z", "!1", "!2", "!3", "!4", "!5", " ", "!6", "!7", "!8", + "$", "%", "!9", "!0", "!-", "!.", "!$", "+", "!/", "-", ".", "/", "0", + "1", "2", "3", "4", "5", "6", "7", "8", "9", "!+", "&1", "&2", "&3", + "&4", "&5", "&6", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", + "Z", "&7", "&8", "&9", "&0", "&-", "&.", "&A", "&B", "&C", "&D", "&E", + "&F", "&G", "&H", "&I", "&J", "&K", "&L", "&M", "&N", "&O", "&P", "&Q", + "&R", "&S", "&T", "&U", "&V", "&W", "&X", "&Y", "&Z", "&$", "&/", "&+", + "&%", "& " + }; + + /* Table 5: Check Character Weighting Values */ + private final int[] c49_x_weight = { + 1, 9, 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, + 5, 41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10 + }; + + private final int[] c49_y_weight = { + 9, 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5, + 41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10, 24 + }; + + private final int[] c49_z_weight = { + 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5, 41, + 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10, 24, 30 + }; + + private final String[] c49_table4 = { + /* Table 4: Row Parity Pattern for Code 49 Symbols */ + "OEEO", "EOEO", "OOEE", "EEOO", "OEOE", "EOOE", "OOOO", "EEEE" + }; + + private final String[] c49_appxe_even = { + /* Appendix E - Code 49 Encodation Patterns (Even Symbol Character Parity) */ + /* Column 1 */ + "11521132", "25112131", "14212132", "25121221", "14221222", "12412132", + "23321221", "12421222", "21521221", "15112222", "15121312", "13312222", + "24221311", "13321312", "11512222", "22421311", "11521312", "25112311", + "14212312", "23312311", "12412312", "21512311", "16121131", "14321131", + "12521131", "15212131", "15221221", "13412131", "13421221", "11612131", + "16112221", "16121311", "14312221", "14321311", "12512221", "12521311", + "15212311", "13412311", "11612311", "11131135", "31131133", "51131131", + "21122134", "41122132", "21131224", "41131222", "11113135", "31113133", + "51113131", "11122225", "31122223", "51122221", "11131315", "31131313", + "51131311", "21113224", "41113222", "21122314", + /* Column 2 */ + "41122312", "11113315", "31113313", "51113311", "12131134", "32131132", + "21231133", "41231131", "22122133", "42122131", "11222134", "22131223", + "42131221", "11231224", "31231222", "12113134", "32113132", "12122224", + "32122222", "12131314", "32131312", "21231313", "41231311", "22113223", + "42113221", "11213224", "22122313", "42122311", "11222314", "31222312", + "12113314", "32113312", "21213313", "41213311", "13131133", "33131131", + "22231132", "11331133", "31331131", "23122132", "12222133", "23131222", + "12231223", "32231221", "21331222", "13113133", "33113131", "13122223", + "33122221", "11313133", "13131313", "33131311", "11322223", "22231312", + "11331313", "31331311", "23113222", "12213223", + /* Column 3 */ + "23122312", "12222313", "32222311", "21322312", "13113313", "33113311", + "22213312", "11313313", "31313311", "14131132", "23231131", "12331132", + "21431131", "24122131", "13222132", "24131221", "13231222", "11422132", + "22331221", "11431222", "14113132", "14122222", "12313132", "14131312", + "12322222", "23231311", "12331312", "21431311", "24113221", "13213222", + "24122311", "13222312", "11413222", "22322311", "11422312", "14113312", + "23213311", "12313312", "21413311", "15131131", "13331131", "14222131", + "14231221", "12422131", "12431221", "15113131", "15122221", "13313131", + "15131311", "13322221", "11513131", "13331311", "11522221", "14213221", + "14222311", "12413221", "12422311", "15113311", + /* Column 4 */ + "13313311", "11513311", "11141134", "31141132", "21132133", "41132131", + "21141223", "41141221", "11123134", "31123132", "11132224", "31132222", + "11141314", "31141312", "21114133", "41114131", "21123223", "41123221", + "21132313", "41132311", "11114224", "31114222", "11123314", "31123312", + "21114313", "41114311", "12141133", "32141131", "21241132", "22132132", + "11232133", "22141222", "11241223", "31241221", "12123133", "32123131", + "12132223", "32132221", "12141313", "32141311", "21241312", "22114132", + "11214133", "22123222", "11223223", "22132312", "11232313", "31232311", + "12114223", "32114221", "12123313", "32123311", "21223312", "22114312", + "11214313", "31214311", "13141132", "22241131", + /* Column 5 */ + "11341132", "23132131", "12232132", "23141221", "12241222", "21341221", + "13123132", "13132222", "11323132", "13141312", "11332222", "22241311", + "11341312", "23114131", "12214132", "23123221", "12223222", "23132311", + "12232312", "21332311", "13114222", "13123312", "11314222", "22223311", + "11323312", "23114311", "12214312", "21314311", "14141131", "12341131", + "13232131", "13241221", "11432131", "14123131", "14132221", "12323131", + "14141311", "12332221", "12341311", "13214131", "13223221", "11414131", + "13232311", "11423221", "11432311", "14114221", "14123311", "12314221", + "12323311", "13214311", "11414311", "11151133", "31151131", "21142132", + "21151222", "11133133", "31133131", "11142223", + /* Column 6 */ + "31142221", "11151313", "31151311", "21124132", "21133222", "21142312", + "11115133", "31115131", "11124223", "31124221", "11133313", "31133311", + "21115222", "21124312", "12151132", "21251131", "22142131", "11242132", + "22151221", "11251222", "12133132", "12142222", "12151312", "21251311", + "22124131", "11224132", "22133221", "11233222", "22142311", "11242312", + "12115132", "12124222", "12133312", "21233311", "22115221", "11215222", + "22124311", "11224312", "13151131", "12242131", "12251221", "13133131", + "13142221", "11333131", "13151311", "11342221", "12224131", "12233221", + "12242311", "13115131", "13124221", "11315131", "13133311", "11324221", + "11333311", "12215221", "12224311", "11161132", + /* Column 7 */ + "21152131", "21161221", "11143132", "11152222", "11161312", "21134131", + "21143221", "21152311", "11125132", "11134222", "11143312", "21116131", + "21125221", "21134311", "12161131", "11252131", "12143131", "12152221", + "12161311", "11234131", "11243221", "11252311", "12125131", "12134221", + "12143311", "11216131", "11225221", "11234311", "11111236", "31111234", + "51111232", "21111325", "41111323", "61111321", "11111416", "31111414", + "51111412", "31211143", "51211141", "12111235", "32111233", "52111231", + "21211234", "41211232", "22111324", "42111322", "11211325", "31211323", + "51211321", "12111415", "32111413", "52111411", "21211414", "41211412", + "12211144", "32211142", "21311143", "41311141", + /* Column 8 */ + "13111234", "33111232", "22211233", "42211231", "11311234", "31311232", + "23111323", "43111321", "12211324", "32211322", "21311323", "41311321", + "13111414", "33111412", "22211413", "42211411", "11311414", "31311412", + "13211143", "33211141", "22311142", "11411143", "31411141", "14111233", + "34111231", "23211232", "12311233", "32311231", "21411232", "24111322", + "13211323", "33211321", "22311322", "11411323", "31411321", "14111413", + "34111411", "23211412", "12311413", "32311411", "21411412", "14211142", + "23311141", "12411142", "21511141", "15111232", "24211231", "13311232", + "22411231", "11511232", "25111321", "14211322", "23311321", "12411322", + "21511321", "15111412", "24211411", "13311412", + /* Column 9 */ + "22411411", "11511412", "15211141", "13411141", "11611141", "16111231", + "14311231", "12511231", "15211321", "13411321", "11611321", "16111411", + "14311411", "12511411", "21121144", "41121142", "11112145", "31112143", + "51112141", "11121235", "31121233", "51121231", "21112234", "41112232", + "21121324", "41121322", "11112325", "31112323", "51112321", "11121415", + "31121413", "51121411", "21112414", "41112412", "22121143", "42121141", + "11221144", "31221142", "12112144", "32112142", "12121234", "32121232", + "21221233", "41221231", "22112233", "42112231", "11212234", "22121323", + "42121321", "11221324", "31221322", "12112324", "32112322", "12121414", + "32121412", "21221413", "41221411", "22112413", + /* Column 10 */ + "42112411", "11212414", "31212412", "23121142", "12221143", "32221141", + "21321142", "13112143", "33112141", "13121233", "33121231", "11312143", + "22221232", "11321233", "31321231", "23112232", "12212233", "23121322", + "12221323", "32221321", "21321322", "13112323", "33112321", "13121413", + "33121411", "11312323", "22221412", "11321413", "31321411", "23112412", + "12212413", "32212411", "21312412", "24121141", "13221142", "22321141", + "11421142", "14112142", "14121232", "12312142", "23221231", "12321232", + "21421231", "24112231", "13212232", "24121321", "13221322", "11412232", + "22321321", "11421322", "14112322", "14121412", "12312322", "23221411", + "12321412", "21421411", "24112411", "13212412", + /* Column 11 */ + "22312411", "11412412", "14221141", "12421141", "15112141", "15121231", + "13312141", "13321231", "11512141", "11521231", "14212231", "14221321", + "12412231", "12421321", "15112321", "15121411", "13312321", "13321411", + "11512321", "11521411", "14212411", "12412411", "21131143", "41131141", + "11122144", "31122142", "11131234", "31131232", "21113143", "41113141", + "21122233", "41122231", "21131323", "41131321", "11113234", "31113232", + "11122324", "31122322", "11131414", "31131412", "21113323", "41113321", + "21122413", "41122411", "11113414", "31113412", "22131142", "11231143", + "31231141", "12122143", "32122141", "12131233", "32131231", "21231232", + "22113142", "11213143", "22122232", "11222233", + /* Column 12 */ + "22131322", "11231323", "31231321", "12113233", "32113231", "12122323", + "32122321", "12131413", "32131411", "21231412", "22113322", "11213323", + "22122412", "11222413", "31222411", "12113413", "32113411", "21213412", + "23131141", "12231142", "21331141", "13122142", "13131232", "11322142", + "22231231", "11331232", "23113141", "12213142", "23122231", "12222232", + "23131321", "12231322", "21331321", "13113232", "13122322", "11313232", + "13131412", "11322322", "22231411", "11331412", "23113321", "12213322", + "23122411", "12222412", "21322411", "13113412", "22213411", "11313412", + "13231141", "11431141", "14122141", "14131231", "12322141", "12331231", + "13213141", "13222231", "11413141", "13231321", + /* Column 13 */ + "11422231", "11431321", "14113231", "14122321", "12313231", "14131411", + "12322321", "12331411", "13213321", "13222411", "11413321", "11422411", + "14113411", "12313411", "21141142", "11132143", "31132141", "11141233", + "31141231", "21123142", "21132232", "21141322", "11114143", "31114141", + "11123233", "31123231", "11132323", "31132321", "11141413", "31141411", + "21114232", "21123322", "21132412", "11114323", "31114321", "11123413", + "31123411", "22141141", "11241142", "12132142", "12141232", "21241231", + "22123141", "11223142", "22132231", "11232232", "22141321", "11241322", + "12114142", "12123232", "12132322", "12141412", "21241411", "22114231", + "11214232", "22123321", "11223322", "22132411", + /* Column 14 */ + "11232412", "12114322", "12123412", "21223411", "12241141", "13132141", + "13141231", "11332141", "11341231", "12223141", "12232231", "12241321", + "13114141", "13123231", "11314141", "13132321", "11323231", "13141411", + "11332321", "11341411", "12214231", "12223321", "12232411", "13114321", + "13123411", "11314321", "11323411", "21151141", "11142142", "11151232", + "21133141", "21142231", "21151321", "11124142", "11133232", "11142322", + "11151412", "21115141", "21124231", "21133321", "21142411", "11115232", + "11124322", "11133412", "11251141", "12142141", "12151231", "11233141", + "11242231", "11251321", "12124141", "12133231", "12142321", "12151411", + "11215141", "11224231", "11233321", "11242411", + /* Column 15 */ + "12115231", "12124321", "12133411", "11152141", "11161231", "11134141", + "11143231", "11152321", "11161411", "11116141", "11125231", "11134321", + "11143411", "21111244", "41111242", "11111335", "31111333", "51111331", + "21111424", "41111422", "11111515", "31111513", "51111511", "21211153", + "41211151", "22111243", "42111241", "11211244", "31211242", "12111334", + "32111332", "21211333", "41211331", "22111423", "42111421", "11211424", + "31211422", "12111514", "32111512", "21211513", "41211511", "22211152", + "11311153", "31311151", "23111242", "12211243", "32211241", "21311242", + "13111333", "33111331", "22211332", "11311333", "31311331", "23111422", + "12211423", "32211421", "21311422", "13111513", + /* Column 16 */ + "33111511", "22211512", "11311513", "31311511", "23211151", "12311152", + "21411151", "24111241", "13211242", "22311241", "11411242", "14111332", + "23211331", "12311332", "21411331", "24111421", "13211422", "22311421", + "11411422", "14111512", "23211511", "12311512", "21411511", "13311151", + "11511151", "14211241", "12411241", "15111331", "13311331", "11511331", + "14211421", "12411421", "15111511", "13311511", "11511511", "31121152", + "21112153", "41112151", "21121243", "41121241", "11112244", "31112242", + "11121334", "31121332", "21112333", "41112331", "21121423", "41121421", + "11112424", "31112422", "11121514", "31121512", "21112513", "41112511", + "12121153", "32121151", "21221152", "22112152", + /* Column 17 */ + "11212153", "22121242", "11221243", "31221241", "12112243", "32112241", + "12121333", "32121331", "21221332", "22112332", "11212333", "22121422", + "11221423", "31221421", "12112423", "32112421", "12121513", "32121511", + "21221512", "22112512", "11212513", "31212511", "13121152", "22221151", + "11321152", "23112151", "12212152", "23121241", "12221242", "21321241", + "13112242", "13121332", "11312242", "22221331", "11321332", "23112331", + "12212332", "23121421", "12221422", "21321421", "13112422", "13121512", + "11312422", "22221511", "11321512", "23112511", "12212512", "21312511", + "14121151", "12321151", "13212151", "13221241", "11412151", "11421241", + "14112241", "14121331", "12312241", "12321331", + /* Column 18 */ + "13212331", "13221421", "11412331", "11421421", "14112421", "14121511", + "12312421", "12321511", "13212511", "11412511", "11131153", "31131151", + "21122152", "21131242", "11113153", "31113151", "11122243", "31122241", + "11131333", "31131331", "21113242", "21122332", "21131422", "11113333", + "31113331", "11122423", "31122421", "11131513", "31131511", "21113422", + "21122512", "12131152", "21231151", "22122151", "11222152", "22131241", + "11231242", "12113152", "12122242", "12131332", "21231331", "22113241", + "11213242", "22122331", "11222332", "22131421", "11231422", "12113332", + "12122422", "12131512", "21231511", "22113421", "11213422", "22122511", + "11222512", "13131151", "11331151", "12222151", + /* Column 19 */ + "12231241", "13113151", "13122241", "11313151", "13131331", "11322241", + "11331331", "12213241", "12222331", "12231421", "13113331", "13122421", + "11313331", "13131511", "11322421", "11331511", "12213421", "12222511", + "11141152", "21132151", "21141241", "11123152", "11132242", "11141332", + "21114151", "21123241", "21132331", "21141421", "11114242", "11123332", + "11132422", "11141512", "21114331", "21123421", "21132511", "12141151", + "11232151", "11241241", "12123151", "12132241", "12141331", "11214151", + "11223241", "11232331", "11241421", "12114241", "12123331", "12132421", + "12141511", "11214331", "11223421", "11232511", "11151151", "11133151", + "11142241", "11151331", "11115151", "11124241", + /* Column 20 */ + "11133331", "11142421", "11151511", "11111254", "31111252", "21111343", + "41111341", "11111434", "31111432", "21111523", "41111521", "11111614", + "31111612", "31211161", "12111253", "32111251", "21211252", "22111342", + "11211343", "31211341", "12111433", "32111431", "21211432", "22111522", + "11211523", "31211521", "12111613", "32111611", "21211612", "12211162", + "21311161", "13111252", "22211251", "11311252", "23111341", "12211342", + "21311341", "13111432", "22211431", "11311432", "23111521", "12211522", + "21311521", "13111612", "22211611", "11311612", "13211161", "11411161", + "14111251", "12311251", "13211341", "11411341", "14111431", "12311431", + "13211521", "11411521", "14111611", "12311611", + /* Column 21 */ + "21121162", "11112163", "31112161", "11121253", "31121251", "21112252", + "21121342", "11112343", "31112341", "11121433", "31121431", "21112432", + "21121522", "11112523", "31112521", "11121613", "31121611", "22121161", + "11221162", "12112162", "12121252", "21221251", "22112251", "11212252", + "22121341", "11221342", "12112342", "12121432", "21221431", "22112431", + "11212432", "22121521", "11221522", "12112522", "12121612", "21221611", + "12221161", "13112161", "13121251", "11312161", "11321251", "32121115", + "52121113", "21221116", "41221114", "61221112", "22112116", "42112114", + "31212115", "51212113", "13121116", "33121114", "22221115", "42221113", + "11321116", "31321114", "51321112", "23112115", + /* Column 22 */ + "43112113", "12212116", "32212114", "52212112", "21312115", "41312113", + "61312111", "14121115", "34121113", "23221114", "43221112", "12321115", + "32321113", "52321111", "21421114", "41421112", "24112114", "13212115", + "33212113", "22312114", "42312112", "11412115", "31412113", "51412111", + "15121114", "24221113", "13321114", "33321112", "22421113", "42421111", + "11521114", "31521112", "25112113", "14212114", "34212112", "23312113", + "43312111", "12412114", "32412112", "21512113", "41512111", "16121113", + "25221112", "14321113", "34321111", "23421112", "12521113", "32521111", + "15212113", "24312112", "13412113", "33412111", "22512112", "11612113", + "31612111", "31131115", "51131113", "21122116", + /* Column 23 */ + "41122114", "61122112", "31113115", "51113113", "12131116", "32131114", + "52131112", "21231115", "41231113", "61231111", "22122115", "42122113", + "11222116", "31222114", "51222112", "12113116", "32113114", "52113112", + "21213115", "41213113", "61213111", "13131115", "33131113", "22231114", + "42231112", "11331115", "31331113", "51331111", "23122114", "43122112", + "12222115", "32222113", "52222111", "21322114", "41322112", "13113115", + "33113113", "22213114", "42213112", "11313115", "31313113", "51313111", + "14131114", "34131112", "23231113", "43231111", "12331114", "32331112", + "21431113", "41431111", "24122113", "13222114", "33222112", "22322113", + "42322111", "11422114", "31422112", "14113114", + /* Column 24 */ + "34113112", "23213113", "43213111", "12313114", "32313112", "21413113", + "41413111", "15131113", "24231112", "13331113", "33331111", "22431112", + "25122112", "14222113", "34222111", "23322112", "12422113", "32422111", + "21522112", "15113113", "24213112", "13313113", "33313111", "22413112", + "11513113", "31513111", "16131112", "25231111", "14331112", "23431111", + "15222112", "24322111", "13422112", "22522111", "16113112", "25213111", + "14313112", "23413111", "12513112", "21613111", "11141116", "31141114", + "51141112", "21132115", "41132113", "61132111", "11123116", "31123114", + "51123112", "21114115", "41114113", "61114111", "12141115", "32141113", + "52141111", "21241114", "41241112", "22132114", + /* Column 25 */ + "42132112", "11232115", "31232113", "51232111", "12123115", "32123113", + "52123111", "21223114", "41223112", "22114114", "42114112", "11214115", + "31214113", "51214111", "13141114", "33141112", "22241113", "42241111", + "11341114", "31341112", "23132113", "43132111", "12232114", "32232112", + "21332113", "41332111", "13123114", "33123112", "22223113", "42223111", + "11323114", "31323112", "23114113", "43114111", "12214114", "32214112", + "21314113", "41314111", "14141113", "34141111", "23241112", "12341113", + "32341111", "24132112", "13232113", "33232111", "22332112", "11432113", + "31432111", "14123113", "34123111", "23223112", "12323113", "32323111", + "21423112", "24114112", "13214113", "33214111", + /* Column 26 */ + "22314112", "11414113", "31414111", "15141112", "24241111", "13341112", + "25132111", "14232112", "23332111", "12432112", "15123112", "24223111", + "13323112", "22423111", "11523112", "25114111", "14214112", "23314111", + "12414112", "21514111", "16141111", "14341111", "15232111", "13432111", + "16123111", "14323111", "12523111", "15214111", "13414111", "11614111", + "11151115", "31151113", "51151111", "21142114", "41142112", "11133115", + "31133113", "51133111", "21124114", "41124112", "11115115", "31115113", + "51115111", "12151114", "32151112", "21251113", "41251111", "22142113", + "42142111", "11242114", "31242112", "12133114", "32133112", "21233113", + "41233111", "22124113", "42124111", "11224114", + /* Column 27 */ + "31224112", "12115114", "32115112", "21215113", "41215111", "13151113", + "33151111", "22251112", "23142112", "12242113", "32242111", "21342112", + "13133113", "33133111", "22233112", "11333113", "31333111", "23124112", + "12224113", "32224111", "21324112", "13115113", "33115111", "22215112", + "11315113", "31315111", "14151112", "23251111", "24142111", "13242112", + "22342111", "14133112", "23233111", "12333112", "21433111", "24124111", + "13224112", "22324111", "11424112", "14115112", "23215111", "12315112", + "21415111", "15151111", "14242111", "15133111", "13333111", "14224111", + "12424111", "15115111", "13315111", "11515111", "11161114", "31161112", + "21152113", "41152111", "11143114", "31143112", + /* Column 28 */ + "21134113", "41134111", "11125114", "31125112", "21116113", "41116111", + "12161113", "32161111", "22152112", "11252113", "31252111", "12143113", + "32143111", "21243112", "22134112", "11234113", "31234111", "12125113", + "32125111", "21225112", "22116112", "11216113", "31216111", "13161112", + "23152111", "12252112", "13143112", "22243111", "11343112", "23134111", + "12234112", "21334111", "13125112", "22225111", "11325112", "23116111", + "12216112", "21316111", "14161111", "13252111", "14143111", "12343111", + "13234111", "11434111", "14125111", "12325111", "13216111", "11416111", + "31111216", "51111214", "31211125", "51211123", "32111215", "52111213", + "21211216", "41211214", "61211212", "12211126", + /* Column 29 */ + "32211124", "52211122", "21311125", "41311123", "61311121", "13111216", + "33111214", "22211215", "42211213", "11311216", "31311214", "51311212", + "13211125", "33211123", "22311124", "42311122", "11411125", "31411123", + "51411121", "14111215", "34111213", "23211214", "43211212", "12311215", + "32311213", "52311211", "21411214", "41411212", "14211124", "34211122", + "23311123", "43311121", "12411124", "32411122", "21511123", "41511121", + "15111214", "24211213", "13311214", "33311212", "22411213", "42411211", + "11511214", "31511212", "15211123", "24311122", "13411123", "33411121", + "22511122", "11611123", "31611121", "16111213", "25211212", "14311213", + "34311211", "23411212", "12511213", "32511211", + /* Column 30 */ + "21611212", "21121126", "41121124", "61121122", "31112125", "51112123", + "31121215", "51121213", "21112216", "41112214", "61112212", "22121125", + "42121123", "11221126", "31221124", "51221122", "12112126", "32112124", + "52112122", "12121216", "32121214", "52121212", "21221215", "41221213", + "61221211", "22112215", "42112213", "11212216", "31212214", "51212212", + "23121124", "43121122", "12221125", "32221123", "52221121", "21321124", + "41321122", "13112125", "33112123", "13121215", "33121213", "11312125", + "22221214", "42221212", "11321215", "31321213", "51321211", "23112214", + "43112212", "12212215", "32212213", "52212211", "21312214", "41312212", + "24121123", "13221124", "33221122", "22321123", + /* Column 31 */ + "42321121", "11421124", "31421122", "14112124", "34112122", "14121214", + "34121212", "12312124", "23221213", "43221211", "12321214", "32321212", + "21421213", "41421211", "24112213", "13212214", "33212212", "22312213", + "42312211", "11412214", "31412212", "25121122", "14221123", "34221121", + "23321122", "12421123", "32421121", "21521122", "15112123", "15121213", + "13312123", "24221212", "13321213", "33321211", "11512123", "22421212", + "11521213", "31521211", "25112212", "14212213", "34212211", "23312212", + "12412213", "32412211", "21512212", "15221122", "24321121", "13421122", + "22521121", "16112122", "16121212", "14312122", "25221211", "14321212", + "12512122", "23421211", "12521212", "15212212", + /* Column 32 */ + "24312211", "13412212", "22512211", "11612212", "21131125", "41131123", + "61131121", "11122126", "31122124", "51122122", "11131216", "31131214", + "51131212", "21113125", "41113123", "61113121", "21122215", "41122213", + "61122211", "11113216", "31113214", "51113212", "22131124", "42131122", + "11231125", "31231123", "51231121", "12122125", "32122123", "52122121", + "12131215", "32131213", "52131211", "21231214", "41231212", "22113124", + "42113122", "11213125", "22122214", "42122212", "11222215", "31222213", + "51222211", "12113215", "32113213", "52113211", "21213214", "41213212", + "23131123", "43131121", "12231124", "32231122", "21331123", "41331121", + "13122124", "33122122", "13131214", "33131212", + /* Column 33 */ + "11322124", "22231213", "42231211", "11331214", "31331212", "23113123", + "43113121", "12213124", "23122213", "43122211", "12222214", "32222212", + "21322213", "41322211", "13113214", "33113212", "22213213", "42213211", + "11313214", "31313212", "24131122", "13231123", "33231121", "22331122", + "11431123", "31431121", "14122123", "34122121", "14131213", "34131211", + "12322123", "23231212", "12331213", "32331211", "21431212", "24113122", + "13213123", "24122212", "13222213", "33222211", "11413123", "22322212", + "11422213", "31422211", "14113213", "34113211", "23213212", "12313213", + "32313211", "21413212", "25131121", "14231122", "23331121", "12431122", + "15122122", "15131212", "13322122", "24231211", + /* Column 34 */ + "13331212", "11522122", "22431211", "25113121", "14213122", "25122211", + "14222212", "12413122", "23322211", "12422212", "21522211", "15113212", + "24213211", "13313212", "22413211", "11513212", "15231121", "13431121", + "16122121", "16131211", "14322121", "14331211", "12522121", "15213121", + "15222211", "13413121", "13422211", "11613121", "16113211", "14313211", + "12513211", "21141124", "41141122", "11132125", "31132123", "51132121", + "11141215", "31141213", "51141211", "21123124", "41123122", "21132214", + "41132212", "11114125", "31114123", "51114121", "11123215", "31123213", + "51123211", "21114214", "41114212", "22141123", "42141121", "11241124", + "31241122", "12132124", "32132122", "12141214", + /* Column 35 */ + "32141212", "21241213", "41241211", "22123123", "42123121", "11223124", + "22132213", "42132211", "11232214", "31232212", "12114124", "32114122", + "12123214", "32123212", "21223213", "41223211", "22114213", "42114211", + "11214214", "31214212", "23141122", "12241123", "32241121", "21341122", + "13132123", "33132121", "13141213", "33141211", "11332123", "22241212", + "11341213", "31341211", "23123122", "12223123", "23132212", "12232213", + "32232211", "21332212", "13114123", "33114121", "13123213", "33123211", + "11314123", "22223212", "11323213", "31323211", "23114212", "12214213", + "32214211", "21314212", "24141121", "13241122", "22341121", "14132122", + "14141212", "12332122", "23241211", "12341212", + /* Column 36 */ + "24123121", "13223122", "24132211", "13232212", "11423122", "22332211", + "11432212", "14114122", "14123212", "12314122", "23223211", "12323212", + "21423211", "24114211", "13214212", "22314211", "11414212", "14241121", + "15132121", "15141211", "13332121", "13341211", "14223121", "14232211", + "12423121", "12432211", "15114121", "15123211", "13314121", "13323211", + "11514121", "11523211", "14214211", "12414211", "21151123", "41151121", + "11142124", "31142122", "11151214", "31151212", "21133123", "41133121", + "21142213", "41142211", "11124124", "31124122", "11133214", "31133212", + "21115123", "41115121", "21124213", "41124211", "11115214", "31115212", + "22151122", "11251123", "31251121", "12142123", + /* Column 37 */ + "32142121", "12151213", "32151211", "21251212", "22133122", "11233123", + "22142212", "11242213", "31242211", "12124123", "32124121", "12133213", + "32133211", "21233212", "22115122", "11215123", "22124212", "11224213", + "31224211", "12115213", "32115211", "21215212", "23151121", "12251122", + "13142122", "13151212", "11342122", "22251211", "23133121", "12233122", + "23142211", "12242212", "21342211", "13124122", "13133212", "11324122", + "22233211", "11333212", "23115121", "12215122", "23124211", "12224212", + "21324211", "13115212", "22215211", "11315212", "13251121", "14142121", + "14151211", "12342121", "13233121", "13242211", "11433121", "14124121", + "14133211", "12324121", "12333211", "13215121", + /* Column 38 */ + "13224211", "11415121", "11424211", "14115211", "12315211", "21161122", + "11152123", "31152121", "11161213", "31161211", "21143122", "21152212", + "11134123", "31134121", "11143213", "31143211", "21125122", "21134212", + "11116123", "31116121", "11125213", "31125211", "22161121", "12152122", + "12161212", "22143121", "11243122", "22152211", "11252212", "12134122", + "12143212", "21243211", "22125121", "11225122", "22134211", "11234212", + "12116122", "12125212", "21225211", "13152121", "13161211", "12243121", + "12252211", "13134121", "13143211", "11334121", "11343211", "12225121", + "12234211", "13116121", "13125211", "11316121", "11325211", "21111226", + "41111224", "61111222", "31111315", "51111313", + /* Column 39 */ + "21211135", "41211133", "61211131", "22111225", "42111223", "11211226", + "31211224", "51211222", "12111316", "32111314", "52111312", "21211315", + "41211313", "61211311", "22211134", "42211132", "11311135", "31311133", + "51311131", "23111224", "43111222", "12211225", "32211223", "52211221", + "21311224", "41311222", "13111315", "33111313", "22211314", "42211312", + "11311315", "31311313", "51311311", "23211133", "43211131", "12311134", + "32311132", "21411133", "41411131", "24111223", "13211224", "33211222", + "22311223", "42311221", "11411224", "31411222", "14111314", "34111312", + "23211313", "43211311", "12311314", "32311312", "21411313", "41411311", + "24211132", "13311133", "33311131", "22411132", + /* Column 40 */ + "11511133", "31511131", "25111222", "14211223", "34211221", "23311222", + "12411223", "32411221", "21511222", "15111313", "24211312", "13311313", + "33311311", "22411312", "11511313", "31511311", "25211131", "14311132", + "23411131", "12511132", "21611131", "15211222", "24311221", "13411222", + "22511221", "11611222", "16111312", "25211311", "14311312", "23411311", + "12511312", "21611311", "31121134", "51121132", "21112135", "41112133", + "61112131", "21121225", "41121223", "61121221", "11112226", "31112224", + "51112222", "11121316", "31121314", "51121312", "21112315", "41112313", + "61112311", "12121135", "32121133", "52121131", "21221134", "41221132", + "22112134", "42112132", "11212135", "22121224", + /* Column 41 */ + "42121222", "11221225", "31221223", "51221221", "12112225", "32112223", + "52112221", "12121315", "32121313", "52121311", "21221314", "41221312", + "22112314", "42112312", "11212315", "31212313", "51212311", "13121134", + "33121132", "22221133", "42221131", "11321134", "31321132", "23112133", + "43112131", "12212134", "23121223", "43121221", "12221224", "32221222", + "21321223", "41321221", "13112224", "33112222", "13121314", "33121312", + "11312224", "22221313", "42221311", "11321314", "31321312", + /* Column 42 */ + "23112313", "43112311", "12212314", "32212312", "21312313", "41312311", + "14121133", "34121131", "23221132", "12321133", "32321131", "21421132", + "24112132", "13212133", "24121222", "13221223", "33221221", "11412133", + "22321222", "11421223", "31421221", "14112223", "34112221", "14121313", + "34121311", "12312223", "23221312", "12321313", "32321311", "21421312", + "24112312", "13212313", "33212311", "22312312", "11412313", "31412311", + "15121132", "24221131", "13321132", "22421131" + }; + + private String[] c49_appxe_odd = { + /* Appendix E - Code 49 Encodation Patterns (Odd Symbol Character Parity) */ + /* Column 1 */ + "22121116", "42121114", "31221115", "51221113", "32112115", "52112113", + "21212116", "41212114", "61212112", "23121115", "43121113", "12221116", + "32221114", "52221112", "21321115", "41321113", "61321111", "13112116", + "33112114", "22212115", "42212113", "11312116", "31312114", "51312112", + "24121114", "13221115", "33221113", "22321114", "42321112", "11421115", + "31421113", "51421111", "14112115", "34112113", "23212114", "43212112", + "12312115", "32312113", "52312111", "21412114", "41412112", "25121113", + "14221114", "34221112", "23321113", "43321111", "12421114", "32421112", + "21521113", "41521111", "15112114", "24212113", "13312114", "33312112", + "22412113", "42412111", "11512114", "31512112", + /* Column 2 */ + "15221113", "24321112", "13421113", "33421111", "22521112", "16112113", + "25212112", "14312113", "34312111", "23412112", "12512113", "32512111", + "21612112", "21131116", "41131114", "61131112", "31122115", "51122113", + "21113116", "41113114", "61113112", "22131115", "42131113", "11231116", + "31231114", "51231112", "12122116", "32122114", "52122112", "21222115", + "41222113", "61222111", "22113115", "42113113", "11213116", "31213114", + "51213112", "23131114", "43131112", "12231115", "32231113", "52231111", + "21331114", "41331112", "13122115", "33122113", "22222114", "42222112", + "11322115", "31322113", "51322111", "23113114", "43113112", "12213115", + "32213113", "52213111", "21313114", "41313112", + /* Column 3 */ + "24131113", "13231114", "33231112", "22331113", "42331111", "11431114", + "31431112", "14122114", "34122112", "23222113", "43222111", "12322114", + "32322112", "21422113", "41422111", "24113113", "13213114", "33213112", + "22313113", "42313111", "11413114", "31413112", "25131112", "14231113", + "34231111", "23331112", "12431113", "32431111", "15122113", "24222112", + "13322113", "33322111", "22422112", "11522113", "31522111", "25113112", + "14213113", "34213111", "23313112", "12413113", "32413111", "21513112", + "15231112", "24331111", "13431112", "16122112", "25222111", "14322112", + "23422111", "12522112", "15213112", "24313111", "13413112", "22513111", + "11613112", "21141115", "41141113", "61141111", + /* Column 4 */ + "11132116", "31132114", "51132112", "21123115", "41123113", "61123111", + "11114116", "31114114", "51114112", "22141114", "42141112", "11241115", + "31241113", "51241111", "12132115", "32132113", "52132111", "21232114", + "41232112", "22123114", "42123112", "11223115", "31223113", "51223111", + "12114115", "32114113", "52114111", "21214114", "41214112", "23141113", + "43141111", "12241114", "32241112", "21341113", "41341111", "13132114", + "33132112", "22232113", "42232111", "11332114", "31332112", "23123113", + "43123111", "12223114", "32223112", "21323113", "41323111", "13114114", + "33114112", "22214113", "42214111", "11314114", "31314112", "24141112", + "13241113", "33241111", "22341112", "14132113", + /* Column 5 */ + "34132111", "23232112", "12332113", "32332111", "21432112", "24123112", + "13223113", "33223111", "22323112", "11423113", "31423111", "14114113", + "34114111", "23214112", "12314113", "32314111", "21414112", "25141111", + "14241112", "23341111", "15132112", "24232111", "13332112", "22432111", + "25123111", "14223112", "23323111", "12423112", "21523111", "15114112", + "24214111", "13314112", "22414111", "11514112", "15241111", "16132111", + "14332111", "15223111", "13423111", "16114111", "14314111", "12514111", + "21151114", "41151112", "11142115", "31142113", "51142111", "21133114", + "41133112", "11124115", "31124113", "51124111", "21115114", "41115112", + "22151113", "42151111", "11251114", "31251112", + /* Column 6 */ + "12142114", "32142112", "21242113", "41242111", "22133113", "42133111", + "11233114", "31233112", "12124114", "32124112", "21224113", "41224111", + "22115113", "42115111", "11215114", "31215112", "23151112", "12251113", + "32251111", "13142113", "33142111", "22242112", "11342113", "31342111", + "23133112", "12233113", "32233111", "21333112", "13124113", "33124111", + "22224112", "11324113", "31324111", "23115112", "12215113", "32215111", + "21315112", "24151111", "13251112", "14142112", "23242111", "12342112", + "24133111", "13233112", "22333111", "11433112", "14124112", "23224111", + "12324112", "21424111", "24115111", "13215112", "22315111", "11415112", + "14251111", "15142111", "13342111", "14233111", + /* Column 7 */ + "12433111", "15124111", "13324111", "11524111", "14215111", "12415111", + "21161113", "41161111", "11152114", "31152112", "21143113", "41143111", + "11134114", "31134112", "21125113", "41125111", "11116114", "31116112", + "22161112", "12152113", "32152111", "21252112", "22143112", "11243113", + "31243111", "12134113", "32134111", "21234112", "22125112", "11225113", + "31225111", "12116113", "32116111", "21216112", "23161111", "13152112", + "22252111", "23143111", "12243112", "21343111", "13134112", "22234111", + "11334112", "23125111", "12225112", "21325111", "13116112", "22216111", + "11316112", "14152111", "13243111", "14134111", "12334111", "13225111", + "11425111", "14116111", "12316111", "41111215", + /* Column 8 */ + "61111213", "21211126", "41211124", "61211122", "22111216", "42111214", + "31211215", "51211213", "22211125", "42211123", "11311126", "31311124", + "51311122", "23111215", "43111213", "12211216", "32211214", "52211212", + "21311215", "41311213", "61311211", "23211124", "43211122", "12311125", + "32311123", "52311121", "21411124", "41411122", "24111214", "13211215", + "33211213", "22311214", "42311212", "11411215", "31411213", "51411211", + "24211123", "13311124", "33311122", "22411123", "42411121", "11511124", + "31511122", "25111213", "14211214", "34211212", "23311213", "43311211", + "12411214", "32411212", "21511213", "41511211", "25211122", "14311123", + "34311121", "23411122", "12511123", "32511121", + /* Column 9 */ + "21611122", "15211213", "24311212", "13411213", "33411211", "22511212", + "11611213", "31611211", "31121125", "51121123", "21112126", "41112124", + "61112122", "21121216", "41121214", "61121212", "31112215", "51112213", + "12121126", "32121124", "52121122", "21221125", "41221123", "61221121", + "22112125", "42112123", "11212126", "22121215", "42121213", "11221216", + "31221214", "51221212", "12112216", "32112214", "52112212", "21212215", + "41212213", "61212211", "13121125", "33121123", "22221124", "42221122", + "11321125", "31321123", "51321121", "23112124", "43112122", "12212125", + "23121214", "43121212", "12221215", "32221213", "52221211", "21321214", + "41321212", "13112215", "33112213", "22212214", + /* Column 10 */ + "42212212", "11312215", "31312213", "51312211", "14121124", "34121122", + "23221123", "43221121", "12321124", "32321122", "21421123", "41421121", + "24112123", "13212124", "24121213", "13221214", "33221212", "11412124", + "22321213", "42321211", "11421214", "31421212", "14112214", "34112212", + "23212213", "43212211", "12312214", "32312212", "21412213", "41412211", + "15121123", "24221122", "13321123", "33321121", "22421122", "11521123", + "31521121", "25112122", "14212123", "25121212", "14221213", "34221211", + "12412123", "23321212", "12421213", "32421211", "21521212", "15112213", + "24212212", "13312213", "33312211", "22412212", "11512213", "31512211", + "16121122", "25221121", "14321122", "23421121", + /* Column 11 */ + "12521122", "15212122", "15221212", "13412122", "24321211", "13421212", + "11612122", "22521211", "16112212", "25212211", "14312212", "23412211", + "12512212", "21612211", "11131126", "31131124", "51131122", "21122125", + "41122123", "61122121", "21131215", "41131213", "61131211", "11113126", + "31113124", "51113122", "11122216", "31122214", "51122212", "21113215", + "41113213", "61113211", "12131125", "32131123", "52131121", "21231124", + "41231122", "22122124", "42122122", "11222125", "22131214", "42131212", + "11231215", "31231213", "51231211", "12113125", "32113123", "52113121", + "12122215", "32122213", "52122211", "21222214", "41222212", "22113214", + "42113212", "11213215", "31213213", "51213211", + /* Column 12 */ + "13131124", "33131122", "22231123", "42231121", "11331124", "31331122", + "23122123", "43122121", "12222124", "23131213", "43131211", "12231214", + "32231212", "21331213", "41331211", "13113124", "33113122", "13122214", + "33122212", "11313124", "22222213", "42222211", "11322214", "31322212", + "23113213", "43113211", "12213214", "32213212", "21313213", "41313211", + "14131123", "34131121", "23231122", "12331123", "32331121", "21431122", + "24122122", "13222123", "24131212", "13231213", "33231211", "11422123", + "22331212", "11431213", "31431211", "14113123", "34113121", "14122213", + "34122211", "12313123", "23222212", "12322213", "32322211", "21422212", + "24113212", "13213213", "33213211", "22313212", + /* Column 13 */ + "11413213", "31413211", "15131122", "24231121", "13331122", "22431121", + "25122121", "14222122", "25131211", "14231212", "12422122", "23331211", + "12431212", "15113122", "15122212", "13313122", "24222211", "13322212", + "11513122", "22422211", "11522212", "25113211", "14213212", "23313211", + "12413212", "21513211", "16131121", "14331121", "15222121", "15231211", + "13422121", "13431211", "16113121", "16122211", "14313121", "14322211", + "12513121", "12522211", "15213211", "13413211", "11613211", "11141125", + "31141123", "51141121", "21132124", "41132122", "21141214", "41141212", + "11123125", "31123123", "51123121", "11132215", "31132213", "51132211", + "21114124", "41114122", "21123214", "41123212", + /* Column 14 */ + "11114215", "31114213", "51114211", "12141124", "32141122", "21241123", + "41241121", "22132123", "42132121", "11232124", "22141213", "42141211", + "11241214", "31241212", "12123124", "32123122", "12132214", "32132212", + "21232213", "41232211", "22114123", "42114121", "11214124", "22123213", + "42123211", "11223214", "31223212", "12114214", "32114212", "21214213", + "41214211", "13141123", "33141121", "22241122", "11341123", "31341121", + "23132122", "12232123", "23141212", "12241213", "32241211", "21341212", + "13123123", "33123121", "13132213", "33132211", "11323123", "22232212", + "11332213", "31332211", "23114122", "12214123", "23123212", "12223213", + "32223211", "21323212", "13114213", "33114211", + /* Column 15 */ + "22214212", "11314213", "31314211", "14141122", "23241121", "12341122", + "24132121", "13232122", "24141211", "13241212", "11432122", "22341211", + "14123122", "14132212", "12323122", "23232211", "12332212", "21432211", + "24114121", "13214122", "24123211", "13223212", "11414122", "22323211", + "11423212", "14114212", "23214211", "12314212", "21414211", "15141121", + "13341121", "14232121", "14241211", "12432121", "15123121", "15132211", + "13323121", "13332211", "11523121", "14214121", "14223211", "12414121", + "12423211", "15114211", "13314211", "11514211", "11151124", "31151122", + "21142123", "41142121", "21151213", "41151211", "11133124", "31133122", + "11142214", "31142212", "21124123", "41124121", + /* Column 16 */ + "21133213", "41133211", "11115124", "31115122", "11124214", "31124212", + "21115213", "41115211", "12151123", "32151121", "21251122", "22142122", + "11242123", "22151212", "11251213", "31251211", "12133123", "32133121", + "12142213", "32142211", "21242212", "22124122", "11224123", "22133212", + "11233213", "31233211", "12115123", "32115121", "12124213", "32124211", + "21224212", "22115212", "11215213", "31215211", "13151122", "22251121", + "23142121", "12242122", "23151211", "12251212", "13133122", "13142212", + "11333122", "22242211", "11342212", "23124121", "12224122", "23133211", + "12233212", "21333211", "13115122", "13124212", "11315122", "22224211", + "11324212", "23115211", "12215212", "21315211", + /* Column 17 */ + "14151121", "13242121", "13251211", "14133121", "14142211", "12333121", + "12342211", "13224121", "13233211", "11424121", "11433211", "14115121", + "14124211", "12315121", "12324211", "13215211", "11415211", "11161123", + "31161121", "21152122", "21161212", "11143123", "31143121", "11152213", + "31152211", "21134122", "21143212", "11125123", "31125121", "11134213", + "31134211", "21116122", "21125212", "12161122", "22152121", "11252122", + "22161211", "12143122", "12152212", "21252211", "22134121", "11234122", + "22143211", "11243212", "12125122", "12134212", "21234211", "22116121", + "11216122", "22125211", "11225212", "13161121", "12252121", "13143121", + "13152211", "11343121", "12234121", "12243211", + /* Column 18 */ + "13125121", "13134211", "11325121", "11334211", "12216121", "12225211", + "31111225", "51111223", "21111316", "41111314", "61111312", "31211134", + "51211132", "12111226", "32111224", "52111222", "21211225", "41211223", + "61211221", "22111315", "42111313", "11211316", "31211314", "51211312", + "12211135", "32211133", "52211131", "21311134", "41311132", "13111225", + "33111223", "22211224", "42211222", "11311225", "31311223", "51311221", + "23111314", "43111312", "12211315", "32211313", "52211311", "21311314", + "41311312", "13211134", "33211132", "22311133", "42311131", "11411134", + "31411132", "14111224", "34111222", "23211223", "43211221", "12311224", + "32311222", "21411223", "41411221", "24111313", + /* Column 19 */ + "13211314", "33211312", "22311313", "42311311", "11411314", "31411312", + "14211133", "34211131", "23311132", "12411133", "32411131", "21511132", + "15111223", "24211222", "13311223", "33311221", "22411222", "11511223", + "31511221", "25111312", "14211313", "34211311", "23311312", "12411313", + "32411311", "21511312", "15211132", "24311131", "13411132", "22511131", + "11611132", "16111222", "25211221", "14311222", "23411221", "12511222", + "21611221", "15211312", "24311311", "13411312", "22511311", "11611312", + "21121135", "41121133", "61121131", "11112136", "31112134", "51112132", + "11121226", "31121224", "51121222", "21112225", "41112223", "61112221", + "21121315", "41121313", "61121311", "11112316", + /* Column 20 */ + "31112314", "51112312", "22121134", "42121132", "11221135", "31221133", + "51221131", "12112135", "32112133", "52112131", "12121225", "32121223", + "52121221", "21221224", "41221222", "22112224", "42112222", "11212225", + "22121314", "42121312", "11221315", "31221313", "51221311", "12112315", + "32112313", "52112311", "21212314", "41212312", "23121133", "43121131", + "12221134", "32221132", "21321133", "41321131", "13112134", "33112132", + "13121224", "33121222", "11312134", "22221223", "42221221", "11321224", + "31321222", "23112223", "43112221", "12212224", "23121313", "43121311", + "12221314", "32221312", "21321313", "41321311", "13112314", "33112312", + "22212313", "42212311", "11312314", "31312312", + /* Column 21 */ + "24121132", "13221133", "33221131", "22321132", "11421133", "31421131", + "14112133", "34112131", "14121223", "34121221", "12312133", "23221222", + "12321223", "32321221", "21421222", "24112222", "13212223", "24121312", + "13221313", "33221311", "11412223", "22321312", "11421313", "31421311", + "14112313", "34112311", "23212312", "12312313", "32312311", "21412312", + "25121131", "14221132", "23321131", "12421132", "21521131", "15112132", + "15121222", "13312132", "24221221", "13321222", "11512132", "22421221", + "11521222", "25112221", "14212222", "25121311", "14221312", "12412222", + "23321311", "12421312", "21521311", "15112312", "24212311", "13312312", + "22412311", "11512312", "15221131", "13421131", + /* Column 22 */ + "16112131", "16121221", "14312131", "14321221", "12512131", "12521221", + "15212221", "15221311", "13412221", "13421311", "11612221", "16112311", + "14312311", "12512311", "21131134", "41131132", "11122135", "31122133", + "51122131", "11131225", "31131223", "51131221", "21113134", "41113132", + "21122224", "41122222", "21131314", "41131312", "11113225", "31113223", + "51113221", "11122315", "31122313", "51122311", "21113314", "41113312", + "22131133", "42131131", "11231134", "31231132", "12122134", "32122132", + "12131224", "32131222", "21231223", "41231221", "22113133", "42113131", + "11213134", "22122223", "42122221", "11222224", "22131313", "42131311", + "11231314", "31231312", "12113224", "32113222", + /* Column 23 */ + "12122314", "32122312", "21222313", "41222311", "22113313", "42113311", + "11213314", "31213312", "23131132", "12231133", "32231131", "21331132", + "13122133", "33122131", "13131223", "33131221", "11322133", "22231222", + "11331223", "31331221", "23113132", "12213133", "23122222", "12222223", + "23131312", "12231313", "32231311", "21331312", "13113223", "33113221", + "13122313", "33122311", "11313223", "22222312", "11322313", "31322311", + "23113312", "12213313", "32213311", "21313312", "24131131", "13231132", + "22331131", "11431132", "14122132", "14131222", "12322132", "23231221", + "12331222", "21431221", "24113131", "13213132", "24122221", "13222222", + "24131311", "11413132", "13231312", "11422222", + /* Column 24 */ + "22331311", "11431312", "14113222", "14122312", "12313222", "23222311", + "12322312", "21422311", "24113311", "13213312", "22313311", "11413312", + "14231131", "12431131", "15122131", "15131221", "13322131", "13331221", + "11522131", "14213131", "14222221", "12413131", "14231311", "12422221", + "12431311", "15113221", "15122311", "13313221", "13322311", "11513221", + "11522311", "14213311", "12413311", "21141133", "41141131", "11132134", + "31132132", "11141224", "31141222", "21123133", "41123131", "21132223", + "41132221", "21141313", "41141311", "11114134", "31114132", "11123224", + "31123222", "11132314", "31132312", "21114223", "41114221", "21123313", + "41123311", "11114314", "31114312", "22141132", + /* Column 25 */ + "11241133", "31241131", "12132133", "32132131", "12141223", "32141221", + "21241222", "22123132", "11223133", "22132222", "11232223", "22141312", + "11241313", "31241311", "12114133", "32114131", "12123223", "32123221", + "12132313", "32132311", "21232312", "22114222", "11214223", "22123312", + "11223313", "31223311", "12114313", "32114311", "21214312", "23141131", + "12241132", "21341131", "13132132", "13141222", "11332132", "22241221", + "11341222", "23123131", "12223132", "23132221", "12232222", "23141311", + "12241312", "21341311", "13114132", "13123222", "11314132", "13132312", + "11323222", "22232311", "11332312", "23114221", "12214222", "23123311", + "12223312", "21323311", "13114312", "22214311", + /* Column 26 */ + "11314312", "13241131", "14132131", "14141221", "12332131", "12341221", + "13223131", "13232221", "11423131", "13241311", "11432221", "14114131", + "14123221", "12314131", "14132311", "12323221", "12332311", "13214221", + "13223311", "11414221", "11423311", "14114311", "12314311", "21151132", + "11142133", "31142131", "11151223", "31151221", "21133132", "21142222", + "21151312", "11124133", "31124131", "11133223", "31133221", "11142313", + "31142311", "21115132", "21124222", "21133312", "11115223", "31115221", + "11124313", "31124311", "22151131", "11251132", "12142132", "12151222", + "21251221", "22133131", "11233132", "22142221", "11242222", "22151311", + "11251312", "12124132", "12133222", "12142312", + /* Column 27 */ + "21242311", "22115131", "11215132", "22124221", "11224222", "22133311", + "11233312", "12115222", "12124312", "21224311", "12251131", "13142131", + "13151221", "11342131", "12233131", "12242221", "12251311", "13124131", + "13133221", "11324131", "13142311", "11333221", "11342311", "12215131", + "12224221", "12233311", "13115221", "13124311", "11315221", "11324311", + "21161131", "11152132", "11161222", "21143131", "21152221", "21161311", + "11134132", "11143222", "11152312", "21125131", "21134221", "21143311", + "11116132", "11125222", "11134312", "12152131", "12161221", "11243131", + "11252221", "12134131", "12143221", "12152311", "11225131", "11234221", + "11243311", "12116131", "12125221", "12134311", + /* Column 28 */ + "21111235", "41111233", "61111231", "11111326", "31111324", "51111322", + "21111415", "41111413", "61111411", "21211144", "41211142", "22111234", + "42111232", "11211235", "31211233", "51211231", "12111325", "32111323", + "52111321", "21211324", "41211322", "22111414", "42111412", "11211415", + "31211413", "51211411", "22211143", "42211141", "11311144", "31311142", + "23111233", "43111231", "12211234", "32211232", "21311233", "41311231", + "13111324", "33111322", "22211323", "42211321", "11311324", "31311322", + "23111413", "43111411", "12211414", "32211412", "21311413", "41311411", + "23211142", "12311143", "32311141", "21411142", "24111232", "13211233", + "33211231", "22311232", "11411233", "31411231", + /* Column 29 */ + "14111323", "34111321", "23211322", "12311323", "32311321", "21411322", + "24111412", "13211413", "33211411", "22311412", "11411413", "31411411", + "24211141", "13311142", "22411141", "11511142", "25111231", "14211232", + "23311231", "12411232", "21511231", "15111322", "24211321", "13311322", + "22411321", "11511322", "25111411", "14211412", "23311411", "12411412", + "21511411", "14311141", "12511141", "15211231", "13411231", "11611231", + "16111321", "14311321", "12511321", "15211411", "13411411", "11611411", + "31121143", "51121141", "21112144", "41112142", "21121234", "41121232", + "11112235", "31112233", "51112231", "11121325", "31121323", "51121321", + "21112324", "41112322", "21121414", "41121412", + /* Column 30 */ + "11112415", "31112413", "51112411", "12121144", "32121142", "21221143", + "41221141", "22112143", "42112141", "11212144", "22121233", "42121231", + "11221234", "31221232", "12112234", "32112232", "12121324", "32121322", + "21221323", "41221321", "22112323", "42112321", "11212324", "22121413", + "42121411", "11221414", "31221412", "12112414", "32112412", "21212413", + "41212411", "13121143", "33121141", "22221142", "11321143", "31321141", + "23112142", "12212143", "23121232", "12221233", "32221231", "21321232", + "13112233", "33112231", "13121323", "33121321", "11312233", "22221322", + "11321323", "31321321", "23112322", "12212323", "23121412", "12221413", + "32221411", "21321412", "13112413", "33112411", + /* Column 31 */ + "22212412", "11312413", "31312411", "14121142", "23221141", "12321142", + "21421141", "24112141", "13212142", "24121231", "13221232", "11412142", + "22321231", "11421232", "14112232", "14121322", "12312232", "23221321", + "12321322", "21421321", "24112321", "13212322", "24121411", "13221412", + "11412322", "22321411", "11421412", "14112412", "23212411", "12312412", + "21412411", "15121141", "13321141", "11521141", "14212141", "14221231", + "12412141", "12421231", "15112231", "15121321", "13312231", "13321321", + "11512231", "11521321", "14212321", "14221411", "12412321", "12421411", + "15112411", "13312411", "11512411", "11131144", "31131142", "21122143", + "41122141", "21131233", "41131231", "11113144", + /* Column 32 */ + "31113142", "11122234", "31122232", "11131324", "31131322", "21113233", + "41113231", "21122323", "41122321", "21131413", "41131411", "11113324", + "31113322", "11122414", "31122412", "21113413", "41113411", "12131143", + "32131141", "21231142", "22122142", "11222143", "22131232", "11231233", + "31231231", "12113143", "32113141", "12122233", "32122231", "12131323", + "32131321", "21231322", "22113232", "11213233", "22122322", "11222323", + "22131412", "11231413", "31231411", "12113323", "32113321", "12122413", + "32122411", "21222412", "22113412", "11213413", "31213411", "13131142", + "22231141", "11331142", "23122141", "12222142", "23131231", "12231232", + "21331231", "13113142", "13122232", "11313142", + /* Column 33 */ + "13131322", "11322232", "22231321", "11331322", "23113231", "12213232", + "23122321", "12222322", "23131411", "12231412", "21331411", "13113322", + "13122412", "11313322", "22222411", "11322412", "23113411", "12213412", + "21313411", "14131141", "12331141", "13222141", "13231231", "11422141", + "11431231", "14113141", "14122231", "12313141", "14131321", "12322231", + "12331321", "13213231", "13222321", "11413231", "13231411", "11422321", + "11431411", "14113321", "14122411", "12313321", "12322411", "13213411", + "11413411", "11141143", "31141141", "21132142", "21141232", "11123143", + "31123141", "11132233", "31132231", "11141323", "31141321", "21114142", + "21123232", "21132322", "21141412", "11114233", + /* Column 34 */ + "31114231", "11123323", "31123321", "11132413", "31132411", "21114322", + "21123412", "12141142", "21241141", "22132141", "11232142", "22141231", + "11241232", "12123142", "12132232", "12141322", "21241321", "22114141", + "11214142", "22123231", "11223232", "22132321", "11232322", "22141411", + "11241412", "12114232", "12123322", "12132412", "21232411", "22114321", + "11214322", "22123411", "11223412", "13141141", "11341141", "12232141", + "12241231", "13123141", "13132231", "11323141", "13141321", "11332231", + "11341321", "12214141", "12223231", "12232321", "12241411", "13114231", + "13123321", "11314231", "13132411", "11323321", "11332411", "12214321", + "12223411", "11151142", "21142141", "21151231", + /* Column 35 */ + "11133142", "11142232", "11151322", "21124141", "21133231", "21142321", + "21151411", "11115142", "11124232", "11133322", "11142412", "21115231", + "21124321", "21133411", "12151141", "11242141", "11251231", "12133141", + "12142231", "12151321", "11224141", "11233231", "11242321", "11251411", + "12115141", "12124231", "12133321", "12142411", "11215231", "11224321", + "11233411", "11161141", "11143141", "11152231", "11161321", "11125141", + "11134231", "11143321", "11152411", "11111245", "31111243", "51111241", + "21111334", "41111332", "11111425", "31111423", "51111421", "21111514", + "41111512", "31211152", "12111244", "32111242", "21211243", "41211241", + "22111333", "42111331", "11211334", "31211332", + /* Column 36 */ + "12111424", "32111422", "21211423", "41211421", "22111513", "42111511", + "11211514", "31211512", "12211153", "32211151", "21311152", "13111243", + "33111241", "22211242", "11311243", "31311241", "23111332", "12211333", + "32211331", "21311332", "13111423", "33111421", "22211422", "11311423", + "31311421", "23111512", "12211513", "32211511", "21311512", "13211152", + "22311151", "11411152", "14111242", "23211241", "12311242", "21411241", + "24111331", "13211332", "22311331", "11411332", "14111422", "23211421", + "12311422", "21411421", "24111511", "13211512", "22311511", "11411512", + "14211151", "12411151", "15111241", "13311241", "11511241", "14211331", + "12411331", "15111421", "13311421", "11511421", + /* Column 37 */ + "14211511", "12411511", "21121153", "41121151", "11112154", "31112152", + "11121244", "31121242", "21112243", "41112241", "21121333", "41121331", + "11112334", "31112332", "11121424", "31121422", "21112423", "41112421", + "21121513", "41121511", "11112514", "31112512", "22121152", "11221153", + "31221151", "12112153", "32112151", "12121243", "32121241", "21221242", + "22112242", "11212243", "22121332", "11221333", "31221331", "12112333", + "32112331", "12121423", "32121421", "21221422", "22112422", "11212423", + "22121512", "11221513", "31221511", "12112513", "32112511", "21212512", + "23121151", "12221152", "21321151", "13112152", "13121242", "11312152", + "22221241", "11321242", "23112241", "12212242", + /* Column 38 */ + "23121331", "12221332", "21321331", "13112332", "13121422", "11312332", + "22221421", "11321422", "23112421", "12212422", "23121511", "12221512", + "21321511", "13112512", "22212511", "11312512", "13221151", "11421151", + "14112151", "14121241", "12312151", "12321241", "13212241", "13221331", + "11412241", "11421331", "14112331", "14121421", "12312331", "12321421", + "13212421", "13221511", "11412421", "11421511", "14112511", "12312511", + "21131152", "11122153", "31122151", "11131243", "31131241", "21113152", + "21122242", "21131332", "11113243", "31113241", "11122333", "31122331", + "11131423", "31131421", "21113332", "21122422", "21131512", "11113423", + "31113421", "11122513", "31122511", "22131151", + /* Column 39 */ + "11231152", "12122152", "12131242", "21231241", "22113151", "11213152", + "22122241", "11222242", "22131331", "11231332", "12113242", "12122332", + "12131422", "21231421", "22113331", "11213332", "22122421", "11222422", + "22131511", "11231512", "12113422", "12122512", "21222511", "12231151", + "13122151", "13131241", "11322151", "11331241", "12213151", "12222241", + "12231331", "13113241", "13122331", "11313241", "13131421", "11322331", + "11331421", "12213331", "12222421", "12231511", "13113421", "13122511", + "11313421", "11322511", "21141151", "11132152", "11141242", "21123151", + "21132241", "21141331", "11114152", "11123242", "11132332", "11141422", + "21114241", "21123331", "21132421", "21141511", + /* Column 40 */ + "11114332", "11123422", "11132512", "11241151", "12132151", "12141241", + "11223151", "11232241", "11241331", "12114151", "12123241", "12132331", + "12141421", "11214241", "11223331", "11232421", "11241511", "12114331", + "12123421", "12132511", "11142151", "11151241", "11124151", "11133241", + "11142331", "11151421", "11115241", "11124331", "11133421", "11142511", + "21111253", "41111251", "11111344", "31111342", "21111433", "41111431", + "11111524", "31111522", "21111613", "41111611", "21211162", "22111252", + "11211253", "31211251", "12111343", "32111341", "21211342", "22111432", + "11211433", "31211431", "12111523", "32111521", "21211522", "22111612", + "11211613", "31211611", "22211161", "11311162", + /* Column 41 */ + "23111251", "12211252", "21311251", "13111342", "22211341", "11311342", + "23111431", "12211432", "21311431", "13111522", "22211521", "11311522", + "23111611", "12211612", "21311611", "12311161", "13211251", "11411251", + "14111341", "12311341", "13211431", "11411431", "14111521", "12311521", + "13211611", "11411611", "31121161", "21112162", "21121252", "11112253", + "31112251", "11121343", "31121341", "21112342", "21121432", "11112433", + "31112431", "11121523", "31121521", "21112522", "21121612", + /* Column 42 */ + "12121162", "21221161", "22112161", "11212162", "22121251", "11221252", + "12112252", "12121342", "21221341", "22112341", "11212342", "22121431", + "11221432", "12112432", "12121522", "21221521", "22112521", "11212522", + "22121611", "11221612", "13121161", "11321161", "12212161", "12221251", + "13112251", "13121341", "11312251", "11321341", "12212341", "12221431", + "13112431", "13121521", "11312431", "11321521", "12212521", "12221611", + "11131162", "21122161", "21131251", "11113162" + }; + + private char[] C49_Set = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', + '%', '!', '&', '*' + }; + + @Override + public boolean encode() { + int length = content.length(); + int i, codeword_count = 0, h, j, M, rows, pad_count = 0; + int x_count, y_count, z_count, posn_val, local_value; + StringBuilder intermediate = new StringBuilder(); + StringBuilder localpattern; + int[] codewords = new int[170]; + int[][] c_grid = new int[8][8]; + int[][] w_grid = new int[8][4]; + + if (!content.matches("[\u0000-\u007F]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + if (inputDataType == DataType.GS1) { + intermediate.append("*"); // FNC1 + } + for (i = 0; i < length; i++) { + if (content.charAt(i) > 127) { + errorMsg.append("Invalid characters in input"); + return false; + } + if ((inputDataType == DataType.GS1) && (content.charAt(i) == '[')) { + intermediate.append("*"); // FNC1 + } else { + intermediate.append(c49_table7[content.charAt(i)]); + } + } + + h = intermediate.length(); + + i = 0; + do { + if ((intermediate.charAt(i) >= '0') && (intermediate.charAt(i) <= '9')) { + + /* Numeric data */ + int latch = 0; + j = 0; + do { + if ((i + j) >= h) { + latch = 1; + } else { + if ((intermediate.charAt(i + j) >= '0') && (intermediate.charAt(i + j) <= '9')) { + j++; + } else { + latch = 1; + } + } + } while (latch == 0); + if (j >= 5) { + /* Use Numeric Encodation Method */ + int block_count, c; + int block_remain; + int block_value; + + codewords[codeword_count] = 48; /* Numeric Shift */ + codeword_count++; + + block_count = j / 5; + block_remain = j % 5; + + for (c = 0; c < block_count; c++) { + if ((c == block_count - 1) && (block_remain == 2)) { + /* Rule (d) */ + block_value = 100000; + block_value += (intermediate.charAt(i) - '0') * 1000; + block_value += (intermediate.charAt(i + 1) - '0') * 100; + block_value += (intermediate.charAt(i + 2) - '0') * 10; + block_value += intermediate.charAt(i + 3) - '0'; + + codewords[codeword_count] = block_value / (48 * 48); + block_value = block_value - (48 * 48) * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value / 48; + block_value = block_value - 48 * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value; + codeword_count++; + i += 4; + block_value = (intermediate.charAt(i) - '0') * 100; + block_value += (intermediate.charAt(i + 1) - '0') * 10; + block_value += intermediate.charAt(i + 2) - '0'; + + codewords[codeword_count] = block_value / 48; + block_value = block_value - 48 * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value; + codeword_count++; + i += 3; + } else { + block_value = (intermediate.charAt(i) - '0') * 10000; + block_value += (intermediate.charAt(i + 1) - '0') * 1000; + block_value += (intermediate.charAt(i + 2) - '0') * 100; + block_value += (intermediate.charAt(i + 3) - '0') * 10; + block_value += intermediate.charAt(i + 4) - '0'; + + codewords[codeword_count] = block_value / (48 * 48); + block_value = block_value - (48 * 48) * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value / 48; + block_value = block_value - 48 * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value; + codeword_count++; + i += 5; + } + } + + switch (block_remain) { + case 1: + /* Rule (a) */ + codewords[codeword_count] = positionOf(intermediate.charAt(i), C49_Set); + codeword_count++; + i++; + break; + case 3: + /* Rule (b) */ + block_value = (intermediate.charAt(i) - '0') * 100; + block_value += (intermediate.charAt(i + 1) - '0') * 10; + block_value += intermediate.charAt(i + 2) - '0'; + + codewords[codeword_count] = block_value / 48; + block_value = block_value - 48 * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value; + codeword_count++; + i += 3; + break; + case 4: + /* Rule (c) */ + block_value = 100000; + block_value += (intermediate.charAt(i) - '0') * 1000; + block_value += (intermediate.charAt(i + 1) - '0') * 100; + block_value += (intermediate.charAt(i + 2) - '0') * 10; + block_value += intermediate.charAt(i + 3) - '0'; + + codewords[codeword_count] = block_value / (48 * 48); + block_value = block_value - (48 * 48) * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value / 48; + block_value = block_value - 48 * codewords[codeword_count]; + codeword_count++; + codewords[codeword_count] = block_value; + codeword_count++; + i += 4; + break; + } + if (i < h) { + /* There is more to add */ + codewords[codeword_count] = 48; /* Numeric Shift */ + codeword_count++; + } + } else { + codewords[codeword_count] = positionOf(intermediate.charAt(i), C49_Set); + codeword_count++; + i++; + } + } else { + codewords[codeword_count] = positionOf(intermediate.charAt(i), C49_Set); + codeword_count++; + i++; + } + } while (i < h); + + switch (codewords[0]) { /* Set starting mode value */ + case 48: + M = 2; + break; + case 43: + M = 4; + break; + case 44: + M = 5; + break; + default: + M = 0; + break; + } + + if (M != 0) { + for (i = 0; i < codeword_count; i++) { + codewords[i] = codewords[i + 1]; + } + codeword_count--; + } + + if (codeword_count > 49) { + errorMsg.append("Input too long"); + return false; + } + + encodeInfo.append("Starting Mode (M): ").append(M).append("\n"); + + /* Place codewords in code character array (c grid) */ + rows = 0; + do { + for (i = 0; i < 7; i++) { + if (((rows * 7) + i) < codeword_count) { + c_grid[rows][i] = codewords[(rows * 7) + i]; + } else { + c_grid[rows][i] = 48; /* Pad */ + pad_count++; + } + } + rows++; + } while ((rows * 7) < codeword_count); + + if ((((rows <= 6) && (pad_count < 5))) || (rows > 6) || (rows == 1)) { + /* Add a row */ + for (i = 0; i < 7; i++) { + c_grid[rows][i] = 48; /* Pad */ + } + rows++; + } + + /* Add row count and mode character */ + c_grid[rows - 1][6] = (7 * (rows - 2)) + M; + + /* Add row check character */ + for (i = 0; i < rows - 1; i++) { + int row_sum = 0; + + for (j = 0; j < 7; j++) { + row_sum += c_grid[i][j]; + } + c_grid[i][7] = row_sum % 49; + } + + /* Calculate Symbol Check Characters */ + posn_val = 0; + x_count = c_grid[rows - 1][6] * 20; + y_count = c_grid[rows - 1][6] * 16; + z_count = c_grid[rows - 1][6] * 38; + for (i = 0; i < rows - 1; i++) { + for (j = 0; j < 4; j++) { + local_value = (c_grid[i][2 * j] * 49) + c_grid[i][(2 * j) + 1]; + x_count += c49_x_weight[posn_val] * local_value; + y_count += c49_y_weight[posn_val] * local_value; + z_count += c49_z_weight[posn_val] * local_value; + posn_val++; + } + } + + if (rows > 6) { + /* Add Z Symbol Check */ + c_grid[rows - 1][0] = (z_count % 2401) / 49; + c_grid[rows - 1][1] = (z_count % 2401) % 49; + } + + local_value = (c_grid[rows - 1][0] * 49) + c_grid[rows - 1][1]; + x_count += c49_x_weight[posn_val] * local_value; + y_count += c49_y_weight[posn_val] * local_value; + posn_val++; + + /* Add Y Symbol Check */ + c_grid[rows - 1][2] = (y_count % 2401) / 49; + c_grid[rows - 1][3] = (y_count % 2401) % 49; + + local_value = (c_grid[rows - 1][2] * 49) + c_grid[rows - 1][3]; + x_count += c49_x_weight[posn_val] * local_value; + + /* Add X Symbol Check */ + c_grid[rows - 1][4] = (x_count % 2401) / 49; + c_grid[rows - 1][5] = (x_count % 2401) % 49; + + encodeInfo.append("Check Characters: ").append(Integer.toString(z_count % 2401)).append(" ").append(Integer.toString(y_count % 2401)).append("\n"); + + /* Add last row check character */ + j = 0; + for (i = 0; i < 7; i++) { + j += c_grid[rows - 1][i]; + } + c_grid[rows - 1][7] = j % 49; + + encodeInfo.append("Codewords: "); + /* Transfer data to symbol character array (w grid) */ + for (i = 0; i < rows; i++) { + for (j = 0; j < 4; j++) { + w_grid[i][j] = (c_grid[i][2 * j] * 49) + c_grid[i][(2 * j) + 1]; + encodeInfo.append(Integer.toString(c_grid[i][2 * j])).append(" ").append(Integer.toString(c_grid[i][(2 * j) + 1])).append(" "); + } + } + encodeInfo.append("\n"); + + readable = new StringBuilder(); + pattern = new String[rows]; + rowCount = rows; + rowHeight = new int[rows]; + + encodeInfo.append("Symbol Characters: "); + for (i = 0; i < rows; i++) { + localpattern = new StringBuilder("11"); /* Start character */ + for (j = 0; j < 4; j++) { + encodeInfo.append(Integer.toString(w_grid[i][j])).append(" "); + if (i != (rows - 1)) { + if (c49_table4[i].charAt(j) == 'E') { + /* Even Parity */ + localpattern.append(c49_appxe_even[w_grid[i][j]]); + } else { + /* Odd Parity */ + localpattern.append(c49_appxe_odd[w_grid[i][j]]); + } + } else { + /* Last row uses all even parity */ + localpattern.append(c49_appxe_even[w_grid[i][j]]); + } + } + localpattern.append("4"); /* Stop character */ + + pattern[i] = localpattern.toString(); + rowHeight[i] = 10; + + } + encodeInfo.append("\n"); + plotSymbol(); + return true; + } + + @Override + protected void plotSymbol() { + int xBlock, yBlock; + int x, y, w, h; + boolean black; + getRectangles().clear(); + y = 1; + h = 1; + for (yBlock = 0; yBlock < rowCount; yBlock++) { + black = true; + x = 15; + for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) { + if (black) { + black = false; + w = pattern[yBlock].charAt(xBlock) - '0'; + if (rowHeight[yBlock] == -1) { + h = defaultHeight; + } else { + h = rowHeight[yBlock]; + } + if (w != 0 && h != 0) { + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + } + if ((x + w) > symbolWidth) { + symbolWidth = x + w; + } + } else { + black = true; + } + x += (double) (pattern[yBlock].charAt(xBlock) - '0'); + } + y += h; + if ((y + h) > symbolHeight) { + symbolHeight = y + h; + } + /* Add bars between rows */ + if (yBlock != (rowCount - 1)) { + Rectangle2D.Double rect = new Rectangle2D.Double(15, y - 1, (symbolWidth - 15), 2); + getRectangles().add(rect); + } + } + Rectangle2D.Double top = new Rectangle2D.Double(0, 0, (symbolWidth + 15), 2); + getRectangles().add(top); + Rectangle2D.Double bottom = new Rectangle2D.Double(0, y - 1, (symbolWidth + 15), 2); + getRectangles().add(bottom); + symbolWidth += 30; + symbolHeight += 2; + mergeVerticalBlocks(); + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Code93.java b/barcode/src/main/java/org/xbib/graphics/barcode/Code93.java new file mode 100755 index 0000000..a286650 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Code93.java @@ -0,0 +1,192 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Code 93. + * Supports encoding of 7-bit ASCII text. Two check digits are added. + */ +public class Code93 extends Symbol { + + /** + * Code 93 control characters, indexed by ASCII codes (NOTE: a = Ctrl $, + * b = Ctrl %, c = Ctrl /, d = Ctrl + for sequences of two characters). + */ + private static final String[] CODE_93_CTRL = { + "bU", "aA", "aB", "aC", "aD", "aE", "aF", "aG", "aH", "aI", + "aJ", "aK", "aL", "aM", "aN", "aO", "aP", "aQ", "aR", "aS", + "aT", "aU", "aV", "aW", "aX", "aY", "aZ", "bA", "bB", "bC", + "bD", "bE", " ", "cA", "cB", "cC", "$", "%", "cF", "cG", + "cH", "cI", "cJ", "+", "cL", "-", ".", "/", "0", "1", + "2", "3", "4", "5", "6", "7", "8", "9", "cZ", "bF", + "bG", "bH", "bI", "bJ", "bV", "A", "B", "C", "D", "E", + "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", + "Z", "bK", "bL", "bM", "bN", "bO", "bW", "dA", "dB", "dC", + "dD", "dE", "dF", "dG", "dH", "dI", "dJ", "dK", "dL", "dM", + "dN", "dO", "dP", "dQ", "dR", "dS", "dT", "dU", "dV", "dW", + "dX", "dY", "dZ", "bP", "bQ", "bR", "bS", "bT"}; + + /** + * Mapping of control characters to pattern table index (NOTE: a = Ctrl $, + * b = Ctrl %, c = Ctrl /, d = Ctrl + for sequences of two characters). + */ + private static final char[] CODE_93_LOOKUP = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', + '/', '+', '%', 'a', 'b', 'c', 'd'}; + + /** + * Code 93 pattern table. + */ + private static final String[] CODE_93_TABLE = { + "131112", "111213", "111312", "111411", "121113", + "121212", "121311", "111114", "131211", "141111", + "211113", "211212", "211311", "221112", "221211", + "231111", "112113", "112212", "112311", "122112", + "132111", "111123", "111222", "111321", "121122", + "131121", "212112", "212211", "211122", "211221", + "221121", "222111", "112122", "112221", "122121", + "123111", "121131", "311112", "311211", "321111", + "112131", "113121", "211131", "121221", "312111", + "311121", "122211"}; + + /** + * Whether or not to show check digits in the human-readable text. + */ + private boolean showCheckDigits = true; + + /** + * Optional start/stop delimiter to be shown in the human-readable text. + */ + private Character startStopDelimiter; + + private static char[] toControlChars(String s) { + StringBuilder buffer = new StringBuilder(); + char[] chars = s.toCharArray(); + for (char asciiCode : chars) { + buffer.append(CODE_93_CTRL[asciiCode]); + } + return buffer.toString().toCharArray(); + } + + private static int calculateCheckDigitC(int[] values, int length) { + int c = 0; + int weight = 1; + for (int i = length - 1; i >= 0; i--) { + c += values[i] * weight; + weight++; + if (weight == 21) { + weight = 1; + } + } + c = c % 47; + return c; + } + + private static int calculateCheckDigitK(int[] values, int length) { + int k = 0; + int weight = 1; + for (int i = length - 1; i >= 0; i--) { + k += values[i] * weight; + weight++; + if (weight == 16) { + weight = 1; + } + } + k = k % 47; + return k; + } + + private static String toPattern(int[] values) { + StringBuilder buffer = new StringBuilder("111141"); + for (int value : values) { + buffer.append(CODE_93_TABLE[value]); + } + buffer.append("1111411"); + return buffer.toString(); + } + + /** + * Returns whether or not this symbol shows check digits in the human-readable text. + * + * @return whether or not this symbol shows check digits in the human-readable text + */ + public boolean getShowCheckDigits() { + return showCheckDigits; + } + + /** + * Sets whether or not to show check digits in the human-readable text (defaults to true). + * + * @param showCheckDigits whether or not to show check digits in the human-readable text + */ + public void setShowCheckDigits(boolean showCheckDigits) { + this.showCheckDigits = showCheckDigits; + } + + /** + * Returns the optional start/stop delimiter to be shown in the human-readable text. + * + * @return the optional start/stop delimiter to be shown in the human-readable text + */ + public Character getStartStopDelimiter() { + return startStopDelimiter; + } + + /** + * Sets an optional start/stop delimiter to be shown in the human-readable text (defaults to null). + * + * @param startStopDelimiter an optional start/stop delimiter to be shown in the human-readable text + */ + public void setStartStopDelimiter(Character startStopDelimiter) { + this.startStopDelimiter = startStopDelimiter; + } + + @Override + public boolean encode() { + + char[] controlChars = toControlChars(content); + int l = controlChars.length; + + if (!content.matches("[\u0000-\u007F]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + int[] values = new int[controlChars.length + 2]; + for (int i = 0; i < l; i++) { + values[i] = positionOf(controlChars[i], CODE_93_LOOKUP); + } + + int c = calculateCheckDigitC(values, l); + values[l] = c; + l++; + + int k = calculateCheckDigitK(values, l); + values[l] = k; + + readable = new StringBuilder(content); + if (showCheckDigits) { + readable.append(CODE_93_LOOKUP[c]).append(CODE_93_LOOKUP[k]); + } + if (startStopDelimiter != null) { + readable = new StringBuilder(startStopDelimiter).append(readable).append(startStopDelimiter); + } + + encodeInfo.append("Check Digit C: ").append(c).append("\n"); + encodeInfo.append("Check Digit K: ").append(k).append("\n"); + pattern = new String[]{toPattern(values)}; + rowCount = 1; + rowHeight = new int[]{-1}; + + plotSymbol(); + + return true; + } + + @Override + protected int[] getCodewords() { + return getPatternAsCodewords(6); + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/CodeOne.java b/barcode/src/main/java/org/xbib/graphics/barcode/CodeOne.java new file mode 100755 index 0000000..78cb569 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/CodeOne.java @@ -0,0 +1,1946 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +/** + * Implements Code One. + * Code One is able to encode the ISO 8859-1 (Latin-1) character set or GS1 + * data. There are two types of Code One symbol - variable height symbols + * which are roughly square (versions A thought to H) and fixed-height + * versions (version S and T). Version S symbols can only encode numeric data. + * The width of version S and version T symbols is determined by the length + * of the input data. + */ +public class CodeOne extends Symbol { + private final int[] c40_shift = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3 + }; + + private final int[] c40_value = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, + 17, 18, 19, 20, 21, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 22, 23, 24, 25, 26, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 + }; + + private final int[] text_shift = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 3, 3, 3, 3 + }; + + private final int[] text_value = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, + 17, 18, 19, 20, 21, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 22, 23, 24, 25, 26, 0, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 27, 28, 29, 30, 31 + }; + + private final int[] c1_height = { + 16, 22, 28, 40, 52, 70, 104, 148 + }; + private final int[] c1_width = { + 18, 22, 32, 42, 54, 76, 98, 134 + }; + private final int[] c1_data_length = { + 10, 19, 44, 91, 182, 370, 732, 1480 + }; + private final int[] c1_ecc_length = { + 10, 16, 26, 44, 70, 140, 280, 560 + }; + private final int[] c1_blocks = { + 1, 1, 1, 1, 1, 2, 4, 8 + }; + private final int[] c1_data_blocks = { + 10, 19, 44, 91, 182, 185, 183, 185 + }; + private final int[] c1_ecc_blocks = { + 10, 16, 26, 44, 70, 70, 70, 70 + }; + private final int[] c1_grid_width = { + 4, 5, 7, 9, 12, 17, 22, 30 + }; + private final int[] c1_grid_height = { + 5, 7, 10, 15, 21, 30, 46, 68 + }; + private int[] data = new int[1500]; + + ; + private byte[] source; + private int[][] datagrid = new int[136][120]; + private boolean[][] outputGrid = new boolean[148][134]; + private Version preferredVersion = Version.NONE; + + /** + * Set symbol size by "version". Versions A to H are square symbols. + * This value may be ignored if the input data does not fit in the + * specified version. Version S and T are fixed height symbols. + * + * @param version Symbol version + */ + public void setPreferredVersion(Version version) { + preferredVersion = version; + } + + @Override + public boolean encode() { + int size = 1, i, j, data_blocks; + int row, col; + int sub_version = 0; + int codewords; + BigInteger elreg; + BigInteger codewordValue; + int[] ecc = new int[600]; + int[] stream = new int[2100]; + int block_width; + int length = content.length(); + ReedSolomon rs = new ReedSolomon(); + int data_length; + int data_cw, ecc_cw; + int[] sub_data = new int[190]; + StringBuilder bin; + + if (!content.matches("[\u0000-\u00FF]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + if (preferredVersion == Version.S) { + /* Version S */ + + encodeInfo.append("Version: S"); + + if (length > 18) { + errorMsg.append("Input data too long"); + return false; + } + + if (!(content.matches("[0-9]+?"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + sub_version = 3; + codewords = 12; + block_width = 6; /* Version S-30 */ + if (length <= 12) { + sub_version = 2; + codewords = 8; + block_width = 4; + } /* Version S-20 */ + if (length <= 6) { + sub_version = 1; + codewords = 4; + block_width = 2; + } /* Version S-10 */ + + elreg = new BigInteger(content); + + for (i = 0; i < codewords; i++) { + codewordValue = elreg.shiftRight(5 * i); + codewordValue = codewordValue.or(BigInteger.valueOf(32)); + data[codewords - i - 1] = codewordValue.intValue(); + } + + rs.init_gf(0x25); + rs.init_code(codewords, 1); + rs.encode(codewords, data); + + for (i = 0; i < codewords; i++) { + stream[i] = data[i]; + stream[i + codewords] = rs.getResult(codewords - i - 1); + } + + for (i = 0; i < 136; i++) { + for (j = 0; j < 120; j++) { + datagrid[i][j] = '0'; + } + } + + i = 0; + for (row = 0; row < 2; row++) { + for (col = 0; col < block_width; col++) { + if ((stream[i] & 0x10) != 0) { + datagrid[row * 2][col * 5] = '1'; + } + if ((stream[i] & 0x08) != 0) { + datagrid[row * 2][(col * 5) + 1] = '1'; + } + if ((stream[i] & 0x04) != 0) { + datagrid[row * 2][(col * 5) + 2] = '1'; + } + if ((stream[i] & 0x02) != 0) { + datagrid[(row * 2) + 1][col * 5] = '1'; + } + if ((stream[i] & 0x01) != 0) { + datagrid[(row * 2) + 1][(col * 5) + 1] = '1'; + } + if ((stream[i + 1] & 0x10) != 0) { + datagrid[row * 2][(col * 5) + 3] = '1'; + } + if ((stream[i + 1] & 0x08) != 0) { + datagrid[row * 2][(col * 5) + 4] = '1'; + } + if ((stream[i + 1] & 0x04) != 0) { + datagrid[(row * 2) + 1][(col * 5) + 2] = '1'; + } + if ((stream[i + 1] & 0x02) != 0) { + datagrid[(row * 2) + 1][(col * 5) + 3] = '1'; + } + if ((stream[i + 1] & 0x01) != 0) { + datagrid[(row * 2) + 1][(col * 5) + 4] = '1'; + } + i += 2; + } + } + + size = 9; + rowCount = 8; + symbolWidth = 10 * sub_version + 1; + } + + if (preferredVersion == Version.T) { + /* Version T */ + + encodeInfo.append("Version: T\n"); + + for (i = 0; i < 40; i++) { + data[i] = 0; + } + data_length = encodeAsCode1Data(); + + if (data_length == 0) { + errorMsg.append("Input data too long"); + return false; + } + + if (data_length > 38) { + errorMsg.append("Input data too long"); + return false; + } + + size = 10; + sub_version = 3; + data_cw = 38; + ecc_cw = 22; + block_width = 12; + if (data_length <= 24) { + sub_version = 2; + data_cw = 24; + ecc_cw = 16; + block_width = 8; + } + if (data_length <= 10) { + sub_version = 1; + data_cw = 10; + ecc_cw = 10; + block_width = 4; + } + + for (i = data_length; i < data_cw; i++) { + data[i] = 129; /* Pad */ + } + + /* Calculate error correction data */ + rs.init_gf(0x12d); + rs.init_code(ecc_cw, 1); + rs.encode(data_cw, data); + + /* "Stream" combines data and error correction data */ + for (i = 0; i < data_cw; i++) { + stream[i] = data[i]; + } + for (i = 0; i < ecc_cw; i++) { + stream[data_cw + i] = rs.getResult(ecc_cw - i - 1); + } + + for (i = 0; i < 136; i++) { + for (j = 0; j < 120; j++) { + datagrid[i][j] = '0'; + } + } + + i = 0; + for (row = 0; row < 5; row++) { + for (col = 0; col < block_width; col++) { + if ((stream[i] & 0x80) != 0) { + datagrid[row * 2][col * 4] = '1'; + } + if ((stream[i] & 0x40) != 0) { + datagrid[row * 2][(col * 4) + 1] = '1'; + } + if ((stream[i] & 0x20) != 0) { + datagrid[row * 2][(col * 4) + 2] = '1'; + } + if ((stream[i] & 0x10) != 0) { + datagrid[row * 2][(col * 4) + 3] = '1'; + } + if ((stream[i] & 0x08) != 0) { + datagrid[(row * 2) + 1][col * 4] = '1'; + } + if ((stream[i] & 0x04) != 0) { + datagrid[(row * 2) + 1][(col * 4) + 1] = '1'; + } + if ((stream[i] & 0x02) != 0) { + datagrid[(row * 2) + 1][(col * 4) + 2] = '1'; + } + if ((stream[i] & 0x01) != 0) { + datagrid[(row * 2) + 1][(col * 4) + 3] = '1'; + } + i++; + } + } + + rowCount = 16; + symbolWidth = (sub_version * 16) + 1; + } + + if ((preferredVersion != Version.S) && (preferredVersion != Version.T)) { + /* Version A to H */ + for (i = 0; i < 1500; i++) { + data[i] = 0; + } + data_length = encodeAsCode1Data(); + + if (data_length == 0) { + errorMsg.append("Input data too long"); + return false; + } + + for (i = 7; i >= 0; i--) { + if (c1_data_length[i] >= data_length) { + size = i + 1; + } + } + + if (getSize(preferredVersion) > size) { + size = getSize(preferredVersion); + } + + encodeInfo.append("Version: ").append((char) ((size - 1) + 'A')).append("\n"); + + encodeInfo.append("Codewords: "); + for (i = 0; i < data_length; i++) { + encodeInfo.append(Integer.toString(data[i])).append(" "); + } + encodeInfo.append("\n"); + + for (i = data_length; i < c1_data_length[size - 1]; i++) { + data[i] = 129; /* Pad */ + } + + /* Calculate error correction data */ + data_length = c1_data_length[size - 1]; + + data_blocks = c1_blocks[size - 1]; + + rs.init_gf(0x12d); + rs.init_code(c1_ecc_blocks[size - 1], 0); + for (i = 0; i < data_blocks; i++) { + for (j = 0; j < c1_data_blocks[size - 1]; j++) { + + sub_data[j] = data[j * data_blocks + i]; + } + rs.encode(c1_data_blocks[size - 1], sub_data); + for (j = 0; j < c1_ecc_blocks[size - 1]; j++) { + ecc[c1_ecc_length[size - 1] - (j * data_blocks + i) - 1] + = rs.getResult(j); + } + } + + encodeInfo.append("ECC Codeword Count: ").append(c1_ecc_length[size - 1]).append("\n"); + + /* "Stream" combines data and error correction data */ + for (i = 0; i < data_length; i++) { + stream[i] = data[i]; + } + for (i = 0; i < c1_ecc_length[size - 1]; i++) { + stream[data_length + i] = ecc[i]; + } + + for (i = 0; i < 136; i++) { + for (j = 0; j < 120; j++) { + datagrid[i][j] = '0'; + } + } + + i = 0; + for (row = 0; row < c1_grid_height[size - 1]; row++) { + for (col = 0; col < c1_grid_width[size - 1]; col++) { + if ((stream[i] & 0x80) != 0) { + datagrid[row * 2][col * 4] = '1'; + } + if ((stream[i] & 0x40) != 0) { + datagrid[row * 2][(col * 4) + 1] = '1'; + } + if ((stream[i] & 0x20) != 0) { + datagrid[row * 2][(col * 4) + 2] = '1'; + } + if ((stream[i] & 0x10) != 0) { + datagrid[row * 2][(col * 4) + 3] = '1'; + } + if ((stream[i] & 0x08) != 0) { + datagrid[(row * 2) + 1][col * 4] = '1'; + } + if ((stream[i] & 0x04) != 0) { + datagrid[(row * 2) + 1][(col * 4) + 1] = '1'; + } + if ((stream[i] & 0x02) != 0) { + datagrid[(row * 2) + 1][(col * 4) + 2] = '1'; + } + if ((stream[i] & 0x01) != 0) { + datagrid[(row * 2) + 1][(col * 4) + 3] = '1'; + } + i++; + } + } + + encodeInfo.append("Grid Size: ").append(c1_grid_width[size - 1]).append(" X ").append(c1_grid_height[size - 1]).append("\n"); + + rowCount = c1_height[size - 1]; + symbolWidth = c1_width[size - 1]; + } + + for (i = 0; i < 148; i++) { + for (j = 0; j < 134; j++) { + outputGrid[i][j] = false; + } + } + + switch (size) { + case 1: + /* Version A */ + plotCentralFinder(6, 3, 1); + plotVerticalBar(4, 6, 1); + plotVerticalBar(12, 5, 0); + setGridModule(5, 12); + plotSpigot(0); + plotSpigot(15); + plotDataBlock(0, 0, 5, 4, 0, 0); + plotDataBlock(0, 4, 5, 12, 0, 2); + plotDataBlock(5, 0, 5, 12, 6, 0); + plotDataBlock(5, 12, 5, 4, 6, 2); + break; + case 2: + /* Version B */ + plotCentralFinder(8, 4, 1); + plotVerticalBar(4, 8, 1); + plotVerticalBar(16, 7, 0); + setGridModule(7, 16); + plotSpigot(0); + plotSpigot(21); + plotDataBlock(0, 0, 7, 4, 0, 0); + plotDataBlock(0, 4, 7, 16, 0, 2); + plotDataBlock(7, 0, 7, 16, 8, 0); + plotDataBlock(7, 16, 7, 4, 8, 2); + break; + case 3: + /* Version C */ + plotCentralFinder(11, 4, 2); + plotVerticalBar(4, 11, 1); + plotVerticalBar(26, 13, 1); + plotVerticalBar(4, 10, 0); + plotVerticalBar(26, 10, 0); + plotSpigot(0); + plotSpigot(27); + plotDataBlock(0, 0, 10, 4, 0, 0); + plotDataBlock(0, 4, 10, 20, 0, 2); + plotDataBlock(0, 24, 10, 4, 0, 4); + plotDataBlock(10, 0, 10, 4, 8, 0); + plotDataBlock(10, 4, 10, 20, 8, 2); + plotDataBlock(10, 24, 10, 4, 8, 4); + break; + case 4: + /* Version D */ + plotCentralFinder(16, 5, 1); + plotVerticalBar(4, 16, 1); + plotVerticalBar(20, 16, 1); + plotVerticalBar(36, 16, 1); + plotVerticalBar(4, 15, 0); + plotVerticalBar(20, 15, 0); + plotVerticalBar(36, 15, 0); + plotSpigot(0); + plotSpigot(12); + plotSpigot(27); + plotSpigot(39); + plotDataBlock(0, 0, 15, 4, 0, 0); + plotDataBlock(0, 4, 15, 14, 0, 2); + plotDataBlock(0, 18, 15, 14, 0, 4); + plotDataBlock(0, 32, 15, 4, 0, 6); + plotDataBlock(15, 0, 15, 4, 10, 0); + plotDataBlock(15, 4, 15, 14, 10, 2); + plotDataBlock(15, 18, 15, 14, 10, 4); + plotDataBlock(15, 32, 15, 4, 10, 6); + break; + case 5: + /* Version E */ + plotCentralFinder(22, 5, 2); + plotVerticalBar(4, 22, 1); + plotVerticalBar(26, 24, 1); + plotVerticalBar(48, 22, 1); + plotVerticalBar(4, 21, 0); + plotVerticalBar(26, 21, 0); + plotVerticalBar(48, 21, 0); + plotSpigot(0); + plotSpigot(12); + plotSpigot(39); + plotSpigot(51); + plotDataBlock(0, 0, 21, 4, 0, 0); + plotDataBlock(0, 4, 21, 20, 0, 2); + plotDataBlock(0, 24, 21, 20, 0, 4); + plotDataBlock(0, 44, 21, 4, 0, 6); + plotDataBlock(21, 0, 21, 4, 10, 0); + plotDataBlock(21, 4, 21, 20, 10, 2); + plotDataBlock(21, 24, 21, 20, 10, 4); + plotDataBlock(21, 44, 21, 4, 10, 6); + break; + case 6: + /* Version F */ + plotCentralFinder(31, 5, 3); + plotVerticalBar(4, 31, 1); + plotVerticalBar(26, 35, 1); + plotVerticalBar(48, 31, 1); + plotVerticalBar(70, 35, 1); + plotVerticalBar(4, 30, 0); + plotVerticalBar(26, 30, 0); + plotVerticalBar(48, 30, 0); + plotVerticalBar(70, 30, 0); + plotSpigot(0); + plotSpigot(12); + plotSpigot(24); + plotSpigot(45); + plotSpigot(57); + plotSpigot(69); + plotDataBlock(0, 0, 30, 4, 0, 0); + plotDataBlock(0, 4, 30, 20, 0, 2); + plotDataBlock(0, 24, 30, 20, 0, 4); + plotDataBlock(0, 44, 30, 20, 0, 6); + plotDataBlock(0, 64, 30, 4, 0, 8); + plotDataBlock(30, 0, 30, 4, 10, 0); + plotDataBlock(30, 4, 30, 20, 10, 2); + plotDataBlock(30, 24, 30, 20, 10, 4); + plotDataBlock(30, 44, 30, 20, 10, 6); + plotDataBlock(30, 64, 30, 4, 10, 8); + break; + case 7: + /* Version G */ + plotCentralFinder(47, 6, 2); + plotVerticalBar(6, 47, 1); + plotVerticalBar(27, 49, 1); + plotVerticalBar(48, 47, 1); + plotVerticalBar(69, 49, 1); + plotVerticalBar(90, 47, 1); + plotVerticalBar(6, 46, 0); + plotVerticalBar(27, 46, 0); + plotVerticalBar(48, 46, 0); + plotVerticalBar(69, 46, 0); + plotVerticalBar(90, 46, 0); + plotSpigot(0); + plotSpigot(12); + plotSpigot(24); + plotSpigot(36); + plotSpigot(67); + plotSpigot(79); + plotSpigot(91); + plotSpigot(103); + plotDataBlock(0, 0, 46, 6, 0, 0); + plotDataBlock(0, 6, 46, 19, 0, 2); + plotDataBlock(0, 25, 46, 19, 0, 4); + plotDataBlock(0, 44, 46, 19, 0, 6); + plotDataBlock(0, 63, 46, 19, 0, 8); + plotDataBlock(0, 82, 46, 6, 0, 10); + plotDataBlock(46, 0, 46, 6, 12, 0); + plotDataBlock(46, 6, 46, 19, 12, 2); + plotDataBlock(46, 25, 46, 19, 12, 4); + plotDataBlock(46, 44, 46, 19, 12, 6); + plotDataBlock(46, 63, 46, 19, 12, 8); + plotDataBlock(46, 82, 46, 6, 12, 10); + break; + case 8: + /* Version H */ + plotCentralFinder(69, 6, 3); + plotVerticalBar(6, 69, 1); + plotVerticalBar(26, 73, 1); + plotVerticalBar(46, 69, 1); + plotVerticalBar(66, 73, 1); + plotVerticalBar(86, 69, 1); + plotVerticalBar(106, 73, 1); + plotVerticalBar(126, 69, 1); + plotVerticalBar(6, 68, 0); + plotVerticalBar(26, 68, 0); + plotVerticalBar(46, 68, 0); + plotVerticalBar(66, 68, 0); + plotVerticalBar(86, 68, 0); + plotVerticalBar(106, 68, 0); + plotVerticalBar(126, 68, 0); + plotSpigot(0); + plotSpigot(12); + plotSpigot(24); + plotSpigot(36); + plotSpigot(48); + plotSpigot(60); + plotSpigot(87); + plotSpigot(99); + plotSpigot(111); + plotSpigot(123); + plotSpigot(135); + plotSpigot(147); + plotDataBlock(0, 0, 68, 6, 0, 0); + plotDataBlock(0, 6, 68, 18, 0, 2); + plotDataBlock(0, 24, 68, 18, 0, 4); + plotDataBlock(0, 42, 68, 18, 0, 6); + plotDataBlock(0, 60, 68, 18, 0, 8); + plotDataBlock(0, 78, 68, 18, 0, 10); + plotDataBlock(0, 96, 68, 18, 0, 12); + plotDataBlock(0, 114, 68, 6, 0, 14); + plotDataBlock(68, 0, 68, 6, 12, 0); + plotDataBlock(68, 6, 68, 18, 12, 2); + plotDataBlock(68, 24, 68, 18, 12, 4); + plotDataBlock(68, 42, 68, 18, 12, 6); + plotDataBlock(68, 60, 68, 18, 12, 8); + plotDataBlock(68, 78, 68, 18, 12, 10); + plotDataBlock(68, 96, 68, 18, 12, 12); + plotDataBlock(68, 114, 68, 6, 12, 14); + break; + case 9: + /* Version S */ + plotHorizontalBar(5, 1); + plotHorizontalBar(7, 1); + setGridModule(6, 0); + setGridModule(6, symbolWidth - 1); + resetGridModule(7, 1); + resetGridModule(7, symbolWidth - 2); + switch (sub_version) { + case 1: + /* Version S-10 */ + setGridModule(0, 5); + plotDataBlock(0, 0, 4, 5, 0, 0); + plotDataBlock(0, 5, 4, 5, 0, 1); + break; + case 2: + /* Version S-20 */ + setGridModule(0, 10); + setGridModule(4, 10); + plotDataBlock(0, 0, 4, 10, 0, 0); + plotDataBlock(0, 10, 4, 10, 0, 1); + break; + case 3: + /* Version S-30 */ + setGridModule(0, 15); + setGridModule(4, 15); + setGridModule(6, 15); + plotDataBlock(0, 0, 4, 15, 0, 0); + plotDataBlock(0, 15, 4, 15, 0, 1); + break; + } + break; + case 10: + /* Version T */ + plotHorizontalBar(11, 1); + plotHorizontalBar(13, 1); + plotHorizontalBar(15, 1); + setGridModule(12, 0); + setGridModule(12, symbolWidth - 1); + setGridModule(14, 0); + setGridModule(14, symbolWidth - 1); + resetGridModule(13, 1); + resetGridModule(13, symbolWidth - 2); + resetGridModule(15, 1); + resetGridModule(15, symbolWidth - 2); + switch (sub_version) { + case 1: + /* Version T-16 */ + setGridModule(0, 8); + setGridModule(10, 8); + plotDataBlock(0, 0, 10, 8, 0, 0); + plotDataBlock(0, 8, 10, 8, 0, 1); + break; + case 2: + /* Version T-32 */ + setGridModule(0, 16); + setGridModule(10, 16); + setGridModule(12, 16); + plotDataBlock(0, 0, 10, 16, 0, 0); + plotDataBlock(0, 16, 10, 16, 0, 1); + break; + case 3: + /* Verion T-48 */ + setGridModule(0, 24); + setGridModule(10, 24); + setGridModule(12, 24); + setGridModule(14, 24); + plotDataBlock(0, 0, 10, 24, 0, 0); + plotDataBlock(0, 24, 10, 24, 0, 1); + break; + } + break; + } + + readable = new StringBuilder(); + pattern = new String[rowCount]; + rowHeight = new int[rowCount]; + for (i = 0; i < rowCount; i++) { + bin = new StringBuilder(); + for (j = 0; j < symbolWidth; j++) { + if (outputGrid[i][j]) { + bin.append("1"); + } else { + bin.append("0"); + } + } + pattern[i] = bin2pat(bin.toString()); + rowHeight[i] = 1; + } + plotSymbol(); + return true; + } + + private int encodeAsCode1Data() { + c1Mode current_mode, next_mode; + boolean latch; + boolean done; + int sourcePoint, targetPoint, i, j; + int c40_p; + int text_p; + int edi_p; + int byte_start = 0; + int[] c40_buffer = new int[6]; + int[] text_buffer = new int[6]; + int[] edi_buffer = new int[6]; + StringBuilder decimal_binary = new StringBuilder(); + int length = content.length(); + int shift_set, value; + int data_left, decimal_count; + int sub_value; + int bits_left_in_byte, target_count; + boolean isTwoDigits; + + try { + source = content.getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Invalid character in input data"); + return 0; + } + + sourcePoint = 0; + targetPoint = 0; + c40_p = 0; + text_p = 0; + edi_p = 0; + + if (inputDataType == DataType.GS1) { + data[targetPoint] = 232; + targetPoint++; + } /* FNC1 */ + + /* Step A */ + current_mode = c1Mode.C1_ASCII; + next_mode = c1Mode.C1_ASCII; + + do { + if (current_mode != next_mode) { + /* Change mode */ + switch (next_mode) { + case C1_C40: + data[targetPoint] = 230; + targetPoint++; + break; + case C1_TEXT: + data[targetPoint] = 239; + targetPoint++; + break; + case C1_EDI: + data[targetPoint] = 238; + targetPoint++; + break; + case C1_BYTE: + data[targetPoint] = 231; + targetPoint++; + break; + } + } + + if ((current_mode != c1Mode.C1_BYTE) && (next_mode == c1Mode.C1_BYTE)) { + byte_start = targetPoint; + } + current_mode = next_mode; + + if (current_mode == c1Mode.C1_ASCII) { /* Step B - ASCII encodation */ + next_mode = c1Mode.C1_ASCII; + + if ((length - sourcePoint) >= 21) { /* Step B1 */ + j = 0; + + for (i = 0; i < 21; i++) { + if ((source[sourcePoint + i] >= '0') && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if (j == 21) { + next_mode = c1Mode.C1_DECIMAL; + decimal_binary.append("1111"); + } + } + + if ((next_mode == c1Mode.C1_ASCII) && ((length - sourcePoint) >= 13)) { /* Step B2 */ + j = 0; + + for (i = 0; i < 13; i++) { + if ((source[sourcePoint + i] >= '0') && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if (j == 13) { + latch = false; + for (i = sourcePoint + 13; i < length; i++) { + if (!((source[i] >= '0') && + (source[i] <= '9'))) { + latch = true; + } + } + + if (!(latch)) { + next_mode = c1Mode.C1_DECIMAL; + decimal_binary.append("1111"); + } + } + } + + if (next_mode == c1Mode.C1_ASCII) { /* Step B3 */ + isTwoDigits = false; + if ((sourcePoint + 1) != length) { + if ((source[sourcePoint] >= '0') && (source[sourcePoint] <= '9')) { + if ((source[sourcePoint + 1] >= '0') && (source[sourcePoint + 1] <= '9')) { + // remaining data consists of two numeric digits + data[targetPoint] = (10 * (source[sourcePoint] - '0')) + + (source[sourcePoint + 1] - '0') + 130; + targetPoint++; + sourcePoint += 2; + isTwoDigits = true; + } + } + } + + if (!(isTwoDigits)) { + if ((inputDataType == DataType.GS1) && (source[sourcePoint] == '[')) { + if ((length - sourcePoint) >= 15) { /* Step B4 */ + j = 0; + + for (i = 0; i < 15; i++) { + if ((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if (j == 15) { + data[targetPoint] = 236; /* FNC1 and change to Decimal */ + targetPoint++; + sourcePoint++; + next_mode = c1Mode.C1_DECIMAL; + } + } + + if ((length - sourcePoint) >= 7) { /* Step B5 */ + j = 0; + + for (i = 0; i < 7; i++) { + if ((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if (j == 7) { + latch = false; + for (i = sourcePoint + 7; i < length; i++) { + if (!((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9'))) { + latch = true; + } + } + + if (!(latch)) { + data[targetPoint] = 236; /* FNC1 and change to Decimal */ + targetPoint++; + sourcePoint++; + next_mode = c1Mode.C1_DECIMAL; + } + } + } + } + + if (next_mode == c1Mode.C1_ASCII) { + + /* Step B6 */ + next_mode = lookAheadTest(length, sourcePoint, current_mode); + + if (next_mode == c1Mode.C1_ASCII) { + if (source[sourcePoint] > 127) { + /* Step B7 */ + data[targetPoint] = 235; + targetPoint++; /* FNC4 */ + data[targetPoint] = (source[sourcePoint] - 128) + 1; + targetPoint++; + sourcePoint++; + } else { + /* Step B8 */ + if ((inputDataType == DataType.GS1) && (source[sourcePoint] == '[')) { + data[targetPoint] = 232; + targetPoint++; + sourcePoint++; /* FNC1 */ + } else { + data[targetPoint] = source[sourcePoint] + 1; + targetPoint++; + sourcePoint++; + } + } + } + } + } + } + } + + if (current_mode == c1Mode.C1_C40) { /* Step C - C40 encodation */ + done = false; + next_mode = c1Mode.C1_C40; + if (c40_p == 0) { + if ((length - sourcePoint) >= 12) { + j = 0; + + for (i = 0; i < 12; i++) { + if ((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if (j == 12) { + next_mode = c1Mode.C1_ASCII; + done = true; + } + } + + if ((length - sourcePoint) >= 8) { + j = 0; + + for (i = 0; i < 8; i++) { + if ((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if ((length - sourcePoint) == 8) { + latch = true; + } else { + latch = true; + for (j = sourcePoint + 8; j < length; j++) { + if ((source[j] <= '0') || (source[j] >= '9')) { + latch = false; + } + } + } + + if ((j == 8) && latch) { + next_mode = c1Mode.C1_ASCII; + done = true; + } + } + + if (!(done)) { + next_mode = lookAheadTest(length, sourcePoint, current_mode); + } + } + + if (next_mode != c1Mode.C1_C40) { + data[targetPoint] = 255; + targetPoint++; /* Unlatch */ + } else { + if (source[sourcePoint] > 127) { + c40_buffer[c40_p] = 1; + c40_p++; + c40_buffer[c40_p] = 30; + c40_p++; /* Upper Shift */ + shift_set = c40_shift[source[sourcePoint] - 128]; + value = c40_value[source[sourcePoint] - 128]; + } else { + shift_set = c40_shift[source[sourcePoint]]; + value = c40_value[source[sourcePoint]]; + } + + if ((inputDataType == DataType.GS1) && (source[sourcePoint] == '[')) { + shift_set = 2; + value = 27; /* FNC1 */ + } + + if (shift_set != 0) { + c40_buffer[c40_p] = shift_set - 1; + c40_p++; + } + c40_buffer[c40_p] = value; + c40_p++; + + if (c40_p >= 3) { + int iv; + + iv = (1600 * c40_buffer[0]) + (40 * c40_buffer[1]) + + (c40_buffer[2]) + 1; + data[targetPoint] = iv / 256; + targetPoint++; + data[targetPoint] = iv % 256; + targetPoint++; + + c40_buffer[0] = c40_buffer[3]; + c40_buffer[1] = c40_buffer[4]; + c40_buffer[2] = c40_buffer[5]; + c40_buffer[3] = 0; + c40_buffer[4] = 0; + c40_buffer[5] = 0; + c40_p -= 3; + } + sourcePoint++; + } + } + + if (current_mode == c1Mode.C1_TEXT) { /* Step D - Text encodation */ + done = false; + next_mode = c1Mode.C1_TEXT; + if (text_p == 0) { + if ((length - sourcePoint) >= 12) { + j = 0; + + for (i = 0; i < 12; i++) { + if ((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if (j == 12) { + next_mode = c1Mode.C1_ASCII; + done = true; + } + } + + if ((length - sourcePoint) >= 8) { + j = 0; + + for (i = 0; i < 8; i++) { + if ((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if ((length - sourcePoint) == 8) { + latch = true; + } else { + latch = true; + for (j = sourcePoint + 8; j < length; j++) { + if ((source[j] <= '0') || (source[j] >= '9')) { + latch = false; + } + } + } + + if ((j == 8) && latch) { + next_mode = c1Mode.C1_ASCII; + done = true; + } + } + + if (!(done)) { + next_mode = lookAheadTest(length, sourcePoint, current_mode); + } + } + + if (next_mode != c1Mode.C1_TEXT) { + data[targetPoint] = 255; + targetPoint++; /* Unlatch */ + } else { + if (source[sourcePoint] > 127) { + text_buffer[text_p] = 1; + text_p++; + text_buffer[text_p] = 30; + text_p++; /* Upper Shift */ + shift_set = text_shift[source[sourcePoint] - 128]; + value = text_value[source[sourcePoint] - 128]; + } else { + shift_set = text_shift[source[sourcePoint]]; + value = text_value[source[sourcePoint]]; + } + + if ((inputDataType == DataType.GS1) && (source[sourcePoint] == '[')) { + shift_set = 2; + value = 27; /* FNC1 */ + } + + if (shift_set != 0) { + text_buffer[text_p] = shift_set - 1; + text_p++; + } + text_buffer[text_p] = value; + text_p++; + + if (text_p >= 3) { + int iv; + + iv = (1600 * text_buffer[0]) + (40 * text_buffer[1]) + + (text_buffer[2]) + 1; + data[targetPoint] = iv / 256; + targetPoint++; + data[targetPoint] = iv % 256; + targetPoint++; + + text_buffer[0] = text_buffer[3]; + text_buffer[1] = text_buffer[4]; + text_buffer[2] = text_buffer[5]; + text_buffer[3] = 0; + text_buffer[4] = 0; + text_buffer[5] = 0; + text_p -= 3; + } + sourcePoint++; + } + } + + if (current_mode == c1Mode.C1_EDI) { /* Step E - EDI Encodation */ + + value = 0; + next_mode = c1Mode.C1_EDI; + if (edi_p == 0) { + if ((length - sourcePoint) >= 12) { + j = 0; + + for (i = 0; i < 12; i++) { + if ((source[sourcePoint + i] >= '0') + && (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if (j == 12) { + next_mode = c1Mode.C1_ASCII; + } + } + + if ((length - sourcePoint) >= 8) { + j = 0; + + for (i = 0; i < 8; i++) { + if ((source[sourcePoint + i] >= '0') && + (source[sourcePoint + i] <= '9')) { + j++; + } + } + + if ((length - sourcePoint) == 8) { + latch = true; + } else { + latch = true; + for (j = sourcePoint + 8; j < length; j++) { + if ((source[j] <= '0') || (source[j] >= '9')) { + latch = false; + } + } + } + + if ((j == 8) && latch) { + next_mode = c1Mode.C1_ASCII; + } + } + + if (!((isEdiEncodable(source[sourcePoint]) + && isEdiEncodable(source[sourcePoint + 1])) + && isEdiEncodable(source[sourcePoint + 2]))) { + next_mode = c1Mode.C1_ASCII; + } + } + + if (next_mode != c1Mode.C1_EDI) { + data[targetPoint] = 255; + targetPoint++; /* Unlatch */ + } else { + if (source[sourcePoint] == 13) { + value = 0; + } + if (source[sourcePoint] == '*') { + value = 1; + } + if (source[sourcePoint] == '>') { + value = 2; + } + if (source[sourcePoint] == ' ') { + value = 3; + } + if ((source[sourcePoint] >= '0') && (source[sourcePoint] <= '9')) { + value = source[sourcePoint] - '0' + 4; + } + if ((source[sourcePoint] >= 'A') && (source[sourcePoint] <= 'Z')) { + value = source[sourcePoint] - 'A' + 14; + } + + edi_buffer[edi_p] = value; + edi_p++; + + if (edi_p >= 3) { + int iv; + + iv = (1600 * edi_buffer[0]) + (40 * edi_buffer[1]) + + (edi_buffer[2]) + 1; + data[targetPoint] = iv / 256; + targetPoint++; + data[targetPoint] = iv % 256; + targetPoint++; + + edi_buffer[0] = edi_buffer[3]; + edi_buffer[1] = edi_buffer[4]; + edi_buffer[2] = edi_buffer[5]; + edi_buffer[3] = 0; + edi_buffer[4] = 0; + edi_buffer[5] = 0; + edi_p -= 3; + } + sourcePoint++; + } + } + + if (current_mode == c1Mode.C1_DECIMAL) { /* Step F - Decimal encodation */ + + next_mode = c1Mode.C1_DECIMAL; + + data_left = length - sourcePoint; + decimal_count = 0; + + if (data_left >= 1) { + if ((source[sourcePoint] >= '0') && (source[sourcePoint] <= '9')) { + decimal_count = 1; + } + } + if (data_left >= 2) { + if ((decimal_count == 1) && ((source[sourcePoint + 1] >= '0') + && (source[sourcePoint + 1] <= '9'))) { + decimal_count = 2; + } + } + if (data_left >= 3) { + if ((decimal_count == 2) && ((source[sourcePoint + 2] >= '0') + && (source[sourcePoint + 2] <= '9'))) { + decimal_count = 3; + } + } + + if (decimal_count != 3) { + + /* Finish Decimal mode and go back to ASCII */ + + decimal_binary.append("111111"); /* Unlatch */ + + target_count = 3; + if (decimal_binary.length() <= 16) { + target_count = 2; + } + if (decimal_binary.length() <= 8) { + target_count = 1; + } + bits_left_in_byte = (8 * target_count) - decimal_binary.length(); + if (bits_left_in_byte == 8) { + bits_left_in_byte = 0; + } + + if (bits_left_in_byte == 2) { + decimal_binary.append("01"); + } + + if ((bits_left_in_byte == 4) || (bits_left_in_byte == 6)) { + if (decimal_count >= 1) { + sub_value = source[sourcePoint] - '0' + 1; + + for (i = 0x08; i > 0; i = i >> 1) { + if ((sub_value & i) != 0) { + decimal_binary.append("1"); + } else { + decimal_binary.append("0"); + } + } + sourcePoint++; + } else { + decimal_binary.append("1111"); + } + } + + if (bits_left_in_byte == 6) { + decimal_binary.append("01"); + } + + /* Binary buffer is full - transfer to data */ + if (target_count >= 1) { + for (i = 0; i < 8; i++) { + if (decimal_binary.charAt(i) == '1') { + data[targetPoint] += 128 >> i; + } + } + targetPoint++; + } + if (target_count >= 2) { + for (i = 0; i < 8; i++) { + if (decimal_binary.charAt(8 + i) == '1') { + data[targetPoint] += 128 >> i; + } + + } + targetPoint++; + } + if (target_count == 3) { + for (i = 0; i < 8; i++) { + if (decimal_binary.charAt(16 + i) == '1') { + data[targetPoint] += 128 >> i; + } + + } + targetPoint++; + } + + next_mode = c1Mode.C1_ASCII; + } else { + /* There are three digits - convert the value to binary */ + value = (100 * (source[sourcePoint] - '0')) + + (10 * (source[sourcePoint + 1] - '0')) + + (source[sourcePoint + 2] - '0') + 1; + + for (i = 0x200; i > 0; i = i >> 1) { + if ((value & i) != 0) { + decimal_binary.append("1"); + } else { + decimal_binary.append("0"); + } + } + sourcePoint += 3; + } + + if (decimal_binary.length() >= 24) { + /* Binary buffer is full - transfer to data */ + for (i = 0; i < 8; i++) { + if (decimal_binary.charAt(i) == '1') { + data[targetPoint] += 128 >> i; + } + + if (decimal_binary.charAt(8 + i) == '1') { + data[targetPoint + 1] += 128 >> i; + } + + if (decimal_binary.charAt(16 + i) == '1') { + data[targetPoint + 2] += 128 >> i; + } + + } + targetPoint += 3; + + if (decimal_binary.length() > 24) { + decimal_binary = new StringBuilder(decimal_binary.substring(24)); + } + } + } + + if (current_mode == c1Mode.C1_BYTE) { + next_mode = c1Mode.C1_BYTE; + + if ((inputDataType == DataType.GS1) && (source[sourcePoint] == '[')) { + next_mode = c1Mode.C1_ASCII; + } else { + if (source[sourcePoint] <= 127) { + next_mode = lookAheadTest(length, sourcePoint, current_mode); + } + } + + if (next_mode != c1Mode.C1_BYTE) { + /* Insert byte field length */ + if ((targetPoint - byte_start) <= 249) { + for (i = targetPoint; i >= byte_start; i--) { + data[i + 1] = data[i]; + } + data[byte_start] = (targetPoint - byte_start); + targetPoint++; + } else { + for (i = targetPoint; i >= byte_start; i--) { + data[i + 2] = data[i]; + } + data[byte_start] = 249 + ((targetPoint - byte_start) / 250); + data[byte_start + 1] = ((targetPoint - byte_start) % 250); + targetPoint += 2; + } + } else { + data[targetPoint] = source[sourcePoint]; + targetPoint++; + sourcePoint++; + } + } + + if (targetPoint > 1480) { + /* Data is too large for symbol */ + errorMsg.append("Input data too long"); + return 0; + } + } while (sourcePoint < length); + + /* Empty buffers */ + if (c40_p == 2) { + int iv; + + c40_buffer[2] = 1; + iv = (1600 * c40_buffer[0]) + (40 * c40_buffer[1]) + + (c40_buffer[2]) + 1; + data[targetPoint] = iv / 256; + targetPoint++; + data[targetPoint] = iv % 256; + targetPoint++; + data[targetPoint] = 255; + targetPoint++; /* Unlatch */ + } + if (c40_p == 1) { + int iv; + + c40_buffer[1] = 1; + c40_buffer[2] = 31; /* Pad */ + iv = (1600 * c40_buffer[0]) + (40 * c40_buffer[1]) + + (c40_buffer[2]) + 1; + data[targetPoint] = iv / 256; + targetPoint++; + data[targetPoint] = iv % 256; + targetPoint++; + data[targetPoint] = 255; + targetPoint++; /* Unlatch */ + } + if (text_p == 2) { + int iv; + + text_buffer[2] = 1; + iv = (1600 * text_buffer[0]) + (40 * text_buffer[1]) + + (text_buffer[2]) + 1; + data[targetPoint] = iv / 256; + targetPoint++; + data[targetPoint] = iv % 256; + targetPoint++; + data[targetPoint] = 255; + targetPoint++; /* Unlatch */ + } + if (text_p == 1) { + int iv; + + text_buffer[1] = 1; + text_buffer[2] = 31; /* Pad */ + iv = (1600 * text_buffer[0]) + (40 * text_buffer[1]) + + (text_buffer[2]) + 1; + data[targetPoint] = iv / 256; + targetPoint++; + data[targetPoint] = iv % 256; + targetPoint++; + data[targetPoint] = 255; + targetPoint++; /* Unlatch */ + } + + if (current_mode == c1Mode.C1_DECIMAL) { + /* Finish Decimal mode and go back to ASCII */ + + decimal_binary.append("111111"); /* Unlatch */ + + target_count = 3; + if (decimal_binary.length() <= 16) { + target_count = 2; + } + if (decimal_binary.length() <= 8) { + target_count = 1; + } + bits_left_in_byte = (8 * target_count) - decimal_binary.length(); + if (bits_left_in_byte == 8) { + bits_left_in_byte = 0; + } + + if (bits_left_in_byte == 2) { + decimal_binary.append("01"); + } + + if ((bits_left_in_byte == 4) || (bits_left_in_byte == 6)) { + decimal_binary.append("1111"); + } + + if (bits_left_in_byte == 6) { + decimal_binary.append("01"); + } + + /* Binary buffer is full - transfer to data */ + if (target_count >= 1) { + for (i = 0; i < 8; i++) { + if (decimal_binary.charAt(i) == '1') { + data[targetPoint] += 128 >> i; + } + } + targetPoint++; + } + if (target_count >= 2) { + for (i = 0; i < 8; i++) { + if (decimal_binary.charAt(8 + i) == '1') { + data[targetPoint] += 128 >> i; + } + } + targetPoint++; + } + if (target_count == 3) { + for (i = 0; i < 8; i++) { + if (decimal_binary.charAt(16 + i) == '1') { + data[targetPoint] += 128 >> i; + } + } + targetPoint++; + } + } + + if (current_mode == c1Mode.C1_BYTE) { + /* Insert byte field length */ + if ((targetPoint - byte_start) <= 249) { + for (i = targetPoint; i >= byte_start; i--) { + data[i + 1] = data[i]; + } + data[byte_start] = (targetPoint - byte_start); + targetPoint++; + } else { + for (i = targetPoint; i >= byte_start; i--) { + data[i + 2] = data[i]; + } + data[byte_start] = 249 + ((targetPoint - byte_start) / 250); + data[byte_start + 1] = ((targetPoint - byte_start) % 250); + targetPoint += 2; + } + } + + /* Re-check length of data */ + if (targetPoint > 1480) { + /* Data is too large for symbol */ + errorMsg.append("Input data too long"); + return 0; + } + + return targetPoint; + } + + private c1Mode lookAheadTest(int sourcelen, int position, + c1Mode current_mode) { + double ascii_count, c40_count, text_count, edi_count, byte_count; + int reduced_char; + int done, best_count, sp; + c1Mode best_scheme; + + /* Step J */ + if (current_mode == c1Mode.C1_ASCII) { + ascii_count = 0.0; + c40_count = 1.0; + text_count = 1.0; + edi_count = 1.0; + byte_count = 2.0; + } else { + ascii_count = 1.0; + c40_count = 2.0; + text_count = 2.0; + edi_count = 2.0; + byte_count = 3.0; + } + + switch (current_mode) { + case C1_C40: + c40_count = 0.0; + break; + case C1_TEXT: + text_count = 0.0; + break; + case C1_BYTE: + byte_count = 0.0; + break; + case C1_EDI: + edi_count = 0.0; + break; + } + + for (sp = position; + (sp < sourcelen) && (sp <= (position + 8)); sp++) { + + if (source[sp] <= 127) { + reduced_char = source[sp]; + } else { + reduced_char = source[sp] - 127; + } + + /* Step L */ + if ((source[sp] >= '0') && (source[sp] <= '9')) { + ascii_count += 0.5; + } else { + ascii_count = roundUpToNextInteger(ascii_count); + if (source[sp] > 127) { + ascii_count += 2.0; + } else { + ascii_count += 1.0; + } + } + + /* Step M */ + done = 0; + if (reduced_char == ' ') { + c40_count += (2.0 / 3.0); + done = 1; + } + if ((reduced_char >= '0') && (reduced_char <= '9')) { + c40_count += (2.0 / 3.0); + done = 1; + } + if ((reduced_char >= 'A') && (reduced_char <= 'Z')) { + c40_count += (2.0 / 3.0); + done = 1; + } + if (source[sp] > 127) { + c40_count += (4.0 / 3.0); + } + if (done == 0) { + c40_count += (4.0 / 3.0); + } + + /* Step N */ + done = 0; + if (reduced_char == ' ') { + text_count += (2.0 / 3.0); + done = 1; + } + if ((reduced_char >= '0') && (reduced_char <= '9')) { + text_count += (2.0 / 3.0); + done = 1; + } + if ((reduced_char >= 'a') && (reduced_char <= 'z')) { + text_count += (2.0 / 3.0); + done = 1; + } + if (source[sp] > 127) { + text_count += (4.0 / 3.0); + } + if (done == 0) { + text_count += (4.0 / 3.0); + } + + /* Step O */ + done = 0; + if (source[sp] == 13) { + edi_count += (2.0 / 3.0); + done = 1; + } + if (source[sp] == '*') { + edi_count += (2.0 / 3.0); + done = 1; + } + if (source[sp] == '>') { + edi_count += (2.0 / 3.0); + done = 1; + } + if (source[sp] == ' ') { + edi_count += (2.0 / 3.0); + done = 1; + } + if ((source[sp] >= '0') && (source[sp] <= '9')) { + edi_count += (2.0 / 3.0); + done = 1; + } + if ((source[sp] >= 'A') && (source[sp] <= 'Z')) { + edi_count += (2.0 / 3.0); + done = 1; + } + if (source[sp] > 127) { + edi_count += (13.0 / 3.0); + } else { + if (done == 0) { + edi_count += (10.0 / 3.0); + } + } + + /* Step P */ + if ((inputDataType == DataType.GS1) && (source[sp] == '[')) { + byte_count += 3.0; + } else { + byte_count += 1.0; + } + + } + + ascii_count = roundUpToNextInteger(ascii_count); + c40_count = roundUpToNextInteger(c40_count); + text_count = roundUpToNextInteger(text_count); + edi_count = roundUpToNextInteger(edi_count); + byte_count = roundUpToNextInteger(byte_count); + best_scheme = c1Mode.C1_ASCII; + + if (sp == sourcelen) { + /* Step K */ + best_count = (int) edi_count; + + if (text_count <= best_count) { + best_count = (int) text_count; + best_scheme = c1Mode.C1_TEXT; + } + + if (c40_count <= best_count) { + best_count = (int) c40_count; + best_scheme = c1Mode.C1_C40; + } + + if (ascii_count <= best_count) { + best_count = (int) ascii_count; + best_scheme = c1Mode.C1_ASCII; + } + + if (byte_count <= best_count) { + best_scheme = c1Mode.C1_BYTE; + } + } else { + /* Step Q */ + + if (((edi_count + 1.0 <= ascii_count) + && (edi_count + 1.0 <= c40_count)) + && ((edi_count + 1.0 <= byte_count) + && (edi_count + 1.0 <= text_count))) { + best_scheme = c1Mode.C1_EDI; + } + + if ((c40_count + 1.0 <= ascii_count) + && (c40_count + 1.0 <= text_count)) { + + if (c40_count < edi_count) { + best_scheme = c1Mode.C1_C40; + } else { + if (c40_count == edi_count) { + if (preferEdi(sourcelen, position)) { + best_scheme = c1Mode.C1_EDI; + } else { + best_scheme = c1Mode.C1_C40; + } + } + } + } + + if (((text_count + 1.0 <= ascii_count) + && (text_count + 1.0 <= c40_count)) + && ((text_count + 1.0 <= byte_count) + && (text_count + 1.0 <= edi_count))) { + best_scheme = c1Mode.C1_TEXT; + } + + if (((ascii_count + 1.0 <= byte_count) + && (ascii_count + 1.0 <= c40_count)) + && ((ascii_count + 1.0 <= text_count) + && (ascii_count + 1.0 <= edi_count))) { + best_scheme = c1Mode.C1_ASCII; + } + + if (((byte_count + 1.0 <= ascii_count) + && (byte_count + 1.0 <= c40_count)) + && ((byte_count + 1.0 <= text_count) + && (byte_count + 1.0 <= edi_count))) { + best_scheme = c1Mode.C1_BYTE; + } + } + return best_scheme; + } + + private double roundUpToNextInteger(double input) { + double fraction, output; + + fraction = input - (int) input; + if (fraction > 0.01) { + output = (input - fraction) + 1.0; + } else { + output = input; + } + + return output; + } + + private boolean preferEdi(int sourcelen, int position) { + int i; + + for (i = position; isEdiEncodable(source[position + i]) + && ((position + i) < sourcelen); i++) { + ; + } + + if ((position + i) == sourcelen) { + /* Reached end of input */ + return false; + } + + if (source[position + i - 1] == 13) { + return true; + } + if (source[position + i - 1] == '*') { + return true; + } + if (source[position + i - 1] == '>') { + return true; + } + + return false; + } + + private boolean isEdiEncodable(int input) { + boolean result = false; + + if (input == 13) { + result = true; + } + if (input == '*') { + result = true; + } + if (input == '>') { + result = true; + } + if (input == ' ') { + result = true; + } + if ((input >= '0') && (input <= '9')) { + result = true; + } + if ((input >= 'A') && (input <= 'Z')) { + result = true; + } + + return result; + } + + private void plotCentralFinder(int start_row, int row_count, int full_rows) { + int i; + + for (i = 0; i < row_count; i++) { + if (i < full_rows) { + plotHorizontalBar(start_row + (i * 2), 1); + } else { + plotHorizontalBar(start_row + (i * 2), 0); + if (i != row_count - 1) { + setGridModule(start_row + (i * 2) + 1, 1); + setGridModule(start_row + (i * 2) + 1, symbolWidth - 2); + } + } + } + } + + private void plotHorizontalBar(int row_no, int full) { + int i; + + if (full != 0) { + for (i = 0; i < symbolWidth; i++) { + setGridModule(row_no, i); + } + } else { + for (i = 1; i < symbolWidth - 1; i++) { + setGridModule(row_no, i); + } + } + } + + private void plotVerticalBar(int column, int height, int top) { + int i; + + if (top != 0) { + for (i = 0; i < height; i++) { + setGridModule(i, column); + } + } else { + for (i = 0; i < height; i++) { + setGridModule(rowCount - i - 1, column); + } + } + } + + private void plotSpigot(int row_no) { + int i; + + for (i = symbolWidth - 1; i > 0; i--) { + if (outputGrid[row_no][i - 1]) { + setGridModule(row_no, i); + } + } + } + + private void plotDataBlock(int start_row, int start_col, int height, + int width, int row_offset, int col_offset) { + int i, j; + + for (i = start_row; i < (start_row + height); i++) { + for (j = start_col; j < (start_col + width); j++) { + if (datagrid[i][j] == '1') { + setGridModule(i + row_offset, j + col_offset); + } + } + } + } + + private void setGridModule(int row, int column) { + outputGrid[row][column] = true; + } + + private void resetGridModule(int row, int column) { + outputGrid[row][column] = false; + } + + private int getSize(Version version) { + int size = 0; + + switch (version) { + case A: + size = 1; + break; + case B: + size = 2; + break; + case C: + size = 3; + break; + case D: + size = 4; + break; + case E: + size = 5; + break; + case F: + size = 6; + break; + case G: + size = 7; + break; + case H: + size = 8; + break; + } + + return size; + } + + private enum c1Mode { + C1_ASCII, C1_C40, C1_DECIMAL, C1_TEXT, C1_EDI, C1_BYTE + } + + public enum Version { + NONE, A, B, C, D, E, F, G, H, S, T + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Composite.java b/barcode/src/main/java/org/xbib/graphics/barcode/Composite.java new file mode 100755 index 0000000..fd0ef27 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Composite.java @@ -0,0 +1,2846 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Rectangle2D; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * Implements GS1 Composite symbology according to ISO/IEC 24723:2010. + * Composite symbols comprise a 2D element which encodes GS1 data + * and a "linear" element which can be UPC, EAN, Code 128 or + * GS1 DataBar symbol. + */ +public class Composite extends Symbol { + /* CC-A component coefficients from ISO/IEC 24728:2006 Annex F */ + private int[] ccaCoeffs = { + /* k = 4 */ + 522, 568, 723, 809, + /* k = 5 */ + 427, 919, 460, 155, 566, + /* k = 6 */ + 861, 285, 19, 803, 17, 766, + /* k = 7 */ + 76, 925, 537, 597, 784, 691, 437, + /* k = 8 */ + 237, 308, 436, 284, 646, 653, 428, 379 + }; + private int[] coefrs = { + /* k = 2 */ + 27, 917, + /* k = 4 */ + 522, 568, 723, 809, + /* k = 8 */ + 237, 308, 436, 284, 646, 653, 428, 379, + /* k = 16 */ + 274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65, + /* k = 32 */ + 361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, + 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410, + /* k = 64 */ + 539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733, 877, 381, 612, + 723, 476, 462, 172, 430, 609, 858, 822, 543, 376, 511, 400, 672, 762, 283, 184, + 440, 35, 519, 31, 460, 594, 225, 535, 517, 352, 605, 158, 651, 201, 488, 502, + 648, 733, 717, 83, 404, 97, 280, 771, 840, 629, 4, 381, 843, 623, 264, 543, + /* k = 128 */ + 521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400, 925, 749, 415, + 822, 93, 217, 208, 928, 244, 583, 620, 246, 148, 447, 631, 292, 908, 490, 704, + 516, 258, 457, 907, 594, 723, 674, 292, 272, 96, 684, 432, 686, 606, 860, 569, + 193, 219, 129, 186, 236, 287, 192, 775, 278, 173, 40, 379, 712, 463, 646, 776, + 171, 491, 297, 763, 156, 732, 95, 270, 447, 90, 507, 48, 228, 821, 808, 898, + 784, 663, 627, 378, 382, 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616, + 157, 374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814, 587, 804, 34, + 211, 330, 539, 297, 827, 865, 37, 517, 834, 315, 550, 86, 801, 4, 108, 539, + /* k = 256 */ + 524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905, 786, 138, 720, + 858, 194, 311, 913, 275, 190, 375, 850, 438, 733, 194, 280, 201, 280, 828, 757, + 710, 814, 919, 89, 68, 569, 11, 204, 796, 605, 540, 913, 801, 700, 799, 137, + 439, 418, 592, 668, 353, 859, 370, 694, 325, 240, 216, 257, 284, 549, 209, 884, + 315, 70, 329, 793, 490, 274, 877, 162, 749, 812, 684, 461, 334, 376, 849, 521, + 307, 291, 803, 712, 19, 358, 399, 908, 103, 511, 51, 8, 517, 225, 289, 470, + 637, 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136, 538, 906, 90, + 2, 290, 743, 199, 655, 903, 329, 49, 802, 580, 355, 588, 188, 462, 10, 134, + 628, 320, 479, 130, 739, 71, 263, 318, 374, 601, 192, 605, 142, 673, 687, 234, + 722, 384, 177, 752, 607, 640, 455, 193, 689, 707, 805, 641, 48, 60, 732, 621, + 895, 544, 261, 852, 655, 309, 697, 755, 756, 60, 231, 773, 434, 421, 726, 528, + 503, 118, 49, 795, 32, 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550, + 73, 914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180, 791, 893, 754, + 605, 383, 228, 749, 760, 213, 54, 297, 134, 54, 834, 299, 922, 191, 910, 532, + 609, 829, 189, 20, 167, 29, 872, 449, 83, 402, 41, 656, 505, 579, 481, 173, + 404, 251, 688, 95, 497, 555, 642, 543, 307, 159, 924, 558, 648, 55, 497, 10, + /* k = 512 */ + 352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285, 380, 350, 492, + 197, 265, 920, 155, 914, 299, 229, 643, 294, 871, 306, 88, 87, 193, 352, 781, + 846, 75, 327, 520, 435, 543, 203, 666, 249, 346, 781, 621, 640, 268, 794, 534, + 539, 781, 408, 390, 644, 102, 476, 499, 290, 632, 545, 37, 858, 916, 552, 41, + 542, 289, 122, 272, 383, 800, 485, 98, 752, 472, 761, 107, 784, 860, 658, 741, + 290, 204, 681, 407, 855, 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142, + 808, 684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513, 192, 516, 258, + 240, 518, 794, 395, 768, 848, 51, 610, 384, 168, 190, 826, 328, 596, 786, 303, + 570, 381, 415, 641, 156, 237, 151, 429, 531, 207, 676, 710, 89, 168, 304, 402, + 40, 708, 575, 162, 864, 229, 65, 861, 841, 512, 164, 477, 221, 92, 358, 785, + 288, 357, 850, 836, 827, 736, 707, 94, 8, 494, 114, 521, 2, 499, 851, 543, + 152, 729, 771, 95, 248, 361, 578, 323, 856, 797, 289, 51, 684, 466, 533, 820, + 669, 45, 902, 452, 167, 342, 244, 173, 35, 463, 651, 51, 699, 591, 452, 578, + 37, 124, 298, 332, 552, 43, 427, 119, 662, 777, 475, 850, 764, 364, 578, 911, + 283, 711, 472, 420, 245, 288, 594, 394, 511, 327, 589, 777, 699, 688, 43, 408, + 842, 383, 721, 521, 560, 644, 714, 559, 62, 145, 873, 663, 713, 159, 672, 729, + 624, 59, 193, 417, 158, 209, 563, 564, 343, 693, 109, 608, 563, 365, 181, 772, + 677, 310, 248, 353, 708, 410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777, + 618, 586, 424, 833, 77, 597, 346, 269, 757, 632, 695, 751, 331, 247, 184, 45, + 787, 680, 18, 66, 407, 369, 54, 492, 228, 613, 830, 922, 437, 519, 644, 905, + 789, 420, 305, 441, 207, 300, 892, 827, 141, 537, 381, 662, 513, 56, 252, 341, + 242, 797, 838, 837, 720, 224, 307, 631, 61, 87, 560, 310, 756, 665, 397, 808, + 851, 309, 473, 795, 378, 31, 647, 915, 459, 806, 590, 731, 425, 216, 548, 249, + 321, 881, 699, 535, 673, 782, 210, 815, 905, 303, 843, 922, 281, 73, 469, 791, + 660, 162, 498, 308, 155, 422, 907, 817, 187, 62, 16, 425, 535, 336, 286, 437, + 375, 273, 610, 296, 183, 923, 116, 667, 751, 353, 62, 366, 691, 379, 687, 842, + 37, 357, 720, 742, 330, 5, 39, 923, 311, 424, 242, 749, 321, 54, 669, 316, + 342, 299, 534, 105, 667, 488, 640, 672, 576, 540, 316, 486, 721, 610, 46, 656, + 447, 171, 616, 464, 190, 531, 297, 321, 762, 752, 533, 175, 134, 14, 381, 433, + 717, 45, 111, 20, 596, 284, 736, 138, 646, 411, 877, 669, 141, 919, 45, 780, + 407, 164, 332, 899, 165, 726, 600, 325, 498, 655, 357, 752, 768, 223, 849, 647, + 63, 310, 863, 251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263 + }; + + /* rows, error codewords, k-offset of valid CC-A sizes from ISO/IEC 24723:2006 Table 9 */ + private int[] ccaVariants = { + 5, 6, 7, 8, 9, 10, 12, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 4, 4, 5, 5, 6, 6, 7, 4, 5, 6, 7, 7, 4, 5, 6, 7, 8, 0, 0, 4, 4, 9, 9, 15, 0, 4, 9, 15, 15, 0, 4, 9, 15, 22 + }; + + /* following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24723:2006 tables 10 and 11 */ + private int[] aRAPTable = { + 39, 1, 32, 8, 14, 43, 20, 11, 1, 5, 15, 21, 40, 43, 46, 34, 29, 0, 0, 0, 0, 0, 0, 0, 43, 33, 37, 47, 1, 20, 23, 26, 14, 9, 19, 33, 12, 40, 46, 23, 52, 23, 13, 17, 27, 33, 52, 3, 6, 46, 41, 6, 0, 3, 3, 3, 0, 3, 3, 0, 3, 6, 6, 0, 0, 0, 0, 3 + }; + private String[] codagemc = { + "urA", "xfs", "ypy", "unk", "xdw", "yoz", "pDA", "uls", "pBk", "eBA", + "pAs", "eAk", "prA", "uvs", "xhy", "pnk", "utw", "xgz", "fDA", "pls", "fBk", "frA", "pvs", + "uxy", "fnk", "ptw", "uwz", "fls", "psy", "fvs", "pxy", "ftw", "pwz", "fxy", "yrx", "ufk", + "xFw", "ymz", "onA", "uds", "xEy", "olk", "ucw", "dBA", "oks", "uci", "dAk", "okg", "dAc", + "ovk", "uhw", "xaz", "dnA", "ots", "ugy", "dlk", "osw", "ugj", "dks", "osi", "dvk", "oxw", + "uiz", "dts", "owy", "dsw", "owj", "dxw", "oyz", "dwy", "dwj", "ofA", "uFs", "xCy", "odk", + "uEw", "xCj", "clA", "ocs", "uEi", "ckk", "ocg", "ckc", "ckE", "cvA", "ohs", "uay", "ctk", + "ogw", "uaj", "css", "ogi", "csg", "csa", "cxs", "oiy", "cww", "oij", "cwi", "cyy", "oFk", + "uCw", "xBj", "cdA", "oEs", "uCi", "cck", "oEg", "uCb", "ccc", "oEa", "ccE", "oED", "chk", + "oaw", "uDj", "cgs", "oai", "cgg", "oab", "cga", "cgD", "obj", "cib", "cFA", "oCs", "uBi", + "cEk", "oCg", "uBb", "cEc", "oCa", "cEE", "oCD", "cEC", "cas", "cag", "caa", "cCk", "uAr", + "oBa", "oBD", "cCB", "tfk", "wpw", "yez", "mnA", "tds", "woy", "mlk", "tcw", "woj", "FBA", + "mks", "FAk", "mvk", "thw", "wqz", "FnA", "mts", "tgy", "Flk", "msw", "Fks", "Fkg", "Fvk", + "mxw", "tiz", "Fts", "mwy", "Fsw", "Fsi", "Fxw", "myz", "Fwy", "Fyz", "vfA", "xps", "yuy", + "vdk", "xow", "yuj", "qlA", "vcs", "xoi", "qkk", "vcg", "xob", "qkc", "vca", "mfA", "tFs", + "wmy", "qvA", "mdk", "tEw", "wmj", "qtk", "vgw", "xqj", "hlA", "Ekk", "mcg", "tEb", "hkk", + "qsg", "hkc", "EvA", "mhs", "tay", "hvA", "Etk", "mgw", "taj", "htk", "qww", "vij", "hss", + "Esg", "hsg", "Exs", "miy", "hxs", "Eww", "mij", "hww", "qyj", "hwi", "Eyy", "hyy", "Eyj", + "hyj", "vFk", "xmw", "ytj", "qdA", "vEs", "xmi", "qck", "vEg", "xmb", "qcc", "vEa", "qcE", + "qcC", "mFk", "tCw", "wlj", "qhk", "mEs", "tCi", "gtA", "Eck", "vai", "tCb", "gsk", "Ecc", + "mEa", "gsc", "qga", "mED", "EcC", "Ehk", "maw", "tDj", "gxk", "Egs", "mai", "gws", "qii", + "mab", "gwg", "Ega", "EgD", "Eiw", "mbj", "gyw", "Eii", "gyi", "Eib", "gyb", "gzj", "qFA", + "vCs", "xli", "qEk", "vCg", "xlb", "qEc", "vCa", "qEE", "vCD", "qEC", "qEB", "EFA", "mCs", + "tBi", "ghA", "EEk", "mCg", "tBb", "ggk", "qag", "vDb", "ggc", "EEE", "mCD", "ggE", "qaD", + "ggC", "Eas", "mDi", "gis", "Eag", "mDb", "gig", "qbb", "gia", "EaD", "giD", "gji", "gjb", + "qCk", "vBg", "xkr", "qCc", "vBa", "qCE", "vBD", "qCC", "qCB", "ECk", "mBg", "tAr", "gak", + "ECc", "mBa", "gac", "qDa", "mBD", "gaE", "ECC", "gaC", "ECB", "EDg", "gbg", "gba", "gbD", + "vAq", "vAn", "qBB", "mAq", "EBE", "gDE", "gDC", "gDB", "lfA", "sps", "wey", "ldk", "sow", + "ClA", "lcs", "soi", "Ckk", "lcg", "Ckc", "CkE", "CvA", "lhs", "sqy", "Ctk", "lgw", "sqj", + "Css", "lgi", "Csg", "Csa", "Cxs", "liy", "Cww", "lij", "Cwi", "Cyy", "Cyj", "tpk", "wuw", + "yhj", "ndA", "tos", "wui", "nck", "tog", "wub", "ncc", "toa", "ncE", "toD", "lFk", "smw", + "wdj", "nhk", "lEs", "smi", "atA", "Cck", "tqi", "smb", "ask", "ngg", "lEa", "asc", "CcE", + "asE", "Chk", "law", "snj", "axk", "Cgs", "trj", "aws", "nii", "lab", "awg", "Cga", "awa", + "Ciw", "lbj", "ayw", "Cii", "ayi", "Cib", "Cjj", "azj", "vpA", "xus", "yxi", "vok", "xug", + "yxb", "voc", "xua", "voE", "xuD", "voC", "nFA", "tms", "wti", "rhA", "nEk", "xvi", "wtb", + "rgk", "vqg", "xvb", "rgc", "nEE", "tmD", "rgE", "vqD", "nEB", "CFA", "lCs", "sli", "ahA", + "CEk", "lCg", "slb", "ixA", "agk", "nag", "tnb", "iwk", "rig", "vrb", "lCD", "iwc", "agE", + "naD", "iwE", "CEB", "Cas", "lDi", "ais", "Cag", "lDb", "iys", "aig", "nbb", "iyg", "rjb", + "CaD", "aiD", "Cbi", "aji", "Cbb", "izi", "ajb", "vmk", "xtg", "ywr", "vmc", "xta", "vmE", + "xtD", "vmC", "vmB", "nCk", "tlg", "wsr", "rak", "nCc", "xtr", "rac", "vna", "tlD", "raE", + "nCC", "raC", "nCB", "raB", "CCk", "lBg", "skr", "aak", "CCc", "lBa", "iik", "aac", "nDa", + "lBD", "iic", "rba", "CCC", "iiE", "aaC", "CCB", "aaB", "CDg", "lBr", "abg", "CDa", "ijg", + "aba", "CDD", "ija", "abD", "CDr", "ijr", "vlc", "xsq", "vlE", "xsn", "vlC", "vlB", "nBc", + "tkq", "rDc", "nBE", "tkn", "rDE", "vln", "rDC", "nBB", "rDB", "CBc", "lAq", "aDc", "CBE", + "lAn", "ibc", "aDE", "nBn", "ibE", "rDn", "CBB", "ibC", "aDB", "ibB", "aDq", "ibq", "ibn", + "xsf", "vkl", "tkf", "nAm", "nAl", "CAo", "aBo", "iDo", "CAl", "aBl", "kpk", "BdA", "kos", + "Bck", "kog", "seb", "Bcc", "koa", "BcE", "koD", "Bhk", "kqw", "sfj", "Bgs", "kqi", "Bgg", + "kqb", "Bga", "BgD", "Biw", "krj", "Bii", "Bib", "Bjj", "lpA", "sus", "whi", "lok", "sug", + "loc", "sua", "loE", "suD", "loC", "BFA", "kms", "sdi", "DhA", "BEk", "svi", "sdb", "Dgk", + "lqg", "svb", "Dgc", "BEE", "kmD", "DgE", "lqD", "BEB", "Bas", "kni", "Dis", "Bag", "knb", + "Dig", "lrb", "Dia", "BaD", "Bbi", "Dji", "Bbb", "Djb", "tuk", "wxg", "yir", "tuc", "wxa", + "tuE", "wxD", "tuC", "tuB", "lmk", "stg", "nqk", "lmc", "sta", "nqc", "tva", "stD", "nqE", + "lmC", "nqC", "lmB", "nqB", "BCk", "klg", "Dak", "BCc", "str", "bik", "Dac", "lna", "klD", + "bic", "nra", "BCC", "biE", "DaC", "BCB", "DaB", "BDg", "klr", "Dbg", "BDa", "bjg", "Dba", + "BDD", "bja", "DbD", "BDr", "Dbr", "bjr", "xxc", "yyq", "xxE", "yyn", "xxC", "xxB", "ttc", + "wwq", "vvc", "xxq", "wwn", "vvE", "xxn", "vvC", "ttB", "vvB", "llc", "ssq", "nnc", "llE", + "ssn", "rrc", "nnE", "ttn", "rrE", "vvn", "llB", "rrC", "nnB", "rrB", "BBc", "kkq", "DDc", + "BBE", "kkn", "bbc", "DDE", "lln", "jjc", "bbE", "nnn", "BBB", "jjE", "rrn", "DDB", "jjC", + "BBq", "DDq", "BBn", "bbq", "DDn", "jjq", "bbn", "jjn", "xwo", "yyf", "xwm", "xwl", "tso", + "wwf", "vto", "xwv", "vtm", "tsl", "vtl", "lko", "ssf", "nlo", "lkm", "rno", "nlm", "lkl", + "rnm", "nll", "rnl", "BAo", "kkf", "DBo", "lkv", "bDo", "DBm", "BAl", "jbo", "bDm", "DBl", + "jbm", "bDl", "jbl", "DBv", "jbv", "xwd", "vsu", "vst", "nku", "rlu", "rlt", "DAu", "bBu", + "jDu", "jDt", "ApA", "Aok", "keg", "Aoc", "AoE", "AoC", "Aqs", "Aqg", "Aqa", "AqD", "Ari", + "Arb", "kuk", "kuc", "sha", "kuE", "shD", "kuC", "kuB", "Amk", "kdg", "Bqk", "kvg", "kda", + "Bqc", "kva", "BqE", "kvD", "BqC", "AmB", "BqB", "Ang", "kdr", "Brg", "kvr", "Bra", "AnD", + "BrD", "Anr", "Brr", "sxc", "sxE", "sxC", "sxB", "ktc", "lvc", "sxq", "sgn", "lvE", "sxn", + "lvC", "ktB", "lvB", "Alc", "Bnc", "AlE", "kcn", "Drc", "BnE", "AlC", "DrE", "BnC", "AlB", + "DrC", "BnB", "Alq", "Bnq", "Aln", "Drq", "Bnn", "Drn", "wyo", "wym", "wyl", "swo", "txo", + "wyv", "txm", "swl", "txl", "kso", "sgf", "lto", "swv", "nvo", "ltm", "ksl", "nvm", "ltl", + "nvl", "Ako", "kcf", "Blo", "ksv", "Dno", "Blm", "Akl", "bro", "Dnm", "Bll", "brm", "Dnl", + "Akv", "Blv", "Dnv", "brv", "yze", "yzd", "wye", "xyu", "wyd", "xyt", "swe", "twu", "swd", + "vxu", "twt", "vxt", "kse", "lsu", "ksd", "ntu", "lst", "rvu", "ypk", "zew", "xdA", "yos", + "zei", "xck", "yog", "zeb", "xcc", "yoa", "xcE", "yoD", "xcC", "xhk", "yqw", "zfj", "utA", + "xgs", "yqi", "usk", "xgg", "yqb", "usc", "xga", "usE", "xgD", "usC", "uxk", "xiw", "yrj", + "ptA", "uws", "xii", "psk", "uwg", "xib", "psc", "uwa", "psE", "uwD", "psC", "pxk", "uyw", + "xjj", "ftA", "pws", "uyi", "fsk", "pwg", "uyb", "fsc", "pwa", "fsE", "pwD", "fxk", "pyw", + "uzj", "fws", "pyi", "fwg", "pyb", "fwa", "fyw", "pzj", "fyi", "fyb", "xFA", "yms", "zdi", + "xEk", "ymg", "zdb", "xEc", "yma", "xEE", "ymD", "xEC", "xEB", "uhA", "xas", "yni", "ugk", + "xag", "ynb", "ugc", "xaa", "ugE", "xaD", "ugC", "ugB", "oxA", "uis", "xbi", "owk", "uig", + "xbb", "owc", "uia", "owE", "uiD", "owC", "owB", "dxA", "oys", "uji", "dwk", "oyg", "ujb", + "dwc", "oya", "dwE", "oyD", "dwC", "dys", "ozi", "dyg", "ozb", "dya", "dyD", "dzi", "dzb", + "xCk", "ylg", "zcr", "xCc", "yla", "xCE", "ylD", "xCC", "xCB", "uak", "xDg", "ylr", "uac", + "xDa", "uaE", "xDD", "uaC", "uaB", "oik", "ubg", "xDr", "oic", "uba", "oiE", "ubD", "oiC", + "oiB", "cyk", "ojg", "ubr", "cyc", "oja", "cyE", "ojD", "cyC", "cyB", "czg", "ojr", "cza", + "czD", "czr", "xBc", "ykq", "xBE", "ykn", "xBC", "xBB", "uDc", "xBq", "uDE", "xBn", "uDC", + "uDB", "obc", "uDq", "obE", "uDn", "obC", "obB", "cjc", "obq", "cjE", "obn", "cjC", "cjB", + "cjq", "cjn", "xAo", "ykf", "xAm", "xAl", "uBo", "xAv", "uBm", "uBl", "oDo", "uBv", "oDm", + "oDl", "cbo", "oDv", "cbm", "cbl", "xAe", "xAd", "uAu", "uAt", "oBu", "oBt", "wpA", "yes", + "zFi", "wok", "yeg", "zFb", "woc", "yea", "woE", "yeD", "woC", "woB", "thA", "wqs", "yfi", + "tgk", "wqg", "yfb", "tgc", "wqa", "tgE", "wqD", "tgC", "tgB", "mxA", "tis", "wri", "mwk", + "tig", "wrb", "mwc", "tia", "mwE", "tiD", "mwC", "mwB", "FxA", "mys", "tji", "Fwk", "myg", + "tjb", "Fwc", "mya", "FwE", "myD", "FwC", "Fys", "mzi", "Fyg", "mzb", "Fya", "FyD", "Fzi", + "Fzb", "yuk", "zhg", "hjs", "yuc", "zha", "hbw", "yuE", "zhD", "hDy", "yuC", "yuB", "wmk", + "ydg", "zEr", "xqk", "wmc", "zhr", "xqc", "yva", "ydD", "xqE", "wmC", "xqC", "wmB", "xqB", + "tak", "wng", "ydr", "vik", "tac", "wna", "vic", "xra", "wnD", "viE", "taC", "viC", "taB", + "viB", "mik", "tbg", "wnr", "qyk", "mic", "tba", "qyc", "vja", "tbD", "qyE", "miC", "qyC", + "miB", "qyB", "Eyk", "mjg", "tbr", "hyk", "Eyc", "mja", "hyc", "qza", "mjD", "hyE", "EyC", + "hyC", "EyB", "Ezg", "mjr", "hzg", "Eza", "hza", "EzD", "hzD", "Ezr", "ytc", "zgq", "grw", + "ytE", "zgn", "gny", "ytC", "glz", "ytB", "wlc", "ycq", "xnc", "wlE", "ycn", "xnE", "ytn", + "xnC", "wlB", "xnB", "tDc", "wlq", "vbc", "tDE", "wln", "vbE", "xnn", "vbC", "tDB", "vbB", + "mbc", "tDq", "qjc", "mbE", "tDn", "qjE", "vbn", "qjC", "mbB", "qjB", "Ejc", "mbq", "gzc", + "EjE", "mbn", "gzE", "qjn", "gzC", "EjB", "gzB", "Ejq", "gzq", "Ejn", "gzn", "yso", "zgf", + "gfy", "ysm", "gdz", "ysl", "wko", "ycf", "xlo", "ysv", "xlm", "wkl", "xll", "tBo", "wkv", + "vDo", "tBm", "vDm", "tBl", "vDl", "mDo", "tBv", "qbo", "vDv", "qbm", "mDl", "qbl", "Ebo", + "mDv", "gjo", "Ebm", "gjm", "Ebl", "gjl", "Ebv", "gjv", "yse", "gFz", "ysd", "wke", "xku", + "wkd", "xkt", "tAu", "vBu", "tAt", "vBt", "mBu", "qDu", "mBt", "qDt", "EDu", "gbu", "EDt", + "gbt", "ysF", "wkF", "xkh", "tAh", "vAx", "mAx", "qBx", "wek", "yFg", "zCr", "wec", "yFa", + "weE", "yFD", "weC", "weB", "sqk", "wfg", "yFr", "sqc", "wfa", "sqE", "wfD", "sqC", "sqB", + "lik", "srg", "wfr", "lic", "sra", "liE", "srD", "liC", "liB", "Cyk", "ljg", "srr", "Cyc", + "lja", "CyE", "ljD", "CyC", "CyB", "Czg", "ljr", "Cza", "CzD", "Czr", "yhc", "zaq", "arw", + "yhE", "zan", "any", "yhC", "alz", "yhB", "wdc", "yEq", "wvc", "wdE", "yEn", "wvE", "yhn", + "wvC", "wdB", "wvB", "snc", "wdq", "trc", "snE", "wdn", "trE", "wvn", "trC", "snB", "trB", + "lbc", "snq", "njc", "lbE", "snn", "njE", "trn", "njC", "lbB", "njB", "Cjc", "lbq", "azc", + "CjE", "lbn", "azE", "njn", "azC", "CjB", "azB", "Cjq", "azq", "Cjn", "azn", "zio", "irs", + "rfy", "zim", "inw", "rdz", "zil", "ily", "ikz", "ygo", "zaf", "afy", "yxo", "ziv", "ivy", + "adz", "yxm", "ygl", "itz", "yxl", "wco", "yEf", "wto", "wcm", "xvo", "yxv", "wcl", "xvm", + "wtl", "xvl", "slo", "wcv", "tno", "slm", "vro", "tnm", "sll", "vrm", "tnl", "vrl", "lDo", + "slv", "nbo", "lDm", "rjo", "nbm", "lDl", "rjm", "nbl", "rjl", "Cbo", "lDv", "ajo", "Cbm", + "izo", "ajm", "Cbl", "izm", "ajl", "izl", "Cbv", "ajv", "zie", "ifw", "rFz", "zid", "idy", + "icz", "yge", "aFz", "ywu", "ygd", "ihz", "ywt", "wce", "wsu", "wcd", "xtu", "wst", "xtt", + "sku", "tlu", "skt", "vnu", "tlt", "vnt", "lBu", "nDu", "lBt", "rbu", "nDt", "rbt", "CDu", + "abu", "CDt", "iju", "abt", "ijt", "ziF", "iFy", "iEz", "ygF", "ywh", "wcF", "wsh", "xsx", + "skh", "tkx", "vlx", "lAx", "nBx", "rDx", "CBx", "aDx", "ibx", "iCz", "wFc", "yCq", "wFE", + "yCn", "wFC", "wFB", "sfc", "wFq", "sfE", "wFn", "sfC", "sfB", "krc", "sfq", "krE", "sfn", + "krC", "krB", "Bjc", "krq", "BjE", "krn", "BjC", "BjB", "Bjq", "Bjn", "yao", "zDf", "Dfy", + "yam", "Ddz", "yal", "wEo", "yCf", "who", "wEm", "whm", "wEl", "whl", "sdo", "wEv", "svo", + "sdm", "svm", "sdl", "svl", "kno", "sdv", "lro", "knm", "lrm", "knl", "lrl", "Bbo", "knv", + "Djo", "Bbm", "Djm", "Bbl", "Djl", "Bbv", "Djv", "zbe", "bfw", "npz", "zbd", "bdy", "bcz", + "yae", "DFz", "yiu", "yad", "bhz", "yit", "wEe", "wgu", "wEd", "wxu", "wgt", "wxt", "scu", + "stu", "sct", "tvu", "stt", "tvt", "klu", "lnu", "klt", "nru", "lnt", "nrt", "BDu", "Dbu", + "BDt", "bju", "Dbt", "bjt", "jfs", "rpy", "jdw", "roz", "jcy", "jcj", "zbF", "bFy", "zjh", + "jhy", "bEz", "jgz", "yaF", "yih", "yyx", "wEF", "wgh", "wwx", "xxx", "sch", "ssx", "ttx", + "vvx", "kkx", "llx", "nnx", "rrx", "BBx", "DDx", "bbx", "jFw", "rmz", "jEy", "jEj", "bCz", + "jaz", "jCy", "jCj", "jBj", "wCo", "wCm", "wCl", "sFo", "wCv", "sFm", "sFl", "kfo", "sFv", + "kfm", "kfl", "Aro", "kfv", "Arm", "Arl", "Arv", "yDe", "Bpz", "yDd", "wCe", "wau", "wCd", + "wat", "sEu", "shu", "sEt", "sht", "kdu", "kvu", "kdt", "kvt", "Anu", "Bru", "Ant", "Brt", + "zDp", "Dpy", "Doz", "yDF", "ybh", "wCF", "wah", "wix", "sEh", "sgx", "sxx", "kcx", "ktx", + "lvx", "Alx", "Bnx", "Drx", "bpw", "nuz", "boy", "boj", "Dmz", "bqz", "jps", "ruy", "jow", + "ruj", "joi", "job", "bmy", "jqy", "bmj", "jqj", "jmw", "rtj", "jmi", "jmb", "blj", "jnj", + "jli", "jlb", "jkr", "sCu", "sCt", "kFu", "kFt", "Afu", "Aft", "wDh", "sCh", "sax", "kEx", + "khx", "Adx", "Avx", "Buz", "Duy", "Duj", "buw", "nxj", "bui", "bub", "Dtj", "bvj", "jus", + "rxi", "jug", "rxb", "jua", "juD", "bti", "jvi", "btb", "jvb", "jtg", "rwr", "jta", "jtD", + "bsr", "jtr", "jsq", "jsn", "Bxj", "Dxi", "Dxb", "bxg", "nyr", "bxa", "bxD", "Dwr", "bxr", + "bwq", "bwn", "pjk", "urw", "ejA", "pbs", "uny", "ebk", "pDw", "ulz", "eDs", "pBy", "eBw", + "zfc", "fjk", "prw", "zfE", "fbs", "pny", "zfC", "fDw", "plz", "zfB", "fBy", "yrc", "zfq", + "frw", "yrE", "zfn", "fny", "yrC", "flz", "yrB", "xjc", "yrq", "xjE", "yrn", "xjC", "xjB", + "uzc", "xjq", "uzE", "xjn", "uzC", "uzB", "pzc", "uzq", "pzE", "uzn", "pzC", "djA", "ors", + "ufy", "dbk", "onw", "udz", "dDs", "oly", "dBw", "okz", "dAy", "zdo", "drs", "ovy", "zdm", + "dnw", "otz", "zdl", "dly", "dkz", "yno", "zdv", "dvy", "ynm", "dtz", "ynl", "xbo", "ynv", + "xbm", "xbl", "ujo", "xbv", "ujm", "ujl", "ozo", "ujv", "ozm", "ozl", "crk", "ofw", "uFz", + "cns", "ody", "clw", "ocz", "cky", "ckj", "zcu", "cvw", "ohz", "zct", "cty", "csz", "ylu", + "cxz", "ylt", "xDu", "xDt", "ubu", "ubt", "oju", "ojt", "cfs", "oFy", "cdw", "oEz", "ccy", + "ccj", "zch", "chy", "cgz", "ykx", "xBx", "uDx", "cFw", "oCz", "cEy", "cEj", "caz", "cCy", + "cCj", "FjA", "mrs", "tfy", "Fbk", "mnw", "tdz", "FDs", "mly", "FBw", "mkz", "FAy", "zFo", + "Frs", "mvy", "zFm", "Fnw", "mtz", "zFl", "Fly", "Fkz", "yfo", "zFv", "Fvy", "yfm", "Ftz", + "yfl", "wro", "yfv", "wrm", "wrl", "tjo", "wrv", "tjm", "tjl", "mzo", "tjv", "mzm", "mzl", + "qrk", "vfw", "xpz", "hbA", "qns", "vdy", "hDk", "qlw", "vcz", "hBs", "qky", "hAw", "qkj", + "hAi", "Erk", "mfw", "tFz", "hrk", "Ens", "mdy", "hns", "qty", "mcz", "hlw", "Eky", "hky", + "Ekj", "hkj", "zEu", "Evw", "mhz", "zhu", "zEt", "hvw", "Ety", "zht", "hty", "Esz", "hsz", + "ydu", "Exz", "yvu", "ydt", "hxz", "yvt", "wnu", "xru", "wnt", "xrt", "tbu", "vju", "tbt", + "vjt", "mju", "mjt", "grA", "qfs", "vFy", "gnk", "qdw", "vEz", "gls", "qcy", "gkw", "qcj", + "gki", "gkb", "Efs", "mFy", "gvs", "Edw", "mEz", "gtw", "qgz", "gsy", "Ecj", "gsj", "zEh", + "Ehy", "zgx", "gxy", "Egz", "gwz", "ycx", "ytx", "wlx", "xnx", "tDx", "vbx", "mbx", "gfk", + "qFw", "vCz", "gds", "qEy", "gcw", "qEj", "gci", "gcb", "EFw", "mCz", "ghw", "EEy", "ggy", + "EEj", "ggj", "Eaz", "giz", "gFs", "qCy", "gEw", "qCj", "gEi", "gEb", "ECy", "gay", "ECj", + "gaj", "gCw", "qBj", "gCi", "gCb", "EBj", "gDj", "gBi", "gBb", "Crk", "lfw", "spz", "Cns", + "ldy", "Clw", "lcz", "Cky", "Ckj", "zCu", "Cvw", "lhz", "zCt", "Cty", "Csz", "yFu", "Cxz", + "yFt", "wfu", "wft", "sru", "srt", "lju", "ljt", "arA", "nfs", "tpy", "ank", "ndw", "toz", + "als", "ncy", "akw", "ncj", "aki", "akb", "Cfs", "lFy", "avs", "Cdw", "lEz", "atw", "ngz", + "asy", "Ccj", "asj", "zCh", "Chy", "zax", "axy", "Cgz", "awz", "yEx", "yhx", "wdx", "wvx", + "snx", "trx", "lbx", "rfk", "vpw", "xuz", "inA", "rds", "voy", "ilk", "rcw", "voj", "iks", + "rci", "ikg", "rcb", "ika", "afk", "nFw", "tmz", "ivk", "ads", "nEy", "its", "rgy", "nEj", + "isw", "aci", "isi", "acb", "isb", "CFw", "lCz", "ahw", "CEy", "ixw", "agy", "CEj", "iwy", + "agj", "iwj", "Caz", "aiz", "iyz", "ifA", "rFs", "vmy", "idk", "rEw", "vmj", "ics", "rEi", + "icg", "rEb", "ica", "icD", "aFs", "nCy", "ihs", "aEw", "nCj", "igw", "raj", "igi", "aEb", + "igb", "CCy", "aay", "CCj", "iiy", "aaj", "iij", "iFk", "rCw", "vlj", "iEs", "rCi", "iEg", + "rCb", "iEa", "iED", "aCw", "nBj", "iaw", "aCi", "iai", "aCb", "iab", "CBj", "aDj", "ibj", + "iCs", "rBi", "iCg", "rBb", "iCa", "iCD", "aBi", "iDi", "aBb", "iDb", "iBg", "rAr", "iBa", + "iBD", "aAr", "iBr", "iAq", "iAn", "Bfs", "kpy", "Bdw", "koz", "Bcy", "Bcj", "Bhy", "Bgz", + "yCx", "wFx", "sfx", "krx", "Dfk", "lpw", "suz", "Dds", "loy", "Dcw", "loj", "Dci", "Dcb", + "BFw", "kmz", "Dhw", "BEy", "Dgy", "BEj", "Dgj", "Baz", "Diz", "bfA", "nps", "tuy", "bdk", + "now", "tuj", "bcs", "noi", "bcg", "nob", "bca", "bcD", "DFs", "lmy", "bhs", "DEw", "lmj", + "bgw", "DEi", "bgi", "DEb", "bgb", "BCy", "Day", "BCj", "biy", "Daj", "bij", "rpk", "vuw", + "xxj", "jdA", "ros", "vui", "jck", "rog", "vub", "jcc", "roa", "jcE", "roD", "jcC", "bFk", + "nmw", "ttj", "jhk", "bEs", "nmi", "jgs", "rqi", "nmb", "jgg", "bEa", "jga", "bED", "jgD", + "DCw", "llj", "baw", "DCi", "jiw", "bai", "DCb", "jii", "bab", "jib", "BBj", "DDj", "bbj", + "jjj", "jFA", "rms", "vti", "jEk", "rmg", "vtb", "jEc", "rma", "jEE", "rmD", "jEC", "jEB", + "bCs", "nli", "jas", "bCg", "nlb", "jag", "rnb", "jaa", "bCD", "jaD", "DBi", "bDi", "DBb", + "jbi", "bDb", "jbb", "jCk", "rlg", "vsr", "jCc", "rla", "jCE", "rlD", "jCC", "jCB", "bBg", + "nkr", "jDg", "bBa", "jDa", "bBD", "jDD", "DAr", "bBr", "jDr", "jBc", "rkq", "jBE", "rkn", + "jBC", "jBB", "bAq", "jBq", "bAn", "jBn", "jAo", "rkf", "jAm", "jAl", "bAf", "jAv", "Apw", + "kez", "Aoy", "Aoj", "Aqz", "Bps", "kuy", "Bow", "kuj", "Boi", "Bob", "Amy", "Bqy", "Amj", + "Bqj", "Dpk", "luw", "sxj", "Dos", "lui", "Dog", "lub", "Doa", "DoD", "Bmw", "ktj", "Dqw", + "Bmi", "Dqi", "Bmb", "Dqb", "Alj", "Bnj", "Drj", "bpA", "nus", "txi", "bok", "nug", "txb", + "boc", "nua", "boE", "nuD", "boC", "boB", "Dms", "lti", "bqs", "Dmg", "ltb", "bqg", "nvb", + "bqa", "DmD", "bqD", "Bli", "Dni", "Blb", "bri", "Dnb", "brb", "ruk", "vxg", "xyr", "ruc", + "vxa", "ruE", "vxD", "ruC", "ruB", "bmk", "ntg", "twr", "jqk", "bmc", "nta", "jqc", "rva", + "ntD", "jqE", "bmC", "jqC", "bmB", "jqB", "Dlg", "lsr", "bng", "Dla", "jrg", "bna", "DlD", + "jra", "bnD", "jrD", "Bkr", "Dlr", "bnr", "jrr", "rtc", "vwq", "rtE", "vwn", "rtC", "rtB", + "blc", "nsq", "jnc", "blE", "nsn", "jnE", "rtn", "jnC", "blB", "jnB", "Dkq", "blq", "Dkn", + "jnq", "bln", "jnn", "rso", "vwf", "rsm", "rsl", "bko", "nsf", "jlo", "bkm", "jlm", "bkl", + "jll", "Dkf", "bkv", "jlv", "rse", "rsd", "bke", "jku", "bkd", "jkt", "Aey", "Aej", "Auw", + "khj", "Aui", "Aub", "Adj", "Avj", "Bus", "kxi", "Bug", "kxb", "Bua", "BuD", "Ati", "Bvi", + "Atb", "Bvb", "Duk", "lxg", "syr", "Duc", "lxa", "DuE", "lxD", "DuC", "DuB", "Btg", "kwr", + "Dvg", "lxr", "Dva", "BtD", "DvD", "Asr", "Btr", "Dvr", "nxc", "tyq", "nxE", "tyn", "nxC", + "nxB", "Dtc", "lwq", "bvc", "nxq", "lwn", "bvE", "DtC", "bvC", "DtB", "bvB", "Bsq", "Dtq", + "Bsn", "bvq", "Dtn", "bvn", "vyo", "xzf", "vym", "vyl", "nwo", "tyf", "rxo", "nwm", "rxm", + "nwl", "rxl", "Dso", "lwf", "bto", "Dsm", "jvo", "btm", "Dsl", "jvm", "btl", "jvl", "Bsf", + "Dsv", "btv", "jvv", "vye", "vyd", "nwe", "rwu", "nwd", "rwt", "Dse", "bsu", "Dsd", "jtu", + "bst", "jtt", "vyF", "nwF", "rwh", "DsF", "bsh", "jsx", "Ahi", "Ahb", "Axg", "kir", "Axa", + "AxD", "Agr", "Axr", "Bxc", "kyq", "BxE", "kyn", "BxC", "BxB", "Awq", "Bxq", "Awn", "Bxn", + "lyo", "szf", "lym", "lyl", "Bwo", "kyf", "Dxo", "lyv", "Dxm", "Bwl", "Dxl", "Awf", "Bwv", + "Dxv", "tze", "tzd", "lye", "nyu", "lyd", "nyt", "Bwe", "Dwu", "Bwd", "bxu", "Dwt", "bxt", + "tzF", "lyF", "nyh", "BwF", "Dwh", "bwx", "Aiq", "Ain", "Ayo", "kjf", "Aym", "Ayl", "Aif", + "Ayv", "kze", "kzd", "Aye", "Byu", "Ayd", "Byt", "szp" + }; + private char[] brSet = { + 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '*', '+', '-' + }; + private String[] PDFttf = { + "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", + "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001", + "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001", "11010", + "11011", "11100", "11101", "11110", "11111", "01", "1111111101010100", "11111101000101001" + }; + /* Left and Right Row Address Pattern from Table 2 */ + private String[] RAPLR = {"", "221311", "311311", "312211", "222211", "213211", "214111", "223111", + "313111", "322111", "412111", "421111", "331111", "241111", "232111", "231211", "321211", + "411211", "411121", "411112", "321112", "312112", "311212", "311221", "311131", "311122", + "311113", "221113", "221122", "221131", "221221", "222121", "312121", "321121", "231121", + "231112", "222112", "213112", "212212", "212221", "212131", "212122", "212113", "211213", + "211123", "211132", "211141", "211231", "211222", "211312", "211321", "211411", "212311"}; + + /* Centre Row Address Pattern from Table 2 */ + private String[] RAPC = {"", "112231", "121231", "122131", "131131", "131221", "132121", "141121", + "141211", "142111", "133111", "132211", "131311", "122311", "123211", "124111", "115111", + "114211", "114121", "123121", "123112", "122212", "122221", "121321", "121411", "112411", + "113311", "113221", "113212", "113122", "122122", "131122", "131113", "122113", "113113", + "112213", "112222", "112312", "112321", "111421", "111331", "111322", "111232", "111223", + "111133", "111124", "111214", "112114", "121114", "121123", "121132", "112132", "112141"}; + private int[] MicroVariants = {1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 11, 14, 17, 20, 24, 28, 8, 11, 14, 17, 20, 23, 26, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, 4, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, + 7, 7, 7, 8, 8, 8, 8, 9, 9, 10, 11, 13, 15, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, 8, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, + 0, 0, 0, 7, 7, 7, 7, 15, 15, 24, 34, 57, 84, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294, 7, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294}; + /* rows, columns, error codewords, k-offset */ + /* MicroPDF417 coefficients from ISO/IEC 24728:2006 Annex F */ + private int[] Microcoeffs = { + /* k = 7 */ + 76, 925, 537, 597, 784, 691, 437, + /* k = 8 */ + 237, 308, 436, 284, 646, 653, 428, 379, + /* k = 9 */ + 567, 527, 622, 257, 289, 362, 501, 441, 205, + /* k = 10 */ + 377, 457, 64, 244, 826, 841, 818, 691, 266, 612, + /* k = 11 */ + 462, 45, 565, 708, 825, 213, 15, 68, 327, 602, 904, + /* k = 12 */ + 597, 864, 757, 201, 646, 684, 347, 127, 388, 7, 69, 851, + /* k = 13 */ + 764, 713, 342, 384, 606, 583, 322, 592, 678, 204, 184, 394, 692, + /* k = 14 */ + 669, 677, 154, 187, 241, 286, 274, 354, 478, 915, 691, 833, 105, 215, + /* k = 15 */ + 460, 829, 476, 109, 904, 664, 230, 5, 80, 74, 550, 575, 147, 868, 642, + /* k = 16 */ + 274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65, + /* k = 18 */ + 279, 577, 315, 624, 37, 855, 275, 739, 120, 297, 312, 202, 560, 321, 233, 756, + 760, 573, + /* k = 21 */ + 108, 519, 781, 534, 129, 425, 681, 553, 422, 716, 763, 693, 624, 610, 310, 691, + 347, 165, 193, 259, 568, + /* k = 26 */ + 443, 284, 887, 544, 788, 93, 477, 760, 331, 608, 269, 121, 159, 830, 446, 893, + 699, 245, 441, 454, 325, 858, 131, 847, 764, 169, + /* k = 32 */ + 361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, + 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410, + /* k = 38 */ + 234, 228, 438, 848, 133, 703, 529, 721, 788, 322, 280, 159, 738, 586, 388, 684, + 445, 680, 245, 595, 614, 233, 812, 32, 284, 658, 745, 229, 95, 689, 920, 771, + 554, 289, 231, 125, 117, 518, + /* k = 44 */ + 476, 36, 659, 848, 678, 64, 764, 840, 157, 915, 470, 876, 109, 25, 632, 405, + 417, 436, 714, 60, 376, 97, 413, 706, 446, 21, 3, 773, 569, 267, 272, 213, + 31, 560, 231, 758, 103, 271, 572, 436, 339, 730, 82, 285, + /* k = 50 */ + 923, 797, 576, 875, 156, 706, 63, 81, 257, 874, 411, 416, 778, 50, 205, 303, + 188, 535, 909, 155, 637, 230, 534, 96, 575, 102, 264, 233, 919, 593, 865, 26, + 579, 623, 766, 146, 10, 739, 246, 127, 71, 244, 211, 477, 920, 876, 427, 820, + 718, 435}; + + /* following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24728:2006 tables 10, 11 and 12 */ + private int[] RAPTable = {1, 8, 36, 19, 9, 25, 1, 1, 8, 36, 19, 9, 27, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, 47, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, 19, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, + 9, 8, 36, 19, 17, 33, 1, 9, 8, 36, 19, 17, 35, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, 43, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, + 0, 3, 6, 0, 6, 0, 0, 0, 3, 6, 0, 6, 6, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 3, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0}; + + private StringBuilder binaryString; + private int ecc; + private LinearEncoding symbology = LinearEncoding.CODE_128; + private String generalField; + private gfMode[] generalFieldType; + + ; + private int ccWidth; + private int[][] pwr928 = new int[69][7]; + private int[] codeWords = new int[180]; + private int codeWordCount; + private int[] bitStr = new int[13]; + private int[] inputData; + private CompositeMode ccMode; + private String linearContent; + private CompositeMode userPreferredMode = CompositeMode.CC_A; + private int targetBitsize; + private int remainder; + private int linearWidth; // Width of Code 128 linear + + public Composite() { + inputDataType = DataType.GS1; + } + + @Override + public void setDataType(DataType dummy) { + // Do nothing! + } + + /** + * Set the type of linear component included in the composite symbol, + * this will determine how the lower part of the symbol is encoded. + * + * @param linearSymbology The symbology of the linear component + */ + public void setSymbology(LinearEncoding linearSymbology) { + symbology = linearSymbology; + } + + /** + * Set the data to be encoded in the linear component of the composite + * symbol. + * + * @param input The linear data in GS1 format + */ + public void setLinear(String input) { + linearContent = input; + } + + /** + * Set the preferred encoding method for the 2D component of the + * composite symbol. This value may be ignored if the amount of data + * supplied is too big for the selected encoding. Mode CC-C can only be + * used with a Code 128 linear component. + * + * @param userMode Preferred mode + */ + public void setPreferredMode(CompositeMode userMode) { + userPreferredMode = userMode; + } + + @Override + public boolean encode() { + List linearRect = new ArrayList<>(); + List linearTxt = new ArrayList<>(); + List combineRect = new ArrayList<>(); + List combineTxt = new ArrayList<>(); + StringBuilder linearEncodeInfo = null; + String linearErrorMsg = ""; + int linearHeight = 0; + int topShift = 0; + int bottomShift = 0; + int maxX = 0; + int i; + linearWidth = 0; + + if (linearContent.isEmpty()) { + errorMsg.append("No linear data set"); + return false; + } + + // Manage composite component encoding first + if (!(encodeComposite())) { + return false; + } + + // Then encode linear component + try { + switch (symbology) { + case UPCA: + Upc upca = new Upc(); + upca.setMode(Upc.Mode.UPCA); + upca.setLinkageFlag(); + upca.setContent(linearContent); + linearRect = upca.getRectangles(); + linearTxt = upca.getTexts(); + linearHeight = upca.symbolHeight; + linearEncodeInfo = upca.encodeInfo; + topShift = 3; + break; + case UPCE: + Upc upce = new Upc(); + upce.setMode(Upc.Mode.UPCE); + upce.setLinkageFlag(); + upce.setContent(linearContent); + linearRect = upce.getRectangles(); + linearTxt = upce.getTexts(); + linearHeight = upce.symbolHeight; + linearEncodeInfo = upce.encodeInfo; + topShift = 3; + break; + case EAN: + Ean ean = new Ean(); + if (eanCalculateVersion() == 8) { + ean.setMode(Ean.Mode.EAN8); + bottomShift = 8; + } else { + ean.setMode(Ean.Mode.EAN13); + topShift = 3; + } + ean.setLinkageFlag(); + ean.setContent(linearContent); + linearRect = ean.getRectangles(); + linearTxt = ean.getTexts(); + linearHeight = ean.symbolHeight; + linearEncodeInfo = ean.encodeInfo; + break; + case CODE_128: + Code128 code128 = new Code128(); + switch (ccMode) { + case CC_A: + code128.setCca(); + break; + case CC_B: + code128.setCcb(); + break; + case CC_C: + code128.setCcc(); + bottomShift = 7; + break; + } + code128.setDataType(DataType.GS1); + code128.setContent(linearContent); + linearWidth = code128.symbolWidth; + linearRect = code128.getRectangles(); + linearTxt = code128.getTexts(); + linearHeight = code128.symbolHeight; + linearEncodeInfo = code128.encodeInfo; + break; + case DATABAR_14: + DataBar14 dataBar14 = new DataBar14(); + dataBar14.setLinkageFlag(); + dataBar14.setLinearMode(); + dataBar14.setContent(linearContent); + linearRect = dataBar14.getRectangles(); + linearTxt = dataBar14.getTexts(); + linearHeight = dataBar14.symbolHeight; + linearEncodeInfo = dataBar14.encodeInfo; + bottomShift = 4; + break; + case DATABAR_14_STACK_OMNI: + DataBar14 dataBar14SO = new DataBar14(); + dataBar14SO.setLinkageFlag(); + dataBar14SO.setOmnidirectionalMode(); + dataBar14SO.setContent(linearContent); + linearRect = dataBar14SO.getRectangles(); + linearTxt = dataBar14SO.getTexts(); + linearHeight = dataBar14SO.symbolHeight; + linearEncodeInfo = dataBar14SO.encodeInfo; + topShift = 1; + break; + case DATABAR_14_STACK: + DataBar14 dataBar14S = new DataBar14(); + dataBar14S.setLinkageFlag(); + dataBar14S.setStackedMode(); + dataBar14S.setContent(linearContent); + linearRect = dataBar14S.getRectangles(); + linearTxt = dataBar14S.getTexts(); + linearHeight = dataBar14S.symbolHeight; + linearEncodeInfo = dataBar14S.encodeInfo; + topShift = 1; + break; + case DATABAR_LIMITED: + DataBarLimited dataBarLimited = new DataBarLimited(); + dataBarLimited.setLinkageFlag(); + dataBarLimited.setContent(linearContent); + linearRect = dataBarLimited.getRectangles(); + linearTxt = dataBarLimited.getTexts(); + linearHeight = dataBarLimited.symbolHeight; + linearEncodeInfo = dataBarLimited.encodeInfo; + topShift = 1; + break; + case DATABAR_EXPANDED: + DataBarExpanded dataBarExpanded = new DataBarExpanded(); + dataBarExpanded.setLinkageFlag(); + dataBarExpanded.setNotStacked(); + dataBarExpanded.setContent(linearContent); + linearRect = dataBarExpanded.getRectangles(); + linearTxt = dataBarExpanded.getTexts(); + linearHeight = dataBarExpanded.symbolHeight; + linearEncodeInfo = dataBarExpanded.encodeInfo; + topShift = 2; + break; + case DATABAR_EXPANDED_STACK: + DataBarExpanded dataBarExpandedS = new DataBarExpanded(); + dataBarExpandedS.setLinkageFlag(); + dataBarExpandedS.setStacked(); + dataBarExpandedS.setContent(linearContent); + linearRect = dataBarExpandedS.getRectangles(); + linearTxt = dataBarExpandedS.getTexts(); + linearHeight = dataBarExpandedS.symbolHeight; + linearEncodeInfo = dataBarExpandedS.encodeInfo; + topShift = 2; + break; + default: + linearErrorMsg = "Linear symbol not recognised"; + break; + } + } catch (Exception e) { + linearErrorMsg = e.getMessage(); + } + + if (!linearErrorMsg.isEmpty()) { + errorMsg.append(linearErrorMsg); + return false; + } + + if ((ccMode == CompositeMode.CC_C) && (symbology == LinearEncoding.CODE_128)) { + /* Width of composite component depends on width of linear component, + so recalculate. */ + rowCount = 0; + getRectangles().clear(); + symbolHeight = 0; + symbolWidth = 0; + encodeInfo = new StringBuilder(); + if (!(encodeComposite())) { + return false; + } + } + + if ((ccMode != CompositeMode.CC_C) && (symbology == LinearEncoding.CODE_128)) { + if (linearWidth > symbolWidth) { + topShift = (linearWidth - symbolWidth) / 2; + } + } + + for (i = 0; i < getRectangles().size(); i++) { + Rectangle2D.Double comprect = new Rectangle2D.Double(getRectangles().get(i).x + topShift, + getRectangles().get(i).y, + getRectangles().get(i).width, + getRectangles().get(i).height); + if ((getRectangles().get(i).x + topShift + getRectangles().get(i).width) > maxX) { + maxX = (int) (getRectangles().get(i).x + topShift + getRectangles().get(i).width); + } + combineRect.add(comprect); + } + + for (i = 0; i < linearRect.size(); i++) { + Rectangle2D.Double linrect = new Rectangle2D.Double(linearRect.get(i).x + bottomShift, linearRect.get(i).y, linearRect.get(i).width, linearRect.get(i).height); + linrect.y += symbolHeight; + if ((linearRect.get(i).x + bottomShift + linearRect.get(i).width) > maxX) { + maxX = (int) (linearRect.get(i).x + bottomShift + linearRect.get(i).width); + } + combineRect.add(linrect); + } + + for (i = 0; i < linearTxt.size(); i++) { + double x = linearTxt.get(i).x + bottomShift; + double y = linearTxt.get(i).y + symbolHeight; + String text = linearTxt.get(i).text; + TextBox lintxt = new TextBox(x, y, text); + combineTxt.add(lintxt); + } + getRectangles().clear(); + getRectangles().addAll(combineRect); + getTexts().clear(); + getTexts().addAll(combineTxt); + symbolHeight += linearHeight; + symbolWidth = maxX; + + encodeInfo.append(linearEncodeInfo); + + return true; + } + + private boolean encodeComposite() { + + if (content.length() > 2990) { + errorMsg.append("2D component input data too long"); + return false; + } + + ccMode = userPreferredMode; + + if ((ccMode == CompositeMode.CC_C) && (symbology != LinearEncoding.CODE_128)) { + /* CC-C can only be used with a GS1-128 linear part */ + errorMsg.append("Invalid mode (CC-C only valid with GS1-128 linear component)"); + return false; + } + + switch (symbology) { + /* Determine width of 2D component according to ISO/IEC 24723 Table 1 */ + case EAN: + if (eanCalculateVersion() == 8) { + ccWidth = 3; + } else { + ccWidth = 4; + } + break; + case UPCE: + case DATABAR_14_STACK_OMNI: + case DATABAR_14_STACK: + ccWidth = 2; + break; + case DATABAR_LIMITED: + ccWidth = 3; + break; + case CODE_128: + case DATABAR_14: + case DATABAR_EXPANDED: + case UPCA: + case DATABAR_EXPANDED_STACK: + ccWidth = 4; + break; + } + + encodeInfo.append("Composite width: ").append(Integer.toString(ccWidth)).append("\n"); + + if (ccMode == CompositeMode.CC_A && !ccBinaryString()) { + ccMode = CompositeMode.CC_B; + } + + if (ccMode == CompositeMode.CC_B) { /* If the data didn't fit into CC-A it is recalculated for CC-B */ + if (!(ccBinaryString())) { + if (symbology != LinearEncoding.CODE_128) { + errorMsg.append("Input too long"); + return false; + } else { + ccMode = CompositeMode.CC_C; + } + } + } + + if (ccMode == CompositeMode.CC_C) { + /* If the data didn't fit in CC-B (and linear + * part is GS1-128) it is recalculated for CC-C */ + if (!(ccBinaryString())) { + errorMsg.append("Input too long"); + return false; + } + } + + switch (ccMode) { /* Note that ecc_level is only relevant to CC-C */ + case CC_A: + ccA(); + encodeInfo.append("Composite type: CC-A\n"); + break; + case CC_B: + ccB(); + encodeInfo.append("Composite type: CC-B\n"); + break; + case CC_C: + ccC(); + encodeInfo.append("Composite type: CC-C\n"); + break; + } + + plotSymbol(); + return true; + } + + private int eanCalculateVersion() { + /* Determine if EAN-8 or EAN-13 is being used */ + + int length = 0; + int i; + boolean latch; + + latch = true; + for (i = 0; i < linearContent.length(); i++) { + if ((linearContent.charAt(i) >= '0') && (linearContent.charAt(i) <= '9')) { + if (latch) { + length++; + } + } else { + latch = false; + } + } + + if (length <= 7) { + // EAN-8 + return 8; + } else { + // EAN-13 + return 13; + } + } + + private boolean calculateSymbolSize() { + int i; + int binaryLength = binaryString.length(); + if (ccMode == CompositeMode.CC_A) { + /* CC-A 2D component - calculate remaining space */ + switch (ccWidth) { + case 2: + if (binaryLength > 167) { + return true; + } + targetBitsize = 167; + if (binaryLength <= 138) { + targetBitsize = 138; + } + if (binaryLength <= 118) { + targetBitsize = 118; + } + if (binaryLength <= 108) { + targetBitsize = 108; + } + if (binaryLength <= 88) { + targetBitsize = 88; + } + if (binaryLength <= 78) { + targetBitsize = 78; + } + if (binaryLength <= 59) { + targetBitsize = 59; + } + break; + case 3: + if (binaryLength > 167) { + return true; + } + targetBitsize = 167; + if (binaryLength <= 138) { + targetBitsize = 138; + } + if (binaryLength <= 118) { + targetBitsize = 118; + } + if (binaryLength <= 98) { + targetBitsize = 98; + } + if (binaryLength <= 78) { + targetBitsize = 78; + } + break; + case 4: + if (binaryLength > 197) { + return true; + } + targetBitsize = 197; + if (binaryLength <= 167) { + targetBitsize = 167; + } + if (binaryLength <= 138) { + targetBitsize = 138; + } + if (binaryLength <= 108) { + targetBitsize = 108; + } + if (binaryLength <= 78) { + targetBitsize = 78; + } + break; + } + } + + if (ccMode == CompositeMode.CC_B) { + /* CC-B 2D component - calculated from ISO/IEC 24728 Table 1 */ + switch (ccWidth) { + case 2: + if (binaryLength > 336) { + return true; + } + targetBitsize = 336; + if (binaryLength <= 296) { + targetBitsize = 296; + } + if (binaryLength <= 256) { + targetBitsize = 256; + } + if (binaryLength <= 208) { + targetBitsize = 208; + } + if (binaryLength <= 160) { + targetBitsize = 160; + } + if (binaryLength <= 104) { + targetBitsize = 104; + } + if (binaryLength <= 56) { + targetBitsize = 56; + } + break; + case 3: + if (binaryLength > 768) { + return true; + } + targetBitsize = 768; + if (binaryLength <= 648) { + targetBitsize = 648; + } + if (binaryLength <= 536) { + targetBitsize = 536; + } + if (binaryLength <= 416) { + targetBitsize = 416; + } + if (binaryLength <= 304) { + targetBitsize = 304; + } + if (binaryLength <= 208) { + targetBitsize = 208; + } + if (binaryLength <= 152) { + targetBitsize = 152; + } + if (binaryLength <= 112) { + targetBitsize = 112; + } + if (binaryLength <= 72) { + targetBitsize = 72; + } + if (binaryLength <= 32) { + targetBitsize = 32; + } + break; + case 4: + if (binaryLength > 1184) { + return true; + } + targetBitsize = 1184; + if (binaryLength <= 1016) { + targetBitsize = 1016; + } + if (binaryLength <= 840) { + targetBitsize = 840; + } + if (binaryLength <= 672) { + targetBitsize = 672; + } + if (binaryLength <= 496) { + targetBitsize = 496; + } + if (binaryLength <= 352) { + targetBitsize = 352; + } + if (binaryLength <= 264) { + targetBitsize = 264; + } + if (binaryLength <= 208) { + targetBitsize = 208; + } + if (binaryLength <= 152) { + targetBitsize = 152; + } + if (binaryLength <= 96) { + targetBitsize = 96; + } + if (binaryLength <= 56) { + targetBitsize = 56; + } + break; + } + } + + if (ccMode == CompositeMode.CC_C) { + /* CC-C 2D Component is a bit more complex! */ + int byteLength, codewordsUsed, eccLevel, eccCodewords, rows; + int codewordsTotal, targetCodewords, targetBytesize; + + byteLength = binaryLength / 8; + if (binaryLength % 8 != 0) { + byteLength++; + } + + codewordsUsed = (byteLength / 6) * 5; + codewordsUsed += byteLength % 6; + + eccLevel = 7; + if (codewordsUsed <= 1280) { + eccLevel = 6; + } + if (codewordsUsed <= 640) { + eccLevel = 5; + } + if (codewordsUsed <= 320) { + eccLevel = 4; + } + if (codewordsUsed <= 160) { + eccLevel = 3; + } + if (codewordsUsed <= 40) { + eccLevel = 2; + } + ecc = eccLevel; + eccCodewords = 1; + for (i = 1; i <= (eccLevel + 1); i++) { + eccCodewords *= 2; + } + + codewordsUsed += eccCodewords; + codewordsUsed += 3; + + if (linearWidth == 0) { + /* Linear component not yet calculated */ + ccWidth = (int) (0.5 + Math.sqrt((codewordsUsed) / 3.0)); + } else { + ccWidth = (linearWidth - 53) / 17; + } + + if ((codewordsUsed / ccWidth) > 90) { + /* stop the symbol from becoming too high */ + ccWidth = ccWidth + 1; + } + + rows = codewordsUsed / ccWidth; + if (codewordsUsed % ccWidth != 0) { + rows++; + } + + while (ccWidth > (3 * rows)) { + /* stop the symbol from becoming too wide (section 10) */ + ccWidth--; + + rows = codewordsUsed / ccWidth; + if (codewordsUsed % ccWidth != 0) { + rows++; + } + } + ; + + codewordsTotal = ccWidth * rows; + + targetCodewords = codewordsTotal - eccCodewords; + targetCodewords -= 3; + + targetBytesize = 6 * (targetCodewords / 5); + targetBytesize += targetCodewords % 5; + + targetBitsize = 8 * targetBytesize; + } + + remainder = targetBitsize - binaryLength; + return false; + } + + private boolean ccBinaryString() { + /* Handles all data encoding from section 5 of ISO/IEC 24723 */ + int encodingMethod, readPosn, d1, d2, value, alphaPad; + int i, j, aiCrop, fnc1Latch; + int groupVal; + int ai90Mode; + boolean latch; + int alpha, alphanum, numeric, test1, test2, test3, nextAiPosn; + int numericValue, table3Letter; + String numericPart; + String ninety; + int latchOffset; + + encodingMethod = 1; + readPosn = 0; + aiCrop = 0; + fnc1Latch = 0; + alphaPad = 0; + ai90Mode = 0; + ecc = 0; + value = 0; + targetBitsize = 0; + + if ((content.charAt(0) == '1') && ((content.charAt(1) == '0') || (content.charAt(1) == '1') || (content.charAt(1) == '7')) && (content.length() >= 8)) { + /* Source starts (10), (11) or (17) */ + encodingMethod = 2; + } + + if ((content.charAt(0) == '9') && (content.charAt(1) == '0')) { + /* Source starts (90) */ + encodingMethod = 3; + } + + encodeInfo.append("Composite Encodation: "); + switch (encodingMethod) { + case 1: + encodeInfo.append("0\n"); + break; + case 2: + encodeInfo.append("10\n"); + break; + case 3: + encodeInfo.append("11\n"); + break; + } + + binaryString = new StringBuilder(); + + if (encodingMethod == 1) { + binaryString.append("0"); + } + + if (encodingMethod == 2) { + /* Encoding Method field "10" - date and lot number */ + + binaryString.append("10"); + + if (content.charAt(1) == '0') { + /* No date data */ + binaryString.append("11"); + readPosn = 2; + } else { + /* Production Date (11) or Expiration Date (17) */ + groupVal = ((10 * (content.charAt(2) - '0')) + (content.charAt(3) - '0')) * 384; + groupVal += (((10 * (content.charAt(4) - '0')) + (content.charAt(5) - '0')) - 1) * 32; + groupVal += (10 * (content.charAt(6) - '0')) + (content.charAt(7) - '0'); + + for (j = 0; j < 16; j++) { + if ((groupVal & (0x8000 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + + if (content.charAt(1) == '1') { + /* Production Date AI 11 */ + binaryString.append("0"); + } else { + /* Expiration Date AI 17 */ + binaryString.append("1"); + } + readPosn = 8; + } + + if ((readPosn + 2) < content.length()) { + if ((content.charAt(readPosn) == '1') && (content.charAt(readPosn + 1) == '0')) { + /* Followed by AI 10 - strip this from general field */ + readPosn += 2; + } else { + /* An FNC1 character needs to be inserted in the general field */ + fnc1Latch = 1; + } + } else { + fnc1Latch = 1; + } + } + + if (encodingMethod == 3) { + /* Encodation Method field of "11" - AI 90 */ + /* "This encodation method may be used if an element string with an AI + 90 occurs at the start of the data message, and if the data field + following the two-digit AI 90 starts with an alphanumeric string which + complies with a specific format." (para 5.2.2) */ + + j = content.length(); + for (i = content.length(); i > 2; i--) { + if (content.charAt(i - 1) == '[') { + j = i; + } + } + + ninety = content.substring(2, j - 1); + + + /* Find out if the AI 90 data is alphabetic or numeric or both */ + + alpha = 0; + alphanum = 0; + numeric = 0; + + for (i = 0; i < ninety.length(); i++) { + + if ((ninety.charAt(i) >= 'A') && (ninety.charAt(i) <= 'Z')) { + /* Character is alphabetic */ + alpha += 1; + } + + if ((ninety.charAt(i) >= '0') && (ninety.charAt(i) <= '9')) { + /* Character is numeric */ + numeric += 1; + } + + switch (ninety.charAt(i)) { + case '*': + case ',': + case '-': + case '.': + case '/': + alphanum += 1; + break; + } + + if (!(((ninety.charAt(i) >= '0') && (ninety.charAt(i) <= '9')) || ((ninety.charAt(i) >= 'A') && (ninety.charAt(i) <= 'Z')))) { + if ((ninety.charAt(i) != '*') && (ninety.charAt(i) != ',') && (ninety.charAt(i) != '-') && (ninety.charAt(i) != '.') && (ninety.charAt(i) != '/')) { + /* An Invalid AI 90 character */ + errorMsg.append("Invalid AI 90 data"); + return false; + } + } + } + + /* must start with 0, 1, 2 or 3 digits followed by an uppercase character */ + test1 = -1; + for (i = 3; i >= 0; i--) { + if ((ninety.charAt(i) >= 'A') && (ninety.charAt(i) <= 'Z')) { + test1 = i; + } + } + + test2 = 0; + for (i = 0; i < test1; i++) { + if (!((ninety.charAt(i) >= '0') && (ninety.charAt(i) <= '9'))) { + test2 = 1; + } + } + + /* leading zeros are not permitted */ + test3 = 0; + if ((test1 >= 1) && (ninety.charAt(0) == '0')) { + test3 = 1; + } + + if ((test1 != -1) && (test2 != 1) && (test3 == 0)) { + /* Encodation method "11" can be used */ + binaryString.append("11"); + + numeric -= test1; + alpha--; + + /* Decide on numeric, alpha or alphanumeric mode */ + /* Alpha mode is a special mode for AI 90 */ + + if (alphanum > 0) { + /* Alphanumeric mode */ + binaryString.append("0"); + ai90Mode = 1; + } else { + if (alpha > numeric) { + /* Alphabetic mode */ + binaryString.append("11"); + ai90Mode = 2; + } else { + /* Numeric mode */ + binaryString.append("10"); + ai90Mode = 3; + } + } + + nextAiPosn = 2 + ninety.length(); + + if (content.charAt(nextAiPosn) == '[') { + /* There are more AIs afterwords */ + if ((content.charAt(nextAiPosn + 1) == '2') && (content.charAt(nextAiPosn + 2) == '1')) { + /* AI 21 follows */ + aiCrop = 1; + } + + if ((content.charAt(nextAiPosn + 1) == '8') && (content.charAt(nextAiPosn + 2) == '0') && (content.charAt(nextAiPosn + 3) == '0') && (content.charAt(nextAiPosn + 4) == '4')) { + /* AI 8004 follows */ + aiCrop = 2; + } + } + + switch (aiCrop) { + case 0: + binaryString.append("0"); + break; + case 1: + binaryString.append("10"); + break; + case 2: + binaryString.append("11"); + break; + } + + if (test1 == 0) { + numericPart = "0"; + } else { + numericPart = ninety.substring(0, test1); + } + + numericValue = 0; + for (i = 0; i < numericPart.length(); i++) { + numericValue *= 10; + numericValue += numericPart.charAt(i) - '0'; + } + + table3Letter = -1; + if (numericValue < 31) { + switch (ninety.charAt(test1)) { + case 'B': + table3Letter = 0; + break; + case 'D': + table3Letter = 1; + break; + case 'H': + table3Letter = 2; + break; + case 'I': + table3Letter = 3; + break; + case 'J': + table3Letter = 4; + break; + case 'K': + table3Letter = 5; + break; + case 'L': + table3Letter = 6; + break; + case 'N': + table3Letter = 7; + break; + case 'P': + table3Letter = 8; + break; + case 'Q': + table3Letter = 9; + break; + case 'R': + table3Letter = 10; + break; + case 'S': + table3Letter = 11; + break; + case 'T': + table3Letter = 12; + break; + case 'V': + table3Letter = 13; + break; + case 'W': + table3Letter = 14; + break; + case 'Z': + table3Letter = 15; + break; + } + } + + if (table3Letter != -1) { + /* Encoding can be done according to 5.2.2 c) 2) */ + /* five bit binary string representing value before letter */ + for (j = 0; j < 5; j++) { + if ((numericValue & (0x10 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + + /* followed by four bit representation of letter from Table 3 */ + for (j = 0; j < 4; j++) { + if ((table3Letter & (0x08 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } else { + /* Encoding is done according to 5.2.2 c) 3) */ + binaryString.append("11111"); + /* ten bit representation of number */ + for (j = 0; j < 10; j++) { + if ((numericValue & (0x200 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + + /* five bit representation of ASCII character */ + for (j = 0; j < 5; j++) { + if (((ninety.charAt(test1) - 65) & (0x10 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + readPosn = test1 + 3; + } else { + /* Use general field encodation instead */ + binaryString.append("0"); + readPosn = 0; + } + + + /* Now encode the rest of the AI 90 data field */ + if (ai90Mode == 2) { + /* Alpha encodation (section 5.2.3) */ + do { + if ((content.charAt(readPosn) >= '0') && (content.charAt(readPosn) <= '9')) { + for (j = 0; j < 5; j++) { + if (((content.charAt(readPosn) + 4) & (0x10 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if ((content.charAt(readPosn) >= 'A') && (content.charAt(readPosn) <= 'Z')) { + for (j = 0; j < 6; j++) { + if (((content.charAt(readPosn) - 65) & (0x20 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if (content.charAt(readPosn) == '[') { + binaryString.append("11111"); + } + + readPosn++; + } while ((content.charAt(readPosn - 1) != '[') && (readPosn < content.length())); + alphaPad = 1; /* This is overwritten if a general field is encoded */ + } + + if (ai90Mode == 1) { + /* Alphanumeric mode */ + do { + if ((content.charAt(readPosn) >= '0') && (content.charAt(readPosn) <= '9')) { + for (j = 0; j < 5; j++) { + if (((content.charAt(readPosn) - 43) & (0x10 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if ((content.charAt(readPosn) >= 'A') && (content.charAt(readPosn) <= 'Z')) { + for (j = 0; j < 6; j++) { + if (((content.charAt(readPosn) - 33) & (0x20 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + switch (content.charAt(readPosn)) { + case '[': + binaryString.append("01111"); + break; + case '*': + binaryString.append("111010"); + break; + case ',': + binaryString.append("111011"); + break; + case '-': + binaryString.append("111100"); + break; + case '.': + binaryString.append("111101"); + break; + case '/': + binaryString.append("111110"); + break; + } + + readPosn++; + } while ((content.charAt(readPosn - 1) != '[') && (content.charAt(readPosn - 1) != '\0')); + } + + readPosn += (2 * aiCrop); + } + + + /* The compressed data field has been processed if appropriate - the + rest of the data (if any) goes into a general-purpose data compaction field */ + + j = 0; + generalField = ""; + if (fnc1Latch == 1) { + /* Encodation method "10" has been used but it is not followed by + AI 10, so a FNC1 character needs to be added */ + generalField += "["; + } + + generalField += content.substring(readPosn); + + + latch = false; + if (generalField.length() != 0) { + alphaPad = 0; + + + generalFieldType = new gfMode[generalField.length()]; + + for (i = 0; i < generalField.length(); i++) { + /* Table 13 - ISO/IEC 646 encodation */ + if ((generalField.charAt(i) < ' ') || (generalField.charAt(i) > 'z')) { + generalFieldType[i] = gfMode.INVALID_CHAR; + latch = true; + } else { + generalFieldType[i] = gfMode.ISOIEC; + } + + if (generalField.charAt(i) == '#') { + generalFieldType[i] = gfMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == '$') { + generalFieldType[i] = gfMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == '@') { + generalFieldType[i] = gfMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == 92) { + generalFieldType[i] = gfMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == '^') { + generalFieldType[i] = gfMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == 96) { + generalFieldType[i] = gfMode.INVALID_CHAR; + latch = true; + } + + /* Table 12 - Alphanumeric encodation */ + if ((generalField.charAt(i) >= 'A') && (generalField.charAt(i) <= 'Z')) { + generalFieldType[i] = gfMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '*') { + generalFieldType[i] = gfMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == ',') { + generalFieldType[i] = gfMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '-') { + generalFieldType[i] = gfMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '.') { + generalFieldType[i] = gfMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '/') { + generalFieldType[i] = gfMode.ALPHA_OR_ISO; + } + + /* Numeric encodation */ + if ((generalField.charAt(i) >= '0') && (generalField.charAt(i) <= '9')) { + generalFieldType[i] = gfMode.ANY_ENC; + } + if (generalField.charAt(i) == '[') { + /* FNC1 can be encoded in any system */ + generalFieldType[i] = gfMode.ANY_ENC; + } + + } + + if (latch) { + /* Invalid characters in input data */ + errorMsg.append("Invalid characters in input data"); + return false; + } + + for (i = 0; i < generalField.length() - 1; i++) { + if ((generalFieldType[i] == gfMode.ISOIEC) && (generalField.charAt(i + 1) == '[')) { + generalFieldType[i + 1] = gfMode.ISOIEC; + } + } + + for (i = 0; i < generalField.length() - 1; i++) { + if ((generalFieldType[i] == gfMode.ALPHA_OR_ISO) && (generalField.charAt(i + 1) == '[')) { + generalFieldType[i + 1] = gfMode.ALPHA_OR_ISO; + } + } + + latch = applyGeneralFieldRules(); + + i = 0; + do { + switch (generalFieldType[i]) { + case NUMERIC: + if (i != 0) { + if ((generalFieldType[i - 1] != gfMode.NUMERIC) && (generalField.charAt(i - 1) != '[')) { + binaryString.append("000"); /* Numeric latch */ + } + } + + if (generalField.charAt(i) != '[') { + d1 = generalField.charAt(i) - '0'; + } else { + d1 = 10; + } + + if (i < generalField.length() - 1) { + if (generalField.charAt(i + 1) != '[') { + d2 = generalField.charAt(i + 1) - '0'; + } else { + d2 = 10; + } + } else { + d2 = 10; + } + + if ((d1 != 10) || (d2 != 10)) { + /* If (d1==10)&&(d2==10) then input is either FNC1,FNC1 or FNC1,EOL */ + value = (11 * d1) + d2 + 8; + + for (j = 0; j < 7; j++) { + if ((value & 0x40 >> j) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + + i += 2; + } + break; + + case ALPHA: + if (i != 0) { + if ((generalFieldType[i - 1] == gfMode.NUMERIC) || (generalField.charAt(i - 1) == '[')) { + binaryString.append("0000"); /* Alphanumeric latch */ + } + if (generalFieldType[i - 1] == gfMode.ISOIEC) { + binaryString.append("00100"); /* ISO/IEC 646 latch */ + } + } + + if ((generalField.charAt(i) >= '0') && (generalField.charAt(i) <= '9')) { + + value = generalField.charAt(i) - 43; + + for (j = 0; j < 5; j++) { + if ((value & (0x10 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if ((generalField.charAt(i) >= 'A') && (generalField.charAt(i) <= 'Z')) { + + value = generalField.charAt(i) - 33; + + for (j = 0; j < 6; j++) { + if ((value & (0x20 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if (generalField.charAt(i) == '[') { + binaryString.append("01111"); /* FNC1/Numeric latch */ + } + if (generalField.charAt(i) == '*') { + binaryString.append("111010"); /* asterisk */ + } + if (generalField.charAt(i) == ',') { + binaryString.append("111011"); /* comma */ + } + if (generalField.charAt(i) == '-') { + binaryString.append("111100"); /* minus or hyphen */ + } + if (generalField.charAt(i) == '.') { + binaryString.append("111101"); /* period or full stop */ + } + if (generalField.charAt(i) == '/') { + binaryString.append("111110"); /* slash or solidus */ + } + + i++; + break; + + case ISOIEC: + if (i != 0) { + if ((generalFieldType[i - 1] == gfMode.NUMERIC) || (generalField.charAt(i - 1) == '[')) { + binaryString.append("0000"); /* Alphanumeric latch */ + binaryString.append("00100"); /* ISO/IEC 646 latch */ + } + if (generalFieldType[i - 1] == gfMode.ALPHA) { + binaryString.append("00100"); /* ISO/IEC 646 latch */ + } + } + + if ((generalField.charAt(i) >= '0') && (generalField.charAt(i) <= '9')) { + + value = generalField.charAt(i) - 43; + + for (j = 0; j < 5; j++) { + if ((value & (0x10 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if ((generalField.charAt(i) >= 'A') && (generalField.charAt(i) <= 'Z')) { + + value = generalField.charAt(i) - 1; + + for (j = 0; j < 7; j++) { + if ((value & (0x40 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if ((generalField.charAt(i) >= 'a') && (generalField.charAt(i) <= 'z')) { + + value = generalField.charAt(i) - 7; + + for (j = 0; j < 7; j++) { + if ((value & (0x40 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if (generalField.charAt(i) == '[') { + binaryString.append("01111"); /* FNC1/Numeric latch */ + } + if (generalField.charAt(i) == '!') { + binaryString.append("11101000"); /* exclamation mark */ + } + if (generalField.charAt(i) == 34) { + binaryString.append("11101001"); /* quotation mark */ + } + if (generalField.charAt(i) == 37) { + binaryString.append("11101010"); /* percent sign */ + } + if (generalField.charAt(i) == '&') { + binaryString.append("11101011"); /* ampersand */ + } + if (generalField.charAt(i) == 39) { + binaryString.append("11101100"); /* apostrophe */ + } + if (generalField.charAt(i) == '(') { + binaryString.append("11101101"); /* left parenthesis */ + } + if (generalField.charAt(i) == ')') { + binaryString.append("11101110"); /* right parenthesis */ + } + if (generalField.charAt(i) == '*') { + binaryString.append("11101111"); /* asterisk */ + } + if (generalField.charAt(i) == '+') { + binaryString.append("11110000"); /* plus sign */ + } + if (generalField.charAt(i) == ',') { + binaryString.append("11110001"); /* comma */ + } + if (generalField.charAt(i) == '-') { + binaryString.append("11110010"); /* minus or hyphen */ + } + if (generalField.charAt(i) == '.') { + binaryString.append("11110011"); /* period or full stop */ + } + if (generalField.charAt(i) == '/') { + binaryString.append("11110100"); /* slash or solidus */ + } + if (generalField.charAt(i) == ':') { + binaryString.append("11110101"); /* colon */ + } + if (generalField.charAt(i) == ';') { + binaryString.append("11110110"); /* semicolon */ + } + if (generalField.charAt(i) == '<') { + binaryString.append("11110111"); /* less-than sign */ + } + if (generalField.charAt(i) == '=') { + binaryString.append("11111000"); /* equals sign */ + } + if (generalField.charAt(i) == '>') { + binaryString.append("11111001"); /* greater-than sign */ + } + if (generalField.charAt(i) == '?') { + binaryString.append("11111010"); /* question mark */ + } + if (generalField.charAt(i) == '_') { + binaryString.append("11111011"); /* underline or low line */ + } + if (generalField.charAt(i) == ' ') { + binaryString.append("11111100"); /* space */ + } + i++; + break; + } + + + latchOffset = 0; + if (latch) { + latchOffset = 1; + } + } while ((i + latchOffset) < generalField.length()); + } + + if (calculateSymbolSize()) { + return false; + } + + if (latch) { + i = generalField.length() - 1; + /* There is still one more numeric digit to encode */ + + if (generalField.charAt(i) == '[') { + binaryString.append("000001111"); + } else { + if ((remainder >= 4) && (remainder <= 6)) { + d1 = generalField.charAt(i) - '0'; + for (j = 0; j < 4; j++) { + if ((value & (0x08 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } else { + d1 = generalField.charAt(i) - '0'; + d2 = 10; + + value = (11 * d1) + d2 + 8; + + for (j = 0; j < 7; j++) { + if ((value & (0x40 >> j)) == 0x00) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + /* This may push the symbol up to the next size */ + } + } + } + + if (binaryString.length() > 11805) { /* (2361 * 5) */ + errorMsg.append("Input too long"); + return false; + } + + /* size of the symbol may have changed when adding data in the above sequence */ + if (calculateSymbolSize()) { + return false; + } + + encodeInfo.append("Composite Binary Length: ").append(Integer.toString(binaryString.length())).append("\n"); + displayBinaryString(); + + if (binaryString.length() < targetBitsize) { + /* Now add padding to binary string */ + if (alphaPad == 1) { + binaryString.append("11111"); + /* Extra FNC1 character required after Alpha encodation (section 5.2.3) */ + } + + if ((generalField.length() != 0) && (generalFieldType[generalField.length() - 1] == gfMode.NUMERIC)) { + binaryString.append("0000"); + } + + while (binaryString.length() < targetBitsize) { + binaryString.append("00100"); + } + + binaryString = new StringBuilder(binaryString.substring(0, targetBitsize)); + } + + return true; + } + + private void displayBinaryString() { + int i, nibble; + /* Display binary string as hexadecimal */ + + encodeInfo.append("Composite Binary String: "); + nibble = 0; + for (i = 0; i < binaryString.length(); i++) { + switch (i % 4) { + case 0: + if (binaryString.charAt(i) == '1') { + nibble += 8; + } + break; + case 1: + if (binaryString.charAt(i) == '1') { + nibble += 4; + } + break; + case 2: + if (binaryString.charAt(i) == '1') { + nibble += 2; + } + break; + case 3: + if (binaryString.charAt(i) == '1') { + nibble += 1; + } + encodeInfo.append(Integer.toHexString(nibble)); + nibble = 0; + break; + } + } + + if ((binaryString.length() % 4) != 0) { + encodeInfo.append(Integer.toHexString(nibble)); + } + encodeInfo.append("\n"); + } + + private boolean applyGeneralFieldRules() { + /* Attempts to apply encoding rules from secions 7.2.5.5.1 to 7.2.5.5.3 + of ISO/IEC 24724:2006 */ + + int blockCount, i, j, k; + gfMode current, next, last; + int[] blockLength = new int[200]; + gfMode[] blockType = new gfMode[200]; + + blockCount = 0; + + blockLength[blockCount] = 1; + blockType[blockCount] = generalFieldType[0]; + + for (i = 1; i < generalField.length(); i++) { + current = generalFieldType[i]; + last = generalFieldType[i - 1]; + + if (current == last) { + blockLength[blockCount] = blockLength[blockCount] + 1; + } else { + blockCount++; + blockLength[blockCount] = 1; + blockType[blockCount] = generalFieldType[i]; + } + } + + blockCount++; + + for (i = 0; i < blockCount; i++) { + current = blockType[i]; + next = blockType[i + 1]; + + if ((current == gfMode.ISOIEC) && (i != (blockCount - 1))) { + if ((next == gfMode.ANY_ENC) && (blockLength[i + 1] >= 4)) { + blockType[i + 1] = gfMode.NUMERIC; + } + if ((next == gfMode.ANY_ENC) && (blockLength[i + 1] < 4)) { + blockType[i + 1] = gfMode.ISOIEC; + } + if ((next == gfMode.ALPHA_OR_ISO) && (blockLength[i + 1] >= 5)) { + blockType[i + 1] = gfMode.ALPHA; + } + if ((next == gfMode.ALPHA_OR_ISO) && (blockLength[i + 1] < 5)) { + blockType[i + 1] = gfMode.ISOIEC; + } + } + + if (current == gfMode.ALPHA_OR_ISO) { + blockType[i] = gfMode.ALPHA; + } + + if ((current == gfMode.ALPHA) && (i != (blockCount - 1))) { + if ((next == gfMode.ANY_ENC) && (blockLength[i + 1] >= 6)) { + blockType[i + 1] = gfMode.NUMERIC; + } + if ((next == gfMode.ANY_ENC) && (blockLength[i + 1] < 6)) { + if ((i == blockCount - 2) && (blockLength[i + 1] >= 4)) { + blockType[i + 1] = gfMode.NUMERIC; + } else { + blockType[i + 1] = gfMode.ALPHA; + } + } + } + + if (current == gfMode.ANY_ENC) { + blockType[i] = gfMode.NUMERIC; + } + } + + if (blockCount > 1) { + i = 1; + while (i < blockCount) { + if (blockType[i - 1] == blockType[i]) { + /* bring together */ + blockLength[i - 1] = blockLength[i - 1] + blockLength[i]; + j = i + 1; + + /* decreace the list */ + while (j < blockCount) { + blockLength[j - 1] = blockLength[j]; + blockType[j - 1] = blockType[j]; + j++; + } + blockCount--; + i--; + } + i++; + } + } + + for (i = 0; i < blockCount - 1; i++) { + if ((blockType[i] == gfMode.NUMERIC) && ((blockLength[i] & 1) != 0)) { + /* Odd size numeric block */ + blockLength[i] = blockLength[i] - 1; + blockLength[i + 1] = blockLength[i + 1] + 1; + } + } + + j = 0; + for (i = 0; i < blockCount; i++) { + for (k = 0; k < blockLength[i]; k++) { + generalFieldType[j] = blockType[i]; + j++; + } + } + + /* If the last block is numeric and an odd size, further + processing needs to be done outside this procedure */ + return (blockType[blockCount - 1] == gfMode.NUMERIC) && ((blockLength[blockCount - 1] & 1) != 0); + } + + private void ccA() { + /* CC-A 2D component */ + int i, strpos, segment, cwCnt, variant, rows; + int k, offset, j, total; + int[] rsCodeWords = new int[8]; + int LeftRAPStart, RightRAPStart, CentreRAPStart, StartCluster; + int LeftRAP, RightRAP, CentreRAP, Cluster; + int[] dummy = new int[5]; + int flip, loop; + String codebarre; + StringBuilder bin; + StringBuilder localSource; /* A copy of source but with padding zeroes to make 208 bits */ + + variant = 0; + + for (i = 0; i < 13; i++) { + bitStr[i] = 0; + } + for (i = 0; i < 28; i++) { + codeWords[i] = 0; + } + + localSource = binaryString; + for (i = binaryString.length(); i < 208; i++) { + localSource.append("0"); + } + + for (segment = 0; segment < 13; segment++) { + strpos = segment * 16; + bitStr[segment] = 0; + for (i = 0; i < 16; i++) { + if (localSource.charAt(strpos + i) == '1') { + bitStr[segment] += 0x8000 >> i; + } + } + } + + init928(); + /* encode codeWords from bitStr */ + cwCnt = encode928(binaryString.length()); + + switch (ccWidth) { + case 2: + switch (cwCnt) { + case 6: + variant = 0; + break; + case 8: + variant = 1; + break; + case 9: + variant = 2; + break; + case 11: + variant = 3; + break; + case 12: + variant = 4; + break; + case 14: + variant = 5; + break; + case 17: + variant = 6; + break; + } + break; + case 3: + switch (cwCnt) { + case 8: + variant = 7; + break; + case 10: + variant = 8; + break; + case 12: + variant = 9; + break; + case 14: + variant = 10; + break; + case 17: + variant = 11; + break; + } + break; + case 4: + switch (cwCnt) { + case 8: + variant = 12; + break; + case 11: + variant = 13; + break; + case 14: + variant = 14; + break; + case 17: + variant = 15; + break; + case 20: + variant = 16; + break; + } + break; + } + + rows = ccaVariants[variant]; + k = ccaVariants[17 + variant]; + offset = ccaVariants[34 + variant]; + + /* Reed-Solomon error correction */ + + for (i = 0; i < 8; i++) { + rsCodeWords[i] = 0; + } + total = 0; + encodeInfo.append("Composite Codewords: "); + for (i = 0; i < cwCnt; i++) { + total = (codeWords[i] + rsCodeWords[k - 1]) % 929; + for (j = k - 1; j >= 0; j--) { + if (j == 0) { + rsCodeWords[j] = (929 - (total * ccaCoeffs[offset + j]) % 929) % 929; + } else { + rsCodeWords[j] = (rsCodeWords[j - 1] + 929 - (total * ccaCoeffs[offset + j]) % 929) % 929; + } + } + encodeInfo.append(Integer.toString(codeWords[i])).append(" "); + } + encodeInfo.append("\n"); + + for (j = 0; j < k; j++) { + if (rsCodeWords[j] != 0) { + rsCodeWords[j] = 929 - rsCodeWords[j]; + } + } + + for (i = k - 1; i >= 0; i--) { + codeWords[cwCnt] = rsCodeWords[i]; + cwCnt++; + } + + /* Place data into table */ + LeftRAPStart = aRAPTable[variant]; + CentreRAPStart = aRAPTable[variant + 17]; + RightRAPStart = aRAPTable[variant + 34]; + StartCluster = aRAPTable[variant + 51] / 3; + + LeftRAP = LeftRAPStart; + CentreRAP = CentreRAPStart; + RightRAP = RightRAPStart; + Cluster = StartCluster; /* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and Cluster(6) */ + + readable = new StringBuilder(); + rowCount = rows; + pattern = new String[rowCount]; + rowHeight = new int[rowCount]; + + for (i = 0; i < rows; i++) { + codebarre = ""; + offset = 929 * Cluster; + for (j = 0; j < 5; j++) { + dummy[j] = 0; + } + for (j = 0; j < ccWidth; j++) { + dummy[j + 1] = codeWords[i * ccWidth + j]; + } + /* Copy the data into codebarre */ + codebarre += RAPLR[LeftRAP]; + codebarre += "1"; + codebarre += codagemc[offset + dummy[1]]; + codebarre += "1"; + if (ccWidth == 3) { + codebarre += RAPC[CentreRAP]; + } + if (ccWidth >= 2) { + codebarre += "1"; + codebarre += codagemc[offset + dummy[2]]; + codebarre += "1"; + } + if (ccWidth == 4) { + codebarre += RAPC[CentreRAP]; + } + if (ccWidth >= 3) { + codebarre += "1"; + codebarre += codagemc[offset + dummy[3]]; + codebarre += "1"; + } + if (ccWidth == 4) { + codebarre += "1"; + codebarre += codagemc[offset + dummy[4]]; + codebarre += "1"; + } + codebarre += RAPLR[RightRAP]; + codebarre += "1"; /* stop */ + + /* Now codebarre is a mixture of letters and numbers */ + + flip = 1; + bin = new StringBuilder(); + for (loop = 0; loop < codebarre.length(); loop++) { + if ((codebarre.charAt(loop) >= '0') && (codebarre.charAt(loop) <= '9')) { + for (k = 0; k < codebarre.charAt(loop) - '0'; k++) { + if (flip == 0) { + bin.append('0'); + } else { + bin.append('1'); + } + } + if (flip == 0) { + flip = 1; + } else { + flip = 0; + } + } else { + bin.append(PDFttf[positionOf(codebarre.charAt(loop), brSet)]); + } + } + + rowHeight[i] = 2; + pattern[i] = bin2pat(bin.toString()); + + /* Set up RAPs and Cluster for next row */ + LeftRAP++; + CentreRAP++; + RightRAP++; + Cluster++; + + if (LeftRAP == 53) { + LeftRAP = 1; + } + if (CentreRAP == 53) { + CentreRAP = 1; + } + if (RightRAP == 53) { + RightRAP = 1; + } + if (Cluster == 3) { + Cluster = 0; + } + } + } + + /* initialize pwr928 encoding table */ + private void init928() { + int i, j, v; + int[] cw = new int[7]; + cw[6] = 1; + for (i = 5; i >= 0; i--) { + cw[i] = 0; + } + + for (i = 0; i < 7; i++) { + pwr928[0][i] = cw[i]; + } + for (j = 1; j < 69; j++) { + for (v = 0, i = 6; i >= 1; i--) { + v = (2 * cw[i]) + (v / 928); + pwr928[j][i] = cw[i] = v % 928; + } + pwr928[j][0] = cw[0] = (2 * cw[0]) + (v / 928); + } + } + + /* converts bit string to base 928 values, codeWords[0] is highest order */ + int encode928(int bitLng) { + int i, j, b, bitCnt, cwNdx, cwCnt, cwLng; + for (cwNdx = cwLng = b = 0; b < bitLng; b += 69, cwNdx += 7) { + bitCnt = min(bitLng - b, 69); + cwLng += cwCnt = bitCnt / 10 + 1; + for (i = 0; i < cwCnt; i++) { + codeWords[cwNdx + i] = 0; /* init 0 */ + } + for (i = 0; i < bitCnt; i++) { + if (getBit(b + bitCnt - i - 1)) { + for (j = 0; j < cwCnt; j++) { + codeWords[cwNdx + j] += pwr928[i][j + 7 - cwCnt]; + } + } + } + for (i = cwCnt - 1; i > 0; i--) { + /* add "carries" */ + codeWords[cwNdx + i - 1] += codeWords[cwNdx + i] / 928L; + codeWords[cwNdx + i] %= 928L; + } + } + return (cwLng); + } + + private int min(int first, int second) { + if (first <= second) { + return first; + } else { + return second; + } + } + + /* gets bit in bitString at bitPos */ + private boolean getBit(int arg) { + return (bitStr[arg >> 4] & (0x8000 >> (arg & 15))) != 0; + } + + private void ccB() { + /* CC-B 2D component */ + int length, i, binloc; + int k, j, longueur, offset; + int[] mccorrection = new int[50]; + int total; + int[] dummy = new int[5]; + String codebarre; + String bin; + int variant, LeftRAPStart, CentreRAPStart, RightRAPStart, StartCluster; + int LeftRAP, CentreRAP, RightRAP, Cluster, flip, loop; + int option2, rows; + inputData = new int[(binaryString.length() / 8) + 3]; + + length = binaryString.length() / 8; + + for (i = 0; i < length; i++) { + binloc = i * 8; + + inputData[i] = 0; + for (j = 0; j < 8; j++) { + if (binaryString.charAt(binloc + j) == '1') { + inputData[i] += 0x80 >> j; + } + } + } + + codeWordCount = 0; + + /* "the CC-B component shall have codeword 920 in the first symbol character position" (section 9a) */ + codeWords[codeWordCount] = 920; + codeWordCount++; + + byteprocess(0, length); + + /* Now figure out which variant of the symbol to use and load values accordingly */ + + variant = 0; + + if (ccWidth == 2) { + variant = 13; + if (codeWordCount <= 33) { + variant = 12; + } + if (codeWordCount <= 29) { + variant = 11; + } + if (codeWordCount <= 24) { + variant = 10; + } + if (codeWordCount <= 19) { + variant = 9; + } + if (codeWordCount <= 13) { + variant = 8; + } + if (codeWordCount <= 8) { + variant = 7; + } + } + + if (ccWidth == 3) { + variant = 23; + if (codeWordCount <= 70) { + variant = 22; + } + if (codeWordCount <= 58) { + variant = 21; + } + if (codeWordCount <= 46) { + variant = 20; + } + if (codeWordCount <= 34) { + variant = 19; + } + if (codeWordCount <= 24) { + variant = 18; + } + if (codeWordCount <= 18) { + variant = 17; + } + if (codeWordCount <= 14) { + variant = 16; + } + if (codeWordCount <= 10) { + variant = 15; + } + if (codeWordCount <= 6) { + variant = 14; + } + } + + if (ccWidth == 4) { + variant = 34; + if (codeWordCount <= 108) { + variant = 33; + } + if (codeWordCount <= 90) { + variant = 32; + } + if (codeWordCount <= 72) { + variant = 31; + } + if (codeWordCount <= 54) { + variant = 30; + } + if (codeWordCount <= 39) { + variant = 29; + } + if (codeWordCount <= 30) { + variant = 28; + } + if (codeWordCount <= 24) { + variant = 27; + } + if (codeWordCount <= 18) { + variant = 26; + } + if (codeWordCount <= 12) { + variant = 25; + } + if (codeWordCount <= 8) { + variant = 24; + } + } + + /* Now we have the variant we can load the data - from here on the same as MicroPDF417 code */ + variant--; + option2 = MicroVariants[variant]; /* columns */ + rows = MicroVariants[variant + 34]; /* rows */ + k = MicroVariants[variant + 68]; /* number of EC CWs */ + longueur = (option2 * rows) - k; /* number of non-EC CWs */ + i = longueur - codeWordCount; /* amount of padding required */ + offset = MicroVariants[variant + 102]; /* coefficient offset */ + + /* We add the padding */ + while (i > 0) { + codeWords[codeWordCount] = 900; + codeWordCount++; + i--; + } + + /* Reed-Solomon error correction */ + longueur = codeWordCount; + for (loop = 0; loop < 50; loop++) { + mccorrection[loop] = 0; + } + encodeInfo.append("Composite Codewords: "); + for (i = 0; i < longueur; i++) { + total = (codeWords[i] + mccorrection[k - 1]) % 929; + for (j = k - 1; j >= 0; j--) { + if (j == 0) { + mccorrection[j] = (929 - (total * Microcoeffs[offset + j]) % 929) % 929; + } else { + mccorrection[j] = (mccorrection[j - 1] + 929 - (total * Microcoeffs[offset + j]) % 929) % 929; + } + } + encodeInfo.append(Integer.toString(codeWords[i])).append(" "); + } + encodeInfo.append("\n"); + + for (j = 0; j < k; j++) { + if (mccorrection[j] != 0) { + mccorrection[j] = 929 - mccorrection[j]; + } + } + /* we add these codes to the string */ + for (i = k - 1; i >= 0; i--) { + codeWords[codeWordCount] = mccorrection[i]; + codeWordCount++; + } + + /* Now get the RAP (Row Address Pattern) start values */ + LeftRAPStart = RAPTable[variant]; + CentreRAPStart = RAPTable[variant + 34]; + RightRAPStart = RAPTable[variant + 68]; + StartCluster = RAPTable[variant + 102] / 3; + + /* That's all values loaded, get on with the encoding */ + + LeftRAP = LeftRAPStart; + CentreRAP = CentreRAPStart; + RightRAP = RightRAPStart; + Cluster = StartCluster; /* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and Cluster(6) */ + + readable = new StringBuilder(); + rowCount = rows; + pattern = new String[rowCount]; + rowHeight = new int[rowCount]; + + for (i = 0; i < rows; i++) { + codebarre = ""; + offset = 929 * Cluster; + for (j = 0; j < 5; j++) { + dummy[j] = 0; + } + for (j = 0; j < option2; j++) { + dummy[j + 1] = codeWords[i * option2 + j]; + } + /* Copy the data into codebarre */ + codebarre += RAPLR[LeftRAP]; + codebarre += "1"; + codebarre += codagemc[offset + dummy[1]]; + codebarre += "1"; + if (ccWidth == 3) { + codebarre += RAPC[CentreRAP]; + } + if (ccWidth >= 2) { + codebarre += "1"; + codebarre += codagemc[offset + dummy[2]]; + codebarre += "1"; + } + if (ccWidth == 4) { + codebarre += RAPC[CentreRAP]; + } + if (ccWidth >= 3) { + codebarre += "1"; + codebarre += codagemc[offset + dummy[3]]; + codebarre += "1"; + } + if (ccWidth == 4) { + codebarre += "1"; + codebarre += codagemc[offset + dummy[4]]; + codebarre += "1"; + } + codebarre += RAPLR[RightRAP]; + codebarre += "1"; /* stop */ + + /* Now codebarre is a mixture of letters and numbers */ + + flip = 1; + bin = ""; + for (loop = 0; loop < codebarre.length(); loop++) { + if ((codebarre.charAt(loop) >= '0') && (codebarre.charAt(loop) <= '9')) { + for (k = 0; k < codebarre.charAt(loop) - '0'; k++) { + if (flip == 0) { + bin += '0'; + } else { + bin += '1'; + } + } + if (flip == 0) { + flip = 1; + } else { + flip = 0; + } + } else { + bin += PDFttf[positionOf(codebarre.charAt(loop), brSet)]; + } + } + + pattern[i] = bin2pat(bin); + rowHeight[i] = 2; + + /* Set up RAPs and Cluster for next row */ + LeftRAP++; + CentreRAP++; + RightRAP++; + Cluster++; + + if (LeftRAP == 53) { + LeftRAP = 1; + } + if (CentreRAP == 53) { + CentreRAP = 1; + } + if (RightRAP == 53) { + RightRAP = 1; + } + if (Cluster == 3) { + Cluster = 0; + } + + } + } + + private void ccC() { + /* CC-C 2D component - byte compressed PDF417 */ + int length, i, binloc, k; + int offset, longueur, loop, total, j; + int[] mccorrection = new int[520]; + int c1, c2, c3; + int[] dummy = new int[35]; + String codebarre; + String bin; + inputData = new int[(binaryString.length() / 8) + 4]; + + length = binaryString.length() / 8; + + for (i = 0; i < length; i++) { + binloc = i * 8; + + inputData[i] = 0; + for (j = 0; j < 8; j++) { + if (binaryString.charAt(binloc + j) == '1') { + inputData[i] += 0x80 >> j; + } + } + } + + codeWordCount = 0; + + codeWords[codeWordCount] = 0; /* space for length descriptor */ + codeWordCount++; + codeWords[codeWordCount] = 920; /* CC-C identifier */ + codeWordCount++; + + byteprocess(0, length); + + codeWords[0] = codeWordCount; + + k = 1; + for (i = 1; i <= (ecc + 1); i++) { + k *= 2; + } + + /* 796 - we now take care of the Reed Solomon codes */ + switch (ecc) { + case 1: + offset = 2; + break; + case 2: + offset = 6; + break; + case 3: + offset = 14; + break; + case 4: + offset = 30; + break; + case 5: + offset = 62; + break; + case 6: + offset = 126; + break; + case 7: + offset = 254; + break; + case 8: + offset = 510; + break; + default: + offset = 0; + break; + } + + longueur = codeWordCount; + for (loop = 0; loop < 520; loop++) { + mccorrection[loop] = 0; + } + encodeInfo.append("Composite Codewords: "); + for (i = 0; i < longueur; i++) { + total = (codeWords[i] + mccorrection[k - 1]) % 929; + for (j = k - 1; j >= 0; j--) { + if (j == 0) { + mccorrection[j] = (929 - (total * coefrs[offset + j]) % 929) % 929; + } else { + mccorrection[j] = (mccorrection[j - 1] + 929 - (total * coefrs[offset + j]) % 929) % 929; + } + } + encodeInfo.append(Integer.toString(codeWords[i])).append(" "); + } + encodeInfo.append("\n"); + + for (j = 0; j < k; j++) { + if (mccorrection[j] != 0) { + mccorrection[j] = 929 - mccorrection[j]; + } + } + /* we add these codes to the string */ + for (i = k - 1; i >= 0; i--) { + codeWords[codeWordCount] = mccorrection[i]; + codeWordCount++; + } + + /* 818 - The CW string is finished */ + c1 = (codeWordCount / ccWidth - 1) / 3; + c2 = ecc * 3 + (codeWordCount / ccWidth - 1) % 3; + c3 = ccWidth - 1; + + readable = new StringBuilder(); + rowCount = codeWordCount / ccWidth; + pattern = new String[rowCount]; + rowHeight = new int[rowCount]; + + /* we now encode each row */ + for (i = 0; i <= (codeWordCount / ccWidth) - 1; i++) { + for (j = 0; j < ccWidth; j++) { + dummy[j + 1] = codeWords[i * ccWidth + j]; + } + k = (i / 3) * 30; + switch (i % 3) { + /* follows this pattern from US Patent 5,243,655: + Row 0: L0 (row #, # of rows) R0 (row #, # of columns) + Row 1: L1 (row #, security level) R1 (row #, # of rows) + Row 2: L2 (row #, # of columns) R2 (row #, security level) + Row 3: L3 (row #, # of rows) R3 (row #, # of columns) + etc. */ + case 0: + dummy[0] = k + c1; + dummy[ccWidth + 1] = k + c3; + break; + case 1: + dummy[0] = k + c2; + dummy[ccWidth + 1] = k + c1; + break; + case 2: + dummy[0] = k + c3; + dummy[ccWidth + 1] = k + c2; + break; + } + codebarre = "+*"; /* Start with a start char and a separator */ + + for (j = 0; j <= ccWidth + 1; j++) { + switch (i % 3) { + case 1: + offset = 929; /* cluster(3) */ + break; + case 2: + offset = 1858; /* cluster(6) */ + break; + default: + offset = 0; /* cluster(0) */ + break; + } + codebarre += codagemc[offset + dummy[j]]; + codebarre += "*"; + } + codebarre += "-"; + + bin = ""; + for (loop = 0; loop < codebarre.length(); loop++) { + bin += PDFttf[positionOf(codebarre.charAt(loop), brSet)]; + } + pattern[i] = bin2pat(bin); + rowHeight[i] = 3; + } + } + + private void byteprocess(int start, int length) { + int len = 0; + int chunkLen = 0; + BigInteger mantisa; + BigInteger total; + BigInteger word; + + /* select the switch for multiple of 6 bytes */ + if ((binaryString.length() % 6) == 0) { + codeWords[codeWordCount++] = 924; + } else { + codeWords[codeWordCount++] = 901; + } + + while (len < length) { + chunkLen = length - len; + if (6 <= chunkLen) /* Take groups of 6 */ { + chunkLen = 6; + len += chunkLen; + total = BigInteger.valueOf(0); + + while ((chunkLen--) != 0) { + mantisa = BigInteger.valueOf(inputData[start++]); + total = total.or(mantisa.shiftLeft(chunkLen * 8)); + } + + chunkLen = 5; + + while ((chunkLen--) != 0) { + + word = total.mod(BigInteger.valueOf(900)); + codeWords[codeWordCount + chunkLen] = word.intValue(); + total = total.divide(BigInteger.valueOf(900)); + } + codeWordCount += 5; + } else /* If it remain a group of less than 6 bytes */ { + len += chunkLen; + while ((chunkLen--) != 0) { + codeWords[codeWordCount++] = inputData[start++]; + } + } + } + } + + public enum LinearEncoding { + UPCA, UPCE, EAN, CODE_128, DATABAR_14, DATABAR_14_STACK, + DATABAR_14_STACK_OMNI, DATABAR_LIMITED, DATABAR_EXPANDED, + DATABAR_EXPANDED_STACK + } + + private enum gfMode { + NUMERIC, ALPHA, ISOIEC, INVALID_CHAR, ANY_ENC, ALPHA_OR_ISO + } + + public enum CompositeMode { + CC_A, CC_B, CC_C + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/DataBar14.java b/barcode/src/main/java/org/xbib/graphics/barcode/DataBar14.java new file mode 100755 index 0000000..c553eb7 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/DataBar14.java @@ -0,0 +1,698 @@ +package org.xbib.graphics.barcode; + +import java.math.BigInteger; + +/** + * Implements GS1 DataBar Omnidirectional and GS1 DataBar Truncated according to ISO/IEC 24724:2011. + * Input data should be a 13 digit Global Trade Identification Number + * without check digit or Application Identifier [01]. + */ +public class DataBar14 extends Symbol { + + private int[] g_sum_table = { + 0, 161, 961, 2015, 2715, 0, 336, 1036, 1516 + }; + private int[] t_table = { + 1, 10, 34, 70, 126, 4, 20, 48, 81 + }; + private int[] widths = new int[8]; + private int[] modules_odd = { + 12, 10, 8, 6, 4, 5, 7, 9, 11 + }; + private int[] modules_even = { + 4, 6, 8, 10, 12, 10, 8, 6, 4 + }; + private int[] widest_odd = { + 8, 6, 4, 3, 1, 2, 4, 6, 8 + }; + private int[] widest_even = { + 1, 3, 5, 6, 8, 7, 5, 3, 1 + }; + private int[] checksum_weight = { /* Table 5 */ + 1, 3, 9, 27, 2, 6, 18, 54, 4, 12, 36, 29, 8, 24, 72, 58, 16, 48, 65, + 37, 32, 17, 51, 74, 64, 34, 23, 69, 49, 68, 46, 59 + }; + private int[] finder_pattern = { + 3, 8, 2, 1, 1, 3, 5, 5, 1, 1, 3, 3, 7, 1, 1, 3, 1, 9, 1, 1, 2, 7, 4, + 1, 1, 2, 5, 6, 1, 1, 2, 3, 8, 1, 1, 1, 5, 7, 1, 1, 1, 3, 9, 1, 1 + }; + + private boolean linkageFlag; + private gb14Mode symbolType; + + private boolean[][] grid = new boolean[5][100]; + private boolean[] seperator = new boolean[100]; + + public DataBar14() { + linkageFlag = false; + symbolType = gb14Mode.LINEAR; + } + + @Override + public void setDataType(DataType dummy) { + // Do nothing! + } + + protected void setLinkageFlag() { + linkageFlag = true; + } + + protected void unsetLinkageFlag() { + linkageFlag = false; + } + + /** + * Set symbol type to DataBar-14 + */ + public void setLinearMode() { + symbolType = gb14Mode.LINEAR; + } + + /** + * Set symbol type to DataBar-14 Omnidirectional + */ + public void setOmnidirectionalMode() { + symbolType = gb14Mode.OMNI; + } + + /** + * Set symbol type to DataBar-14 Omnidirectional Stacked + */ + public void setStackedMode() { + symbolType = gb14Mode.STACKED; + } + + @Override + public boolean encode() { + BigInteger accum; + BigInteger left_reg; + BigInteger right_reg; + int[] data_character = new int[4]; + int[] data_group = new int[4]; + int[] v_odd = new int[4]; + int[] v_even = new int[4]; + int i; + int[][] data_widths = new int[8][4]; + int checksum; + int c_left; + int c_right; + int[] total_widths = new int[46]; + int writer; + char latch; + int j; + int count; + int check_digit; + String hrt; + String bin; + int compositeOffset = 0; + + if (content.length() > 13) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+?"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + accum = new BigInteger(content); + if (linkageFlag) { + accum = accum.add(new BigInteger("10000000000000")); + compositeOffset = 1; + } + + /* Calculate left and right pair values */ + left_reg = accum.divide(new BigInteger("4537077")); + right_reg = accum.mod(new BigInteger("4537077")); + + /* Calculate four data characters */ + accum = left_reg.divide(new BigInteger("1597")); + data_character[0] = accum.intValue(); + accum = left_reg.mod(new BigInteger("1597")); + data_character[1] = accum.intValue(); + accum = right_reg.divide(new BigInteger("1597")); + data_character[2] = accum.intValue(); + accum = right_reg.mod(new BigInteger("1597")); + data_character[3] = accum.intValue(); + + encodeInfo.append("Data characters: "); + for (i = 0; i < 4; i++) { + encodeInfo.append(Integer.toString(data_character[i])).append(" "); + } + encodeInfo.append("\n"); + + /* Calculate odd and even subset values */ + if ((data_character[0] >= 0) && (data_character[0] <= 160)) { + data_group[0] = 0; + } + if ((data_character[0] >= 161) && (data_character[0] <= 960)) { + data_group[0] = 1; + } + if ((data_character[0] >= 961) && (data_character[0] <= 2014)) { + data_group[0] = 2; + } + if ((data_character[0] >= 2015) && (data_character[0] <= 2714)) { + data_group[0] = 3; + } + if ((data_character[0] >= 2715) && (data_character[0] <= 2840)) { + data_group[0] = 4; + } + if ((data_character[1] >= 0) && (data_character[1] <= 335)) { + data_group[1] = 5; + } + if ((data_character[1] >= 336) && (data_character[1] <= 1035)) { + data_group[1] = 6; + } + if ((data_character[1] >= 1036) && (data_character[1] <= 1515)) { + data_group[1] = 7; + } + if ((data_character[1] >= 1516) && (data_character[1] <= 1596)) { + data_group[1] = 8; + } + if ((data_character[3] >= 0) && (data_character[3] <= 335)) { + data_group[3] = 5; + } + if ((data_character[3] >= 336) && (data_character[3] <= 1035)) { + data_group[3] = 6; + } + if ((data_character[3] >= 1036) && (data_character[3] <= 1515)) { + data_group[3] = 7; + } + if ((data_character[3] >= 1516) && (data_character[3] <= 1596)) { + data_group[3] = 8; + } + if ((data_character[2] >= 0) && (data_character[2] <= 160)) { + data_group[2] = 0; + } + if ((data_character[2] >= 161) && (data_character[2] <= 960)) { + data_group[2] = 1; + } + if ((data_character[2] >= 961) && (data_character[2] <= 2014)) { + data_group[2] = 2; + } + if ((data_character[2] >= 2015) && (data_character[2] <= 2714)) { + data_group[2] = 3; + } + if ((data_character[2] >= 2715) && (data_character[2] <= 2840)) { + data_group[2] = 4; + } + + v_odd[0] = (data_character[0] - g_sum_table[data_group[0]]) / t_table[data_group[0]]; + v_even[0] = (data_character[0] - g_sum_table[data_group[0]]) % t_table[data_group[0]]; + v_odd[1] = (data_character[1] - g_sum_table[data_group[1]]) % t_table[data_group[1]]; + v_even[1] = (data_character[1] - g_sum_table[data_group[1]]) / t_table[data_group[1]]; + v_odd[3] = (data_character[3] - g_sum_table[data_group[3]]) % t_table[data_group[3]]; + v_even[3] = (data_character[3] - g_sum_table[data_group[3]]) / t_table[data_group[3]]; + v_odd[2] = (data_character[2] - g_sum_table[data_group[2]]) / t_table[data_group[2]]; + v_even[2] = (data_character[2] - g_sum_table[data_group[2]]) % t_table[data_group[2]]; + + /* Use RSS subset width algorithm */ + for (i = 0; i < 4; i++) { + if ((i == 0) || (i == 2)) { + getWidths(v_odd[i], modules_odd[data_group[i]], 4, widest_odd[data_group[i]], 1); + data_widths[0][i] = widths[0]; + data_widths[2][i] = widths[1]; + data_widths[4][i] = widths[2]; + data_widths[6][i] = widths[3]; + getWidths(v_even[i], modules_even[data_group[i]], 4, widest_even[data_group[i]], 0); + data_widths[1][i] = widths[0]; + data_widths[3][i] = widths[1]; + data_widths[5][i] = widths[2]; + data_widths[7][i] = widths[3]; + } else { + getWidths(v_odd[i], modules_odd[data_group[i]], 4, widest_odd[data_group[i]], 0); + data_widths[0][i] = widths[0]; + data_widths[2][i] = widths[1]; + data_widths[4][i] = widths[2]; + data_widths[6][i] = widths[3]; + getWidths(v_even[i], modules_even[data_group[i]], 4, widest_even[data_group[i]], 1); + data_widths[1][i] = widths[0]; + data_widths[3][i] = widths[1]; + data_widths[5][i] = widths[2]; + data_widths[7][i] = widths[3]; + } + } + + checksum = 0; + /* Calculate the checksum */ + for (i = 0; i < 8; i++) { + checksum += checksum_weight[i] * data_widths[i][0]; + checksum += checksum_weight[i + 8] * data_widths[i][1]; + checksum += checksum_weight[i + 16] * data_widths[i][2]; + checksum += checksum_weight[i + 24] * data_widths[i][3]; + } + checksum %= 79; + + /* Calculate the two check characters */ + if (checksum >= 8) { + checksum++; + } + if (checksum >= 72) { + checksum++; + } + c_left = checksum / 9; + c_right = checksum % 9; + + encodeInfo.append("Checksum: ").append(Integer.toString(checksum)).append("\n"); + + /* Put element widths together */ + total_widths[0] = 1; + total_widths[1] = 1; + total_widths[44] = 1; + total_widths[45] = 1; + for (i = 0; i < 8; i++) { + total_widths[i + 2] = data_widths[i][0]; + total_widths[i + 15] = data_widths[7 - i][1]; + total_widths[i + 23] = data_widths[i][3]; + total_widths[i + 36] = data_widths[7 - i][2]; + } + for (i = 0; i < 5; i++) { + total_widths[i + 10] = finder_pattern[i + (5 * c_left)]; + total_widths[i + 31] = finder_pattern[(4 - i) + (5 * c_right)]; + } + + rowCount = 0; + for (i = 0; i < 100; i++) { + seperator[i] = false; + } + /* Put this data into the symbol */ + if (symbolType == gb14Mode.LINEAR) { + writer = 0; + latch = '0'; + for (i = 0; i < 46; i++) { + for (j = 0; j < total_widths[i]; j++) { + if (latch == '1') { + setGridModule(rowCount, writer); + } + writer++; + } + if (latch == '1') { + latch = '0'; + } else { + latch = '1'; + } + } + if (symbolWidth < writer) { + symbolWidth = writer; + } + + if (linkageFlag) { + /* separator pattern for composite symbol */ + for (i = 4; i < 92; i++) { + seperator[i] = (!(grid[0][i])); + } + latch = '1'; + for (i = 16; i < 32; i++) { + if (!(grid[0][i])) { + if (latch == '1') { + seperator[i] = true; + latch = '0'; + } else { + seperator[i] = false; + latch = '1'; + } + } else { + seperator[i] = false; + latch = '1'; + } + } + latch = '1'; + for (i = 63; i < 78; i++) { + if (!(grid[0][i])) { + if (latch == '1') { + seperator[i] = true; + latch = '0'; + } else { + seperator[i] = false; + latch = '1'; + } + } else { + seperator[i] = false; + latch = '1'; + } + } + } + rowCount = rowCount + 1; + + count = 0; + check_digit = 0; + + /* Calculate check digit from Annex A and place human readable text */ + readable = new StringBuilder("(01)"); + hrt = ""; + for (i = content.length(); i < 13; i++) { + hrt += "0"; + } + hrt += content; + + for (i = 0; i < 13; i++) { + count += hrt.charAt(i) - '0'; + + if ((i & 1) == 0) { + count += 2 * (hrt.charAt(i) - '0'); + } + } + + check_digit = 10 - (count % 10); + if (check_digit == 10) { + check_digit = 0; + } + hrt += (char) (check_digit + '0'); + + readable.append(hrt); + } + + if (symbolType == gb14Mode.STACKED) { + /* top row */ + writer = 0; + latch = '0'; + for (i = 0; i < 23; i++) { + for (j = 0; j < total_widths[i]; j++) { + if (latch == '1') { + setGridModule(rowCount, writer); + } else { + unsetGridModule(rowCount, writer); + } + writer++; + } + if (latch == '1') { + latch = '0'; + } else { + latch = '1'; + } + } + setGridModule(rowCount, writer); + unsetGridModule(rowCount, writer + 1); + + /* bottom row */ + rowCount = rowCount + 2; + setGridModule(rowCount, 0); + unsetGridModule(rowCount, 1); + writer = 0; + latch = '1'; + for (i = 23; i < 46; i++) { + for (j = 0; j < total_widths[i]; j++) { + if (latch == '1') { + setGridModule(rowCount, writer + 2); + } else { + unsetGridModule(rowCount, writer + 2); + } + writer++; + } + if (latch == '1') { + latch = '0'; + } else { + latch = '1'; + } + } + + /* separator pattern */ + for (i = 4; i < 46; i++) { + if (gridModuleIsSet(rowCount - 2, i) == gridModuleIsSet(rowCount, i)) { + if (!(gridModuleIsSet(rowCount - 2, i))) { + setGridModule(rowCount - 1, i); + } + } else { + if (!(gridModuleIsSet(rowCount - 1, i - 1))) { + setGridModule(rowCount - 1, i); + } + } + } + + if (linkageFlag) { + /* separator pattern for composite symbol */ + for (i = 4; i < 46; i++) { + seperator[i] = (!(grid[0][i])); + } + latch = '1'; + for (i = 16; i < 32; i++) { + if (!(grid[0][i])) { + if (latch == '1') { + seperator[i] = true; + latch = '0'; + } else { + seperator[i] = false; + latch = '1'; + } + } else { + seperator[i] = false; + latch = '1'; + } + } + } + rowCount = rowCount + 1; + if (symbolWidth < 50) { + symbolWidth = 50; + } + } + + if (symbolType == gb14Mode.OMNI) { + /* top row */ + writer = 0; + latch = '0'; + for (i = 0; i < 23; i++) { + for (j = 0; j < total_widths[i]; j++) { + if (latch == '1') { + setGridModule(rowCount, writer); + } else { + unsetGridModule(rowCount, writer); + } + writer++; + } + latch = (latch == '1' ? '0' : '1'); + } + setGridModule(rowCount, writer); + unsetGridModule(rowCount, writer + 1); + + /* bottom row */ + rowCount = rowCount + 4; + setGridModule(rowCount, 0); + unsetGridModule(rowCount, 1); + writer = 0; + latch = '1'; + for (i = 23; i < 46; i++) { + for (j = 0; j < total_widths[i]; j++) { + if (latch == '1') { + setGridModule(rowCount, writer + 2); + } else { + unsetGridModule(rowCount, writer + 2); + } + writer++; + } + if (latch == '1') { + latch = '0'; + } else { + latch = '1'; + } + } + + /* middle separator */ + for (i = 5; i < 46; i += 2) { + setGridModule(rowCount - 2, i); + } + + /* top separator */ + for (i = 4; i < 46; i++) { + if (!(gridModuleIsSet(rowCount - 4, i))) { + setGridModule(rowCount - 3, i); + } + } + latch = '1'; + for (i = 17; i < 33; i++) { + if (!(gridModuleIsSet(rowCount - 4, i))) { + if (latch == '1') { + setGridModule(rowCount - 3, i); + latch = '0'; + } else { + unsetGridModule(rowCount - 3, i); + latch = '1'; + } + } else { + unsetGridModule(rowCount - 3, i); + latch = '1'; + } + } + + /* bottom separator */ + for (i = 4; i < 46; i++) { + if (!(gridModuleIsSet(rowCount, i))) { + setGridModule(rowCount - 1, i); + } + } + latch = '1'; + for (i = 16; i < 32; i++) { + if (!(gridModuleIsSet(rowCount, i))) { + if (latch == '1') { + setGridModule(rowCount - 1, i); + latch = '0'; + } else { + unsetGridModule(rowCount - 1, i); + latch = '1'; + } + } else { + unsetGridModule(rowCount - 1, i); + latch = '1'; + } + } + + if (symbolWidth < 50) { + symbolWidth = 50; + } + if (linkageFlag) { + /* separator pattern for composite symbol */ + for (i = 4; i < 46; i++) { + seperator[i] = (!(grid[0][i])); + } + latch = '1'; + for (i = 16; i < 32; i++) { + if (!(grid[0][i])) { + if (latch == '1') { + seperator[i] = true; + latch = '0'; + } else { + seperator[i] = false; + latch = '1'; + } + } else { + seperator[i] = false; + latch = '1'; + } + } + } + rowCount = rowCount + 1; + } + + pattern = new String[rowCount + compositeOffset]; + rowHeight = new int[rowCount + compositeOffset]; + + if (linkageFlag) { + bin = ""; + for (j = 0; j < symbolWidth; j++) { + if (seperator[j]) { + bin += "1"; + } else { + bin += "0"; + } + } + pattern[0] = bin2pat(bin); + rowHeight[0] = 1; + } + + for (i = 0; i < rowCount; i++) { + bin = ""; + for (j = 0; j < symbolWidth; j++) { + if (grid[i][j]) { + bin += "1"; + } else { + bin += "0"; + } + } + pattern[i + compositeOffset] = bin2pat(bin); + } + + if (symbolType == gb14Mode.LINEAR) { + rowHeight[0 + compositeOffset] = -1; + } + if (symbolType == gb14Mode.STACKED) { + rowHeight[0 + compositeOffset] = 5; + rowHeight[1 + compositeOffset] = 1; + rowHeight[2 + compositeOffset] = 7; + } + if (symbolType == gb14Mode.OMNI) { + rowHeight[0 + compositeOffset] = -1; + rowHeight[1 + compositeOffset] = 1; + rowHeight[2 + compositeOffset] = 1; + rowHeight[3 + compositeOffset] = 1; + rowHeight[4 + compositeOffset] = -1; + } + + if (linkageFlag) { + rowCount++; + } + + plotSymbol(); + return true; + } + + private int getCombinations(int n, int r) { + int i, j; + int maxDenom, minDenom; + int val; + + if (n - r > r) { + minDenom = r; + maxDenom = n - r; + } else { + minDenom = n - r; + maxDenom = r; + } + val = 1; + j = 1; + for (i = n; i > maxDenom; i--) { + val *= i; + if (j <= minDenom) { + val /= j; + j++; + } + } + for (; j <= minDenom; j++) { + val /= j; + } + return (val); + } + + private void getWidths(int val, int n, int elements, int maxWidth, int noNarrow) { + int bar; + int elmWidth; + int mxwElement; + int subVal, lessVal; + int narrowMask = 0; + for (bar = 0; bar < elements - 1; bar++) { + for (elmWidth = 1, narrowMask |= (1 << bar); ; + elmWidth++, narrowMask &= ~(1 << bar)) { + /* get all combinations */ + subVal = getCombinations(n - elmWidth - 1, elements - bar - 2); + /* less combinations with no single-module element */ + if ((noNarrow == 0) && (narrowMask == 0) + && (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) { + subVal -= getCombinations(n - elmWidth - (elements - bar), elements - bar - 2); + } + /* less combinations with elements > maxVal */ + if (elements - bar - 1 > 1) { + lessVal = 0; + for (mxwElement = n - elmWidth - (elements - bar - 2); + mxwElement > maxWidth; + mxwElement--) { + lessVal += getCombinations(n - elmWidth - mxwElement - 1, elements - bar - 3); + } + subVal -= lessVal * (elements - 1 - bar); + } else if (n - elmWidth > maxWidth) { + subVal--; + } + val -= subVal; + if (val < 0) break; + } + val += subVal; + n -= elmWidth; + widths[bar] = elmWidth; + } + widths[bar] = n; + } + + private void setGridModule(int row, int column) { + grid[row][column] = true; + } + + private void unsetGridModule(int row, int column) { + grid[row][column] = false; + } + + private boolean gridModuleIsSet(int row, int column) { + return grid[row][column]; + } + + private enum gb14Mode { + LINEAR, OMNI, STACKED + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/DataBarExpanded.java b/barcode/src/main/java/org/xbib/graphics/barcode/DataBarExpanded.java new file mode 100755 index 0000000..fe0da9d --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/DataBarExpanded.java @@ -0,0 +1,1652 @@ +package org.xbib.graphics.barcode; + +/** + * Implements GS1 DataBar Expanded Omnidirectional and GS1 Expanded Stacked + * Omnidirectional according to ISO/IEC 24724:2011. + * DataBar expanded encodes GS1 data in either a linear or stacked + * format. + */ +public class DataBarExpanded extends Symbol { + + private static final int[] G_SUM_EXP = { + 0, 348, 1388, 2948, 3988 + }; + private static final int[] T_EVEN_EXP = { + 4, 20, 52, 104, 204 + }; + private static final int[] MODULES_ODD_EXP = { + 12, 10, 8, 6, 4 + }; + private static final int[] MODULES_EVEN_EXP = { + 5, 7, 9, 11, 13 + }; + private static final int[] WIDEST_ODD_EXP = { + 7, 5, 4, 3, 1 + }; + private static final int[] WIDEST_EVEN_EXP = { + 2, 4, 5, 6, 8 + }; + private static final int[] CHECKSUM_WEIGHT_EXP = { /* Table 14 */ + 1, 3, 9, 27, 81, 32, 96, 77, 20, 60, 180, 118, 143, 7, 21, 63, 189, + 145, 13, 39, 117, 140, 209, 205, 193, 157, 49, 147, 19, 57, 171, 91, + 62, 186, 136, 197, 169, 85, 44, 132, 185, 133, 188, 142, 4, 12, 36, + 108, 113, 128, 173, 97, 80, 29, 87, 50, 150, 28, 84, 41, 123, 158, 52, + 156, 46, 138, 203, 187, 139, 206, 196, 166, 76, 17, 51, 153, 37, 111, + 122, 155, 43, 129, 176, 106, 107, 110, 119, 146, 16, 48, 144, 10, 30, + 90, 59, 177, 109, 116, 137, 200, 178, 112, 125, 164, 70, 210, 208, 202, + 184, 130, 179, 115, 134, 191, 151, 31, 93, 68, 204, 190, 148, 22, 66, + 198, 172, 94, 71, 2, 6, 18, 54, 162, 64, 192, 154, 40, 120, 149, 25, + 75, 14, 42, 126, 167, 79, 26, 78, 23, 69, 207, 199, 175, 103, 98, 83, + 38, 114, 131, 182, 124, 161, 61, 183, 127, 170, 88, 53, 159, 55, 165, + 73, 8, 24, 72, 5, 15, 45, 135, 194, 160, 58, 174, 100, 89 + }; + private static final int[] FINDER_PATTERN_EXP = { /* Table 15 */ + 1, 8, 4, 1, 1, 1, 1, 4, 8, 1, 3, 6, 4, 1, 1, 1, 1, 4, 6, 3, 3, 4, 6, 1, + 1, 1, 1, 6, 4, 3, 3, 2, 8, 1, 1, 1, 1, 8, 2, 3, 2, 6, 5, 1, 1, 1, 1, 5, + 6, 2, 2, 2, 9, 1, 1, 1, 1, 9, 2, 2 + }; + private static final int[] FINDER_SEQUENCE = { /* Table 16 */ + 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, + 3, 8, 0, 0, 0, 0, 0, 0, 0, 1, 10, 3, 8, 5, 0, 0, 0, 0, 0, 0, 1, 10, 3, + 8, 7, 12, 0, 0, 0, 0, 0, 1, 10, 3, 8, 9, 12, 11, 0, 0, 0, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 10, 9, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 10, 11, 12, 0, 1, 2, 3, 4, 5, 8, 7, 10, 9, 12, 11 + }; + private static final int[] WEIGHT_ROWS = { + 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, + 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 3, 4, + 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13, + 14, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13, 14, + 11, 12, 21, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13, 14, + 15, 16, 21, 22, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 17, 18, 15, 16, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 17, 18, 19, 20, 21, 22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 13, 14, 11, 12, 17, 18, 15, 16, 21, 22, 19, 20 + }; + + private String source; + private StringBuilder binaryString = new StringBuilder(); + private String generalField; + private EncodeMode[] generalFieldType; + private int[] widths = new int[8]; + private boolean linkageFlag; + + private int preferredNoOfColumns = 0; + private dbeMode symbolType; + + public DataBarExpanded() { + linkageFlag = false; + inputDataType = DataType.GS1; + } + + private static int calculateRemainder(int binaryStringLength) { + int remainder = 12 - (binaryStringLength % 12); + if (remainder == 12) { + remainder = 0; + } + if (binaryStringLength < 36) { + remainder = 36 - binaryStringLength; + } + return remainder; + } + + /** + * Set the width of a stacked symbol by selecting the number + * of "columns" or symbol segments in each row of data. + * + * @param columns Number of segments in each row + */ + public void setNoOfColumns(int columns) { + preferredNoOfColumns = columns; + } + + ; + + @Override + public void setDataType(DataType dummy) { + // Do nothing! + } + + /** + * Set symbology to DataBar Expanded Stacked + */ + public void setStacked() { + symbolType = dbeMode.STACKED; + } + + /** + * Set symbology to DataBar Expanded + */ + public void setNotStacked() { + symbolType = dbeMode.UNSTACKED; + } + + protected void setLinkageFlag() { + linkageFlag = true; + } + + protected void unsetLinkageFlag() { + linkageFlag = false; + } + + @Override + public boolean encode() { + int i; + int j; + int k; + int dataChars; + int[] vs = new int[21]; + int[] group = new int[21]; + int[] vOdd = new int[21]; + int[] vEven = new int[21]; + int[][] charWidths = new int[21][8]; + int checksum; + int row; + int checkChar; + int cGroup; + int cOdd; + int cEven; + int[] checkWidths = new int[8]; + int patternWidth; + int[] elements = new int[235]; + int codeblocks; + int stackRows; + int blocksPerRow; + int currentBlock; + int currentRow; + boolean specialCaseRow; + int elementsInSub; + int reader; + int writer; + int[] subElements = new int[235]; + int l; + int symbolRow; + String seperatorBinary; + String seperatorPattern; + boolean black; + boolean leftToRight; + int compositeOffset; + + source = content; + + if (linkageFlag) { + binaryString = new StringBuilder("1"); + compositeOffset = 1; + } else { + binaryString = new StringBuilder("0"); + compositeOffset = 0; + } + if (!calculateBinaryString()) { + return false; + } + + dataChars = binaryString.length() / 12; + + encodeInfo.append("Data characters: "); + for (i = 0; i < dataChars; i++) { + vs[i] = 0; + for (j = 0; j < 12; j++) { + if (binaryString.charAt((i * 12) + j) == '1') { + vs[i] += 2048 >> j; + } + } + encodeInfo.append(Integer.toString(vs[i])).append(" "); + } + encodeInfo.append("\n"); + + for (i = 0; i < dataChars; i++) { + if (vs[i] <= 347) { + group[i] = 1; + } + if ((vs[i] >= 348) && (vs[i] <= 1387)) { + group[i] = 2; + } + if ((vs[i] >= 1388) && (vs[i] <= 2947)) { + group[i] = 3; + } + if ((vs[i] >= 2948) && (vs[i] <= 3987)) { + group[i] = 4; + } + if (vs[i] >= 3988) { + group[i] = 5; + } + vOdd[i] = (vs[i] - G_SUM_EXP[group[i] - 1]) / T_EVEN_EXP[group[i] - 1]; + vEven[i] = (vs[i] - G_SUM_EXP[group[i] - 1]) % T_EVEN_EXP[group[i] - 1]; + + getWidths(vOdd[i], MODULES_ODD_EXP[group[i] - 1], 4, WIDEST_ODD_EXP[group[i] - 1], 0); + charWidths[i][0] = widths[0]; + charWidths[i][2] = widths[1]; + charWidths[i][4] = widths[2]; + charWidths[i][6] = widths[3]; + getWidths(vEven[i], MODULES_EVEN_EXP[group[i] - 1], 4, WIDEST_EVEN_EXP[group[i] - 1], 1); + charWidths[i][1] = widths[0]; + charWidths[i][3] = widths[1]; + charWidths[i][5] = widths[2]; + charWidths[i][7] = widths[3]; + } + + /* 7.2.6 Check character */ + /* The checksum value is equal to the mod 211 residue of the weighted sum of the widths of the + elements in the data characters. */ + checksum = 0; + for (i = 0; i < dataChars; i++) { + row = WEIGHT_ROWS[(((dataChars - 2) / 2) * 21) + i]; + for (j = 0; j < 8; j++) { + checksum += (charWidths[i][j] * CHECKSUM_WEIGHT_EXP[(row * 8) + j]); + + } + } + + checkChar = (211 * ((dataChars + 1) - 4)) + (checksum % 211); + + encodeInfo.append("Check Character: ").append(Integer.toString(checkChar)).append("\n"); + + cGroup = 1; + if ((checkChar >= 348) && (checkChar <= 1387)) { + cGroup = 2; + } + if ((checkChar >= 1388) && (checkChar <= 2947)) { + cGroup = 3; + } + if ((checkChar >= 2948) && (checkChar <= 3987)) { + cGroup = 4; + } + if (checkChar >= 3988) { + cGroup = 5; + } + + cOdd = (checkChar - G_SUM_EXP[cGroup - 1]) / T_EVEN_EXP[cGroup - 1]; + cEven = (checkChar - G_SUM_EXP[cGroup - 1]) % T_EVEN_EXP[cGroup - 1]; + + getWidths(cOdd, MODULES_ODD_EXP[cGroup - 1], 4, WIDEST_ODD_EXP[cGroup - 1], 0); + checkWidths[0] = widths[0]; + checkWidths[2] = widths[1]; + checkWidths[4] = widths[2]; + checkWidths[6] = widths[3]; + getWidths(cEven, MODULES_EVEN_EXP[cGroup - 1], 4, WIDEST_EVEN_EXP[cGroup - 1], 1); + checkWidths[1] = widths[0]; + checkWidths[3] = widths[1]; + checkWidths[5] = widths[2]; + checkWidths[7] = widths[3]; + + /* Initialise element array */ + patternWidth = ((((dataChars + 1) / 2) + ((dataChars + 1) & 1)) * 5) + ((dataChars + 1) * 8) + 4; + for (i = 0; i < patternWidth; i++) { + elements[i] = 0; + } + + elements[0] = 1; + elements[1] = 1; + elements[patternWidth - 2] = 1; + elements[patternWidth - 1] = 1; + + /* Put finder patterns in element array */ + for (i = 0; i < (((dataChars + 1) / 2) + ((dataChars + 1) & 1)); i++) { + k = ((((((dataChars + 1) - 2) / 2) + ((dataChars + 1) & 1)) - 1) * 11) + i; + for (j = 0; j < 5; j++) { + elements[(21 * i) + j + 10] = FINDER_PATTERN_EXP[((FINDER_SEQUENCE[k] - 1) * 5) + j]; + } + } + + /* Put check character in element array */ + for (i = 0; i < 8; i++) { + elements[i + 2] = checkWidths[i]; + } + + /* Put forward reading data characters in element array */ + for (i = 1; i < dataChars; i += 2) { + for (j = 0; j < 8; j++) { + elements[(((i - 1) / 2) * 21) + 23 + j] = charWidths[i][j]; + } + } + + /* Put reversed data characters in element array */ + for (i = 0; i < dataChars; i += 2) { + for (j = 0; j < 8; j++) { + elements[((i / 2) * 21) + 15 + j] = charWidths[i][7 - j]; + } + } + + + if (symbolType == dbeMode.UNSTACKED) { + /* Copy elements into symbol */ + rowCount = 1 + compositeOffset; + rowHeight = new int[1 + compositeOffset]; + rowHeight[0 + compositeOffset] = -1; + pattern = new String[1 + compositeOffset]; + + pattern[0 + compositeOffset] = "0"; + writer = 0; + black = false; + seperatorBinary = ""; + for (i = 0; i < patternWidth; i++) { + pattern[0 + compositeOffset] += (char) (elements[i] + '0'); + for (j = 0; j < elements[i]; j++) { + if (black) { + seperatorBinary += "0"; + } else { + seperatorBinary += "1"; + } + } + + black = !(black); + writer += elements[i]; + } + seperatorBinary = "0000" + seperatorBinary.substring(4, writer - 4); + for (j = 0; j < (writer / 49); j++) { + k = (49 * j) + 18; + for (i = 0; i < 15; i++) { + if ((seperatorBinary.charAt(i + k - 1) == '1') + && (seperatorBinary.charAt(i + k) == '1')) { + seperatorBinary = seperatorBinary.substring(0, (i + k)) + + "0" + seperatorBinary.substring(i + k + 1); + } + } + } + if (linkageFlag) { + // Add composite code seperator + pattern[0] = bin2pat(seperatorBinary); + rowHeight[0] = 1; + } + + } else { + /* RSS Expanded Stacked */ + codeblocks = (dataChars + 1) / 2 + ((dataChars + 1) % 2); + + blocksPerRow = preferredNoOfColumns; + if ((blocksPerRow < 1) || (blocksPerRow > 10)) { + blocksPerRow = 2; + } + + if (linkageFlag && (blocksPerRow == 1)) { + /* "There shall be a minimum of four symbol characters in the + first row of an RSS Expanded Stacked symbol when it is the linear + component of an EAN.UCC Composite symbol." */ + blocksPerRow = 2; + } + + stackRows = codeblocks / blocksPerRow; + if (codeblocks % blocksPerRow > 0) { + stackRows++; + } + + rowCount = (stackRows * 4) - 3; + rowHeight = new int[rowCount + compositeOffset]; + pattern = new String[rowCount + compositeOffset]; + symbolRow = 0; + + currentBlock = 0; + for (currentRow = 1; currentRow <= stackRows; currentRow++) { + for (i = 0; i < 235; i++) { + subElements[i] = 0; + } + specialCaseRow = false; + + /* Row Start */ + subElements[0] = 1; + subElements[1] = 1; + elementsInSub = 2; + + /* Row Data */ + reader = 0; + do { + if ((((blocksPerRow & 1) != 0) || ((currentRow & 1) != 0)) + || ((currentRow == stackRows) + && (codeblocks != (currentRow * blocksPerRow)) + && ((((currentRow * blocksPerRow) - codeblocks) & 1)) != 0)) { + /* left to right */ + leftToRight = true; + i = 2 + (currentBlock * 21); + for (j = 0; j < 21; j++) { + if ((i + j) < patternWidth) { + subElements[j + (reader * 21) + 2] = elements[i + j]; + elementsInSub++; + } + } + } else { + /* right to left */ + leftToRight = false; + if ((currentRow * blocksPerRow) < codeblocks) { + /* a full row */ + i = 2 + (((currentRow * blocksPerRow) - reader - 1) * 21); + for (j = 0; j < 21; j++) { + if ((i + j) < patternWidth) { + subElements[(20 - j) + (reader * 21) + 2] = elements[i + j]; + elementsInSub++; + } + } + } else { + /* a partial row */ + k = ((currentRow * blocksPerRow) - codeblocks); + l = (currentRow * blocksPerRow) - reader - 1; + i = 2 + ((l - k) * 21); + for (j = 0; j < 21; j++) { + if ((i + j) < patternWidth) { + subElements[(20 - j) + (reader * 21) + 2] = elements[i + j]; + elementsInSub++; + } + } + } + } + reader++; + currentBlock++; + } while ((reader < blocksPerRow) && (currentBlock < codeblocks)); + + /* Row Stop */ + subElements[elementsInSub] = 1; + subElements[elementsInSub + 1] = 1; + elementsInSub += 2; + + pattern[symbolRow + compositeOffset] = ""; + black = true; + rowHeight[symbolRow + compositeOffset] = -1; + + if ((currentRow & 1) != 0) { + pattern[symbolRow + compositeOffset] = "0"; + black = false; + } else { + if ((currentRow == stackRows) + && (codeblocks != (currentRow * blocksPerRow)) + && ((((currentRow * blocksPerRow) - codeblocks) & 1) != 0)) { + /* Special case bottom row */ + specialCaseRow = true; + subElements[0] = 2; + pattern[symbolRow + compositeOffset] = "0"; + black = false; + } + } + + writer = 0; + + seperatorBinary = ""; + for (i = 0; i < elementsInSub; i++) { + pattern[symbolRow + compositeOffset] += (char) (subElements[i] + '0'); + for (j = 0; j < subElements[i]; j++) { + if (black) { + seperatorBinary += "0"; + } else { + seperatorBinary += "1"; + } + } + + black = !(black); + writer += subElements[i]; + } + seperatorBinary = "0000" + seperatorBinary.substring(4, writer - 4); + for (j = 0; j < reader; j++) { + k = (49 * j) + (specialCaseRow ? 19 : 18); + if (leftToRight) { + for (i = 0; i < 15; i++) { + if ((seperatorBinary.charAt(i + k - 1) == '1') + && (seperatorBinary.charAt(i + k) == '1')) { + seperatorBinary = seperatorBinary.substring(0, (i + k)) + + "0" + seperatorBinary.substring(i + k + 1); + } + } + } else { + for (i = 14; i >= 0; i--) { + if ((seperatorBinary.charAt(i + k + 1) == '1') + && (seperatorBinary.charAt(i + k) == '1')) { + seperatorBinary = seperatorBinary.substring(0, (i + k)) + + "0" + seperatorBinary.substring(i + k + 1); + } + } + } + } + seperatorPattern = bin2pat(seperatorBinary); + + if ((currentRow == 1) && linkageFlag) { + // Add composite code seperator + rowHeight[0] = 1; + pattern[0] = seperatorPattern; + } + + if (currentRow != 1) { + /* middle separator pattern (above current row) */ + pattern[symbolRow - 2 + compositeOffset] = "05"; + for (j = 5; j < (49 * blocksPerRow); j += 2) { + pattern[symbolRow - 2 + compositeOffset] += "11"; + } + rowHeight[symbolRow - 2 + compositeOffset] = 1; + /* bottom separator pattern (above current row) */ + rowHeight[symbolRow - 1 + compositeOffset] = 1; + pattern[symbolRow - 1 + compositeOffset] = seperatorPattern; + } + + if (currentRow != stackRows) { + rowHeight[symbolRow + 1 + compositeOffset] = 1; + pattern[symbolRow + 1 + compositeOffset] = seperatorPattern; + } + + symbolRow += 4; + } + readable = new StringBuilder(); + rowCount += compositeOffset; + } + + plotSymbol(); + return true; + } + + private boolean calculateBinaryString() { + /* Handles all data encodation from section 7.2.5 of ISO/IEC 24724 */ + EncodeMode lastMode = EncodeMode.NUMERIC; + int encodingMethod, i, j, readPosn; + boolean latch; + int remainder, d1, d2, value; + StringBuilder padstring; + double weight; + int groupVal; + int currentLength; + String patch; + + readPosn = 0; + + /* Decide whether a compressed data field is required and if so what + method to use - method 2 = no compressed data field */ + + if ((source.length() >= 16) && ((source.charAt(0) == '0') + && (source.charAt(1) == '1'))) { + /* (01) and other AIs */ + encodingMethod = 1; + } else { + /* any AIs */ + encodingMethod = 2; + } + + if (((source.length() >= 20) && (encodingMethod == 1)) + && ((source.charAt(2) == '9') && (source.charAt(16) == '3'))) { + /* Possibly encoding method > 2 */ + + if ((source.length() >= 26) && (source.charAt(17) == '1')) { + /* Methods 3, 7, 9, 11 and 13 */ + + if (source.charAt(18) == '0') { + /* (01) and (310x) */ + /* In kilos */ + + weight = 0.0; + for (i = 0; i < 6; i++) { + weight *= 10; + weight += (source.charAt(20 + i) - '0'); + } + + if (weight < 99999.0) { /* Maximum weight = 99999 */ + + if ((source.charAt(19) == '3') && (source.length() == 26)) { + /* (01) and (3103) */ + weight /= 1000.0; + + if (weight <= 32.767) { + encodingMethod = 3; + } + } + + if (source.length() == 34) { + if ((source.charAt(26) == '1') && (source.charAt(27) == '1')) { + /* (01), (310x) and (11) - metric weight and production date */ + encodingMethod = 7; + } + + if ((source.charAt(26) == '1') && (source.charAt(27) == '3')) { + /* (01), (310x) and (13) - metric weight and packaging date */ + encodingMethod = 9; + } + + if ((source.charAt(26) == '1') && (source.charAt(27) == '5')) { + /* (01), (310x) and (15) - metric weight and "best before" date */ + encodingMethod = 11; + } + + if ((source.charAt(26) == '1') && (source.charAt(27) == '7')) { + /* (01), (310x) and (17) - metric weight and expiration date */ + encodingMethod = 13; + } + } + } + } + } + + if ((source.length() >= 26) && (source.charAt(17) == '2')) { + /* Methods 4, 8, 10, 12 and 14 */ + + if (source.charAt(18) == '0') { + /* (01) and (320x) */ + /* In pounds */ + + weight = 0.0; + for (i = 0; i < 6; i++) { + weight *= 10; + weight += (source.charAt(20 + i) - '0'); + } + + if (weight < 99999.0) { /* Maximum weight = 99999 */ + + if (((source.charAt(19) == '2') || (source.charAt(19) == '3')) + && (source.length() == 26)) { + /* (01) and (3202)/(3203) */ + + if (source.charAt(19) == '3') { + weight /= 1000.0; + if (weight <= 22.767) { + encodingMethod = 4; + } + } else { + weight /= 100.0; + if (weight <= 99.99) { + encodingMethod = 4; + } + } + + } + + if (source.length() == 34) { + if ((source.charAt(26) == '1') && (source.charAt(27) == '1')) { + /* (01), (320x) and (11) - English weight and production date */ + encodingMethod = 8; + } + + if ((source.charAt(26) == '1') && (source.charAt(27) == '3')) { + /* (01), (320x) and (13) - English weight and packaging date */ + encodingMethod = 10; + } + + if ((source.charAt(26) == '1') && (source.charAt(27) == '5')) { + /* (01), (320x) and (15) - English weight and "best before" date */ + encodingMethod = 12; + } + + if ((source.charAt(26) == '1') && (source.charAt(27) == '7')) { + /* (01), (320x) and (17) - English weight and expiration date */ + encodingMethod = 14; + } + } + } + } + } + + if (source.charAt(17) == '9') { + /* Methods 5 and 6 */ + if ((source.charAt(18) == '2') && ((source.charAt(19) >= '0') + && (source.charAt(19) <= '3'))) { + /* (01) and (392x) */ + encodingMethod = 5; + } + if ((source.charAt(18) == '3') && ((source.charAt(19) >= '0') + && (source.charAt(19) <= '3'))) { + /* (01) and (393x) */ + encodingMethod = 6; + } + } + } + + encodeInfo.append("Encoding Method: ").append(Integer.toString(encodingMethod)).append("\n"); + switch (encodingMethod) { /* Encoding method - Table 10 */ + case 1: + binaryString.append("1XX"); + readPosn = 16; + break; + case 2: + binaryString.append("00XX"); + readPosn = 0; + break; + case 3: + binaryString.append("0100"); + readPosn = source.length(); + break; + case 4: + binaryString.append("0101"); + readPosn = source.length(); + break; + case 5: + binaryString.append("01100XX"); + readPosn = 20; + break; + case 6: + binaryString.append("01101XX"); + readPosn = 23; + break; + case 7: + binaryString.append("0111000"); + readPosn = source.length(); + break; + case 8: + binaryString.append("0111001"); + readPosn = source.length(); + break; + case 9: + binaryString.append("0111010"); + readPosn = source.length(); + break; + case 10: + binaryString.append("0111011"); + readPosn = source.length(); + break; + case 11: + binaryString.append("0111100"); + readPosn = source.length(); + break; + case 12: + binaryString.append("0111101"); + readPosn = source.length(); + break; + case 13: + binaryString.append("0111110"); + readPosn = source.length(); + break; + case 14: + binaryString.append("0111111"); + readPosn = source.length(); + break; + } + + + /* Variable length symbol bit field is just given a place holder (XX) + for the time being */ + + /* Verify that the data to be placed in the compressed data field is all + numeric data before carrying out compression */ + for (i = 0; i < readPosn; i++) { + if ((source.charAt(i) < '0') || (source.charAt(i) > '9')) { + if ((source.charAt(i) != '[') && (source.charAt(i) != ']')) { + /* Something is wrong */ + errorMsg.append("Invalid characters in input data"); + return false; + } + } + } + + /* Now encode the compressed data field */ + + if (encodingMethod == 1) { + /* Encoding method field "1" - general item identification data */ + groupVal = source.charAt(2) - '0'; + + for (j = 0; j < 4; j++) { + if ((groupVal & (0x08 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + + for (i = 1; i < 5; i++) { + groupVal = 100 * (source.charAt(i * 3) - '0'); + groupVal += 10 * (source.charAt((i * 3) + 1) - '0'); + groupVal += source.charAt((i * 3) + 2) - '0'; + + for (j = 0; j < 10; j++) { + if ((groupVal & (0x200 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + } + + if (encodingMethod == 3) { + /* Encoding method field "0100" - variable weight item + (0,001 kilogram icrements) */ + + for (i = 1; i < 5; i++) { + groupVal = 100 * (source.charAt(i * 3) - '0'); + groupVal += 10 * (source.charAt((i * 3) + 1) - '0'); + groupVal += (source.charAt((i * 3) + 2) - '0'); + + for (j = 0; j < 10; j++) { + if ((groupVal & (0x200 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + groupVal = 0; + for (i = 0; i < 6; i++) { + groupVal *= 10; + groupVal += source.charAt(20 + i) - '0'; + } + + for (j = 0; j < 15; j++) { + if ((groupVal & (0x4000 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if (encodingMethod == 4) { + /* Encoding method field "0101" - variable weight item (0,01 or + 0,001 pound increment) */ + + for (i = 1; i < 5; i++) { + groupVal = 100 * (source.charAt(i * 3) - '0'); + groupVal += 10 * (source.charAt((i * 3) + 1) - '0'); + groupVal += (source.charAt((i * 3) + 2) - '0'); + + for (j = 0; j < 10; j++) { + if ((groupVal & (0x200 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + + groupVal = 0; + for (i = 0; i < 6; i++) { + groupVal *= 10; + groupVal += source.charAt(20 + i) - '0'; + } + + if (source.charAt(19) == '3') { + groupVal = groupVal + 10000; + } + + for (j = 0; j < 15; j++) { + if ((groupVal & (0x4000 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if (encodingMethod >= 7) { + /* Encoding method fields "0111000" through "0111111" - variable + weight item plus date */ + + for (i = 1; i < 5; i++) { + groupVal = 100 * (source.charAt(i * 3) - '0'); + groupVal += 10 * (source.charAt((i * 3) + 1) - '0'); + groupVal += (source.charAt((i * 3) + 2) - '0'); + + for (j = 0; j < 10; j++) { + if ((groupVal & (0x200 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + groupVal = source.charAt(19) - '0'; + + for (i = 0; i < 5; i++) { + groupVal *= 10; + groupVal += source.charAt(21 + i) - '0'; + } + + for (j = 0; j < 20; j++) { + if ((groupVal & (0x80000 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + + if (source.length() == 34) { + /* Date information is included */ + groupVal = ((10 * (source.charAt(28) - '0')) + + (source.charAt(29) - '0')) * 384; + groupVal += (((10 * (source.charAt(30) - '0')) + + (source.charAt(31) - '0')) - 1) * 32; + groupVal += (10 * (source.charAt(32) - '0')) + + (source.charAt(33) - '0'); + } else { + groupVal = 38400; + } + + for (j = 0; j < 16; j++) { + if ((groupVal & (0x8000 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + if (encodingMethod == 5) { + /* Encoding method field "01100" - variable measure item and price */ + + for (i = 1; i < 5; i++) { + groupVal = 100 * (source.charAt(i * 3) - '0'); + groupVal += 10 * (source.charAt((i * 3) + 1) - '0'); + groupVal += (source.charAt((i * 3) + 2) - '0'); + + for (j = 0; j < 10; j++) { + if ((groupVal & (0x200 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + switch (source.charAt(19)) { + case '0': + binaryString.append("00"); + break; + case '1': + binaryString.append("01"); + break; + case '2': + binaryString.append("10"); + break; + case '3': + binaryString.append("11"); + break; + } + } + + if (encodingMethod == 6) { + /* Encoding method "01101" - variable measure item and price with ISO 4217 + Currency Code */ + + for (i = 1; i < 5; i++) { + groupVal = 100 * (source.charAt(i * 3) - '0'); + groupVal += 10 * (source.charAt((i * 3) + 1) - '0'); + groupVal += (source.charAt((i * 3) + 2) - '0'); + + for (j = 0; j < 10; j++) { + if ((groupVal & (0x200 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + switch (source.charAt(19)) { + case '0': + binaryString.append("00"); + break; + case '1': + binaryString.append("01"); + break; + case '2': + binaryString.append("10"); + break; + case '3': + binaryString.append("11"); + break; + } + + groupVal = 0; + for (i = 0; i < 3; i++) { + groupVal *= 10; + groupVal += source.charAt(20 + i) - '0'; + } + + for (j = 0; j < 10; j++) { + if ((groupVal & (0x200 >> j)) == 0) { + binaryString.append("0"); + } else { + binaryString.append("1"); + } + } + } + + /* The compressed data field has been processed if appropriate - the + rest of the data (if any) goes into a general-purpose data compaction field */ + + generalField = source.substring(readPosn); + generalFieldType = new EncodeMode[generalField.length()]; + + if (generalField.length() != 0) { + latch = false; + for (i = 0; i < generalField.length(); i++) { + /* Table 13 - ISO/IEC 646 encodation */ + if ((generalField.charAt(i) < ' ') || (generalField.charAt(i) > 'z')) { + generalFieldType[i] = EncodeMode.INVALID_CHAR; + latch = true; + } else { + generalFieldType[i] = EncodeMode.ISOIEC; + } + + if (generalField.charAt(i) == '#') { + generalFieldType[i] = EncodeMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == '$') { + generalFieldType[i] = EncodeMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == '@') { + generalFieldType[i] = EncodeMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == 92) { + generalFieldType[i] = EncodeMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == '^') { + generalFieldType[i] = EncodeMode.INVALID_CHAR; + latch = true; + } + if (generalField.charAt(i) == 96) { + generalFieldType[i] = EncodeMode.INVALID_CHAR; + latch = true; + } + + /* Table 12 - Alphanumeric encodation */ + if ((generalField.charAt(i) >= 'A') && (generalField.charAt(i) <= 'Z')) { + generalFieldType[i] = EncodeMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '*') { + generalFieldType[i] = EncodeMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == ',') { + generalFieldType[i] = EncodeMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '-') { + generalFieldType[i] = EncodeMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '.') { + generalFieldType[i] = EncodeMode.ALPHA_OR_ISO; + } + if (generalField.charAt(i) == '/') { + generalFieldType[i] = EncodeMode.ALPHA_OR_ISO; + } + + /* Numeric encodation */ + if ((generalField.charAt(i) >= '0') && (generalField.charAt(i) <= '9')) { + generalFieldType[i] = EncodeMode.ANY_ENC; + } + if (generalField.charAt(i) == '[') { + /* FNC1 can be encoded in any system */ + generalFieldType[i] = EncodeMode.ANY_ENC; + } + } + + if (latch) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + for (i = 0; i < generalField.length() - 1; i++) { + if ((generalFieldType[i] == EncodeMode.ISOIEC) + && (generalField.charAt(i + 1) == '[')) { + generalFieldType[i + 1] = EncodeMode.ISOIEC; + } + } + + for (i = 0; i < generalField.length() - 1; i++) { + if ((generalFieldType[i] == EncodeMode.ALPHA_OR_ISO) + && (generalField.charAt(i + 1) == '[')) { + generalFieldType[i + 1] = EncodeMode.ALPHA_OR_ISO; + } + } + + latch = applyGeneralFieldRules(); + + /* Set initial mode if not NUMERIC */ + if (generalFieldType[0] == EncodeMode.ALPHA) { + binaryString.append("0000"); /* Alphanumeric latch */ + lastMode = EncodeMode.ALPHA; + } + if (generalFieldType[0] == EncodeMode.ISOIEC) { + binaryString.append("0000"); /* Alphanumeric latch */ + binaryString.append("00100"); /* ISO/IEC 646 latch */ + lastMode = EncodeMode.ISOIEC; + } + + i = 0; + do { + switch (generalFieldType[i]) { + case NUMERIC: + + if (lastMode != EncodeMode.NUMERIC) { + binaryString.append("000"); /* Numeric latch */ + } + + if (generalField.charAt(i) != '[') { + d1 = generalField.charAt(i) - '0'; + } else { + d1 = 10; + } + + if (generalField.charAt(i + 1) != '[') { + d2 = generalField.charAt(i + 1) - '0'; + } else { + d2 = 10; + } + + value = (11 * d1) + d2 + 8; + + for (j = 0; j < 7; j++) { + if ((value & (0x40 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + + i += 2; + lastMode = EncodeMode.NUMERIC; + break; + + case ALPHA: + if (i != 0) { + if (lastMode == EncodeMode.NUMERIC) { + binaryString.append("0000"); /* Alphanumeric latch */ + } + if (lastMode == EncodeMode.ISOIEC) { + binaryString.append("00100"); /* Alphanumeric latch */ + } + } + + if ((generalField.charAt(i) >= '0') && (generalField.charAt(i) <= '9')) { + + value = generalField.charAt(i) - 43; + + for (j = 0; j < 5; j++) { + if ((value & (0x10 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + + if ((generalField.charAt(i) >= 'A') && (generalField.charAt(i) <= 'Z')) { + + value = generalField.charAt(i) - 33; + + for (j = 0; j < 6; j++) { + if ((value & (0x20 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + + lastMode = EncodeMode.ALPHA; + if (generalField.charAt(i) == '[') { + binaryString.append("01111"); + lastMode = EncodeMode.NUMERIC; + } /* FNC1/Numeric latch */ + if (generalField.charAt(i) == '*') { + binaryString.append("111010"); /* asterisk */ + } + if (generalField.charAt(i) == ',') { + binaryString.append("111011"); /* comma */ + } + if (generalField.charAt(i) == '-') { + binaryString.append("111100"); /* minus or hyphen */ + } + if (generalField.charAt(i) == '.') { + binaryString.append("111101"); /* period or full stop */ + } + if (generalField.charAt(i) == '/') { + binaryString.append("111110"); /* slash or solidus */ + } + + i++; + break; + + case ISOIEC: + if (i != 0) { + if (lastMode == EncodeMode.NUMERIC) { + binaryString.append("0000"); /* Alphanumeric latch */ + binaryString.append("00100"); /* ISO/IEC 646 latch */ + } + if (lastMode == EncodeMode.ALPHA) { + binaryString.append("00100"); /* ISO/IEC 646 latch */ + } + } + + if ((generalField.charAt(i) >= '0') + && (generalField.charAt(i) <= '9')) { + + value = generalField.charAt(i) - 43; + + for (j = 0; j < 5; j++) { + if ((value & (0x10 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + + if ((generalField.charAt(i) >= 'A') + && (generalField.charAt(i) <= 'Z')) { + + value = generalField.charAt(i) - 1; + + for (j = 0; j < 7; j++) { + if ((value & (0x40 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + + if ((generalField.charAt(i) >= 'a') + && (generalField.charAt(i) <= 'z')) { + + value = generalField.charAt(i) - 7; + + for (j = 0; j < 7; j++) { + if ((value & (0x40 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + + lastMode = EncodeMode.ISOIEC; + if (generalField.charAt(i) == '[') { + binaryString.append("01111"); + lastMode = EncodeMode.NUMERIC; + } /* FNC1/Numeric latch */ + if (generalField.charAt(i) == '!') { + binaryString.append("11101000"); /* exclamation mark */ + } + if (generalField.charAt(i) == 34) { + binaryString.append("11101001"); /* quotation mark */ + } + if (generalField.charAt(i) == 37) { + binaryString.append("11101010"); /* percent sign */ + } + if (generalField.charAt(i) == '&') { + binaryString.append("11101011"); /* ampersand */ + } + if (generalField.charAt(i) == 39) { + binaryString.append("11101100"); /* apostrophe */ + } + if (generalField.charAt(i) == '(') { + binaryString.append("11101101"); /* left parenthesis */ + } + if (generalField.charAt(i) == ')') { + binaryString.append("11101110"); /* right parenthesis */ + } + if (generalField.charAt(i) == '*') { + binaryString.append("11101111"); /* asterisk */ + } + if (generalField.charAt(i) == '+') { + binaryString.append("11110000"); /* plus sign */ + } + if (generalField.charAt(i) == ',') { + binaryString.append("11110001"); /* comma */ + } + if (generalField.charAt(i) == '-') { + binaryString.append("11110010"); /* minus or hyphen */ + } + if (generalField.charAt(i) == '.') { + binaryString.append("11110011"); /* period or full stop */ + } + if (generalField.charAt(i) == '/') { + binaryString.append("11110100"); /* slash or solidus */ + } + if (generalField.charAt(i) == ':') { + binaryString.append("11110101"); /* colon */ + } + if (generalField.charAt(i) == ';') { + binaryString.append("11110110"); /* semicolon */ + } + if (generalField.charAt(i) == '<') { + binaryString.append("11110111"); /* less-than sign */ + } + if (generalField.charAt(i) == '=') { + binaryString.append("11111000"); /* equals sign */ + } + if (generalField.charAt(i) == '>') { + binaryString.append("11111001"); /* greater-than sign */ + } + if (generalField.charAt(i) == '?') { + binaryString.append("11111010"); /* question mark */ + } + if (generalField.charAt(i) == '_') { + binaryString.append("11111011"); /* underline or low line */ + } + if (generalField.charAt(i) == ' ') { + binaryString.append("11111100"); /* space */ + } + + i++; + break; + } + currentLength = i; + if (latch) { + currentLength++; + } + } while (currentLength < generalField.length()); + + remainder = calculateRemainder(binaryString.length()); + + if (latch) { + /* There is still one more numeric digit to encode */ + + if (lastMode == EncodeMode.NUMERIC) { + if ((remainder >= 4) && (remainder <= 6)) { + value = generalField.charAt(i) - '0'; + value++; + + for (j = 0; j < 4; j++) { + if ((value & (0x08 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } else { + d1 = generalField.charAt(i) - '0'; + d2 = 10; + + value = (11 * d1) + d2 + 8; + + for (j = 0; j < 7; j++) { + if ((value & (0x40 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + } else { + value = generalField.charAt(i) - 43; + + for (j = 0; j < 5; j++) { + if ((value & (0x10 >> j)) != 0) { + binaryString.append("1"); + } else { + binaryString.append("0"); + } + } + } + } + } + + if (binaryString.length() > 252) { + errorMsg.append("Input too long"); + return false; + } + + remainder = calculateRemainder(binaryString.length()); + + /* Now add padding to binary string (7.2.5.5.4) */ + i = remainder; + if ((generalField.length() != 0) && (lastMode == EncodeMode.NUMERIC)) { + padstring = new StringBuilder("0000"); + i -= 4; + } else { + padstring = new StringBuilder(); + } + for (; i > 0; i -= 5) { + padstring.append("00100"); + } + + binaryString.append(padstring.substring(0, remainder)); + + /* Patch variable length symbol bit field */ + patch = ""; + if ((((binaryString.length() / 12) + 1) & 1) == 0) { + patch += "0"; + } else { + patch += "1"; + } + if (binaryString.length() <= 156) { + patch += "0"; + } else { + patch += "1"; + } + + if (encodingMethod == 1) { + binaryString = new StringBuilder(binaryString.substring(0, 2)) + .append(patch) + .append(binaryString.substring(4)); + } + if (encodingMethod == 2) { + binaryString = new StringBuilder(binaryString.substring(0, 3)) + .append(patch) + .append(binaryString.substring(5)); + } + if ((encodingMethod == 5) || (encodingMethod == 6)) { + binaryString = new StringBuilder(binaryString.substring(0, 6)) + .append(patch) + .append(binaryString.substring(8)); + } + + encodeInfo.append("Binary length: ").append(Integer.toString(binaryString.length())).append("\n"); + displayBinaryString(); + return true; + } + + private void displayBinaryString() { + int i, nibble; + /* Display binary string as hexadecimal */ + + encodeInfo.append("Binary String: "); + nibble = 0; + for (i = 0; i < binaryString.length(); i++) { + switch (i % 4) { + case 0: + if (binaryString.charAt(i) == '1') { + nibble += 8; + } + break; + case 1: + if (binaryString.charAt(i) == '1') { + nibble += 4; + } + break; + case 2: + if (binaryString.charAt(i) == '1') { + nibble += 2; + } + break; + case 3: + if (binaryString.charAt(i) == '1') { + nibble += 1; + } + encodeInfo.append(Integer.toHexString(nibble)); + nibble = 0; + break; + } + } + + if ((binaryString.length() % 4) != 0) { + encodeInfo.append(Integer.toHexString(nibble)); + } + encodeInfo.append("\n"); + } + + private boolean applyGeneralFieldRules() { + /* Attempts to apply encoding rules from secions 7.2.5.5.1 to 7.2.5.5.3 + of ISO/IEC 24724:2006 */ + + int blockCount, i, j, k; + EncodeMode current, next, last; + int[] blockLength = new int[200]; + EncodeMode[] blockType = new EncodeMode[200]; + + blockCount = 0; + + blockLength[blockCount] = 1; + blockType[blockCount] = generalFieldType[0]; + + for (i = 1; i < generalField.length(); i++) { + current = generalFieldType[i]; + last = generalFieldType[i - 1]; + + if (current == last) { + blockLength[blockCount] = blockLength[blockCount] + 1; + } else { + blockCount++; + blockLength[blockCount] = 1; + blockType[blockCount] = generalFieldType[i]; + } + } + + blockCount++; + + for (i = 0; i < blockCount; i++) { + current = blockType[i]; + next = blockType[i + 1]; + + if ((current == EncodeMode.ISOIEC) && (i != (blockCount - 1))) { + if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] >= 4)) { + blockType[i + 1] = EncodeMode.NUMERIC; + } + if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] < 4)) { + blockType[i + 1] = EncodeMode.ISOIEC; + } + if ((next == EncodeMode.ALPHA_OR_ISO) && (blockLength[i + 1] >= 5)) { + blockType[i + 1] = EncodeMode.ALPHA; + } + if ((next == EncodeMode.ALPHA_OR_ISO) && (blockLength[i + 1] < 5)) { + blockType[i + 1] = EncodeMode.ISOIEC; + } + } + + if (current == EncodeMode.ALPHA_OR_ISO) { + blockType[i] = EncodeMode.ALPHA; + current = EncodeMode.ALPHA; + } + + if ((current == EncodeMode.ALPHA) && (i != (blockCount - 1))) { + if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] >= 6)) { + blockType[i + 1] = EncodeMode.NUMERIC; + } + if ((next == EncodeMode.ANY_ENC) && (blockLength[i + 1] < 6)) { + if ((i == blockCount - 2) && (blockLength[i + 1] >= 4)) { + blockType[i + 1] = EncodeMode.NUMERIC; + } else { + blockType[i + 1] = EncodeMode.ALPHA; + } + } + } + + if (current == EncodeMode.ANY_ENC) { + blockType[i] = EncodeMode.NUMERIC; + } + } + + if (blockCount > 1) { + i = 1; + while (i < blockCount) { + if (blockType[i - 1] == blockType[i]) { + /* bring together */ + blockLength[i - 1] = blockLength[i - 1] + blockLength[i]; + j = i + 1; + + /* decreace the list */ + while (j < blockCount) { + blockLength[j - 1] = blockLength[j]; + blockType[j - 1] = blockType[j]; + j++; + } + blockCount--; + i--; + } + i++; + } + } + + for (i = 0; i < blockCount - 1; i++) { + if ((blockType[i] == EncodeMode.NUMERIC) && ((blockLength[i] & 1) != 0)) { + /* Odd size numeric block */ + blockLength[i] = blockLength[i] - 1; + blockLength[i + 1] = blockLength[i + 1] + 1; + } + } + + j = 0; + for (i = 0; i < blockCount; i++) { + for (k = 0; k < blockLength[i]; k++) { + generalFieldType[j] = blockType[i]; + j++; + } + } + + /* If the last block is numeric and an odd size, further + processing needs to be done outside this procedure */ + return (blockType[blockCount - 1] == EncodeMode.NUMERIC) + && ((blockLength[blockCount - 1] & 1) != 0); + } + + private int getCombinations(int n, int r) { + int i, j; + int maxDenom, minDenom; + int val; + + if (n - r > r) { + minDenom = r; + maxDenom = n - r; + } else { + minDenom = n - r; + maxDenom = r; + } + val = 1; + j = 1; + for (i = n; i > maxDenom; i--) { + val *= i; + if (j <= minDenom) { + val /= j; + j++; + } + } + for (; j <= minDenom; j++) { + val /= j; + } + return (val); + } + + private void getWidths(int val, int n, int elements, int maxWidth, int noNarrow) { + int bar; + int elmWidth; + int mxwElement; + int subVal, lessVal; + int narrowMask = 0; + for (bar = 0; bar < elements - 1; bar++) { + for (elmWidth = 1, narrowMask |= (1 << bar); ; + elmWidth++, narrowMask &= ~(1 << bar)) { + /* get all combinations */ + subVal = getCombinations(n - elmWidth - 1, elements - bar - 2); + /* less combinations with no single-module element */ + if ((noNarrow == 0) && (narrowMask == 0) + && (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) { + subVal -= getCombinations(n - elmWidth - (elements - bar), + elements - bar - 2); + } + /* less combinations with elements > maxVal */ + if (elements - bar - 1 > 1) { + lessVal = 0; + for (mxwElement = n - elmWidth - (elements - bar - 2); + mxwElement > maxWidth; + mxwElement--) { + lessVal += getCombinations(n - elmWidth - mxwElement - 1, + elements - bar - 3); + } + subVal -= lessVal * (elements - 1 - bar); + } else if (n - elmWidth > maxWidth) { + subVal--; + } + val -= subVal; + if (val < 0) break; + } + val += subVal; + n -= elmWidth; + widths[bar] = elmWidth; + } + widths[bar] = n; + } + + private enum dbeMode { + UNSTACKED, STACKED + } + + private enum EncodeMode { + NUMERIC, ALPHA, ISOIEC, INVALID_CHAR, ANY_ENC, ALPHA_OR_ISO + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/DataBarLimited.java b/barcode/src/main/java/org/xbib/graphics/barcode/DataBarLimited.java new file mode 100755 index 0000000..4163543 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/DataBarLimited.java @@ -0,0 +1,476 @@ +package org.xbib.graphics.barcode; + +import java.math.BigInteger; + +/** + * Implements GS1 DataBar Limited according to ISO/IEC 24724:2011. + * Input data should be a 12 digit Global Trade Identification Number + * without check digit or Application Identifier [01]. + */ +public class DataBarLimited extends Symbol { + + private static final int[] t_even_ltd = { + 28, 728, 6454, 203, 2408, 1, 16632 + }; + private static final int[] modules_odd_ltd = { + 17, 13, 9, 15, 11, 19, 7 + }; + private static final int[] modules_even_ltd = { + 9, 13, 17, 11, 15, 7, 19 + }; + private static final int[] widest_odd_ltd = { + 6, 5, 3, 5, 4, 8, 1 + }; + private static final int[] widest_even_ltd = { + 3, 4, 6, 4, 5, 1, 8 + }; + private static final int[] checksum_weight_ltd = { /* Table 7 */ + 1, 3, 9, 27, 81, 65, 17, 51, 64, 14, 42, 37, 22, 66, + 20, 60, 2, 6, 18, 54, 73, 41, 34, 13, 39, 28, 84, 74 + }; + private static final int[] finder_pattern_ltd = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 2, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, + 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 3, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 2, 1, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 2, 1, 1, 1, + 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1, + 1, 1, 1, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1, 1, + 1, 2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1, + 1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, + 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, + 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1, + 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, + 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1, + 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 2, 1, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, + 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, + 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, + 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, + 1, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, + 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, + 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, + 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, + 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, + 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, + 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1 /* ГОСТ ISO/IEC 24724-2011 Ñтраница 57 */ + }; + + private boolean linkageFlag; + private int[] widths = new int[8]; + + public DataBarLimited() { + linkageFlag = false; + } + + @Override + public void setDataType(DataType dummy) { + // Do nothing! + } + + protected void setLinkageFlag() { + linkageFlag = true; + } + + @Override + public boolean encode() { + BigInteger accum; + BigInteger left_reg; + BigInteger right_reg; + int left_group; + int right_group; + int i, j; + int left_character; + int right_character; + int left_odd; + int right_odd; + int left_even; + int right_even; + int[] left_widths = new int[14]; + int[] right_widths = new int[14]; + int checksum; + int[] check_elements = new int[14]; + int[] total_widths = new int[46]; + StringBuilder bin; + StringBuilder notbin; + boolean bar_latch; + int writer; + int check_digit; + int count = 0; + StringBuilder hrt; + int compositeOffset = 0; + + if (content.length() > 13) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+?"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (content.length() == 13) { + if ((content.charAt(0) != '0') && (content.charAt(0) != '1')) { + errorMsg.append("Input out of range"); + return false; + } + } + + accum = new BigInteger(content); + + if (linkageFlag) { + /* Add symbol linkage flag */ + accum = accum.add(new BigInteger("2015133531096")); + } + + /* Calculate left and right pair values */ + left_reg = accum.divide(new BigInteger("2013571")); + right_reg = accum.mod(new BigInteger("2013571")); + + left_group = 0; + if (left_reg.compareTo(new BigInteger("183063")) > 0) { + left_group = 1; + } + if (left_reg.compareTo(new BigInteger("820063")) > 0) { + left_group = 2; + } + if (left_reg.compareTo(new BigInteger("1000775")) > 0) { + left_group = 3; + } + if (left_reg.compareTo(new BigInteger("1491020")) > 0) { + left_group = 4; + } + if (left_reg.compareTo(new BigInteger("1979844")) > 0) { + left_group = 5; + } + if (left_reg.compareTo(new BigInteger("1996938")) > 0) { + left_group = 6; + } + + right_group = 0; + if (right_reg.compareTo(new BigInteger("183063")) > 0) { + right_group = 1; + } + if (right_reg.compareTo(new BigInteger("820063")) > 0) { + right_group = 2; + } + if (right_reg.compareTo(new BigInteger("1000775")) > 0) { + right_group = 3; + } + if (right_reg.compareTo(new BigInteger("1491020")) > 0) { + right_group = 4; + } + if (right_reg.compareTo(new BigInteger("1979844")) > 0) { + right_group = 5; + } + if (right_reg.compareTo(new BigInteger("1996938")) > 0) { + right_group = 6; + } + + encodeInfo.append("Data Characters: ").append(Integer.toString(left_group + 1)).append(" ").append(Integer.toString(right_group + 1)).append("\n"); + + switch (left_group) { + case 1: + left_reg = left_reg.subtract(new BigInteger("183064")); + break; + case 2: + left_reg = left_reg.subtract(new BigInteger("820064")); + break; + case 3: + left_reg = left_reg.subtract(new BigInteger("1000776")); + break; + case 4: + left_reg = left_reg.subtract(new BigInteger("1491021")); + break; + case 5: + left_reg = left_reg.subtract(new BigInteger("1979845")); + break; + case 6: + left_reg = left_reg.subtract(new BigInteger("1996939")); + break; + } + + switch (right_group) { + case 1: + right_reg = right_reg.subtract(new BigInteger("183064")); + break; + case 2: + right_reg = right_reg.subtract(new BigInteger("820064")); + break; + case 3: + right_reg = right_reg.subtract(new BigInteger("1000776")); + break; + case 4: + right_reg = right_reg.subtract(new BigInteger("1491021")); + break; + case 5: + right_reg = right_reg.subtract(new BigInteger("1979845")); + break; + case 6: + right_reg = right_reg.subtract(new BigInteger("1996939")); + break; + } + + left_character = left_reg.intValue(); + right_character = right_reg.intValue(); + + left_odd = left_character / t_even_ltd[left_group]; + left_even = left_character % t_even_ltd[left_group]; + right_odd = right_character / t_even_ltd[right_group]; + right_even = right_character % t_even_ltd[right_group]; + + getWidths(left_odd, modules_odd_ltd[left_group], 7, widest_odd_ltd[left_group], 1); + left_widths[0] = widths[0]; + left_widths[2] = widths[1]; + left_widths[4] = widths[2]; + left_widths[6] = widths[3]; + left_widths[8] = widths[4]; + left_widths[10] = widths[5]; + left_widths[12] = widths[6]; + getWidths(left_even, modules_even_ltd[left_group], 7, widest_even_ltd[left_group], 0); + left_widths[1] = widths[0]; + left_widths[3] = widths[1]; + left_widths[5] = widths[2]; + left_widths[7] = widths[3]; + left_widths[9] = widths[4]; + left_widths[11] = widths[5]; + left_widths[13] = widths[6]; + getWidths(right_odd, modules_odd_ltd[right_group], 7, widest_odd_ltd[right_group], 1); + right_widths[0] = widths[0]; + right_widths[2] = widths[1]; + right_widths[4] = widths[2]; + right_widths[6] = widths[3]; + right_widths[8] = widths[4]; + right_widths[10] = widths[5]; + right_widths[12] = widths[6]; + getWidths(right_even, modules_even_ltd[right_group], 7, widest_even_ltd[right_group], 0); + right_widths[1] = widths[0]; + right_widths[3] = widths[1]; + right_widths[5] = widths[2]; + right_widths[7] = widths[3]; + right_widths[9] = widths[4]; + right_widths[11] = widths[5]; + right_widths[13] = widths[6]; + + checksum = 0; + /* Calculate the checksum */ + for (i = 0; i < 14; i++) { + checksum += checksum_weight_ltd[i] * left_widths[i]; + checksum += checksum_weight_ltd[i + 14] * right_widths[i]; + } + checksum %= 89; + + encodeInfo.append("Checksum: ").append(Integer.toString(checksum)).append("\n"); + + for (i = 0; i < 14; i++) { + check_elements[i] = finder_pattern_ltd[i + (checksum * 14)]; + } + + total_widths[0] = 1; + total_widths[1] = 1; + total_widths[44] = 1; + total_widths[45] = 1; + for (i = 0; i < 14; i++) { + total_widths[i + 2] = left_widths[i]; + total_widths[i + 16] = check_elements[i]; + total_widths[i + 30] = right_widths[i]; + } + + bin = new StringBuilder(); + notbin = new StringBuilder(); + writer = 0; + bar_latch = false; + for (i = 0; i < 46; i++) { + for (j = 0; j < total_widths[i]; j++) { + if (bar_latch) { + bin.append("1"); + notbin.append("0"); + } else { + bin.append("0"); + notbin.append("1"); + } + writer++; + } + bar_latch = !bar_latch; + } + + if (symbolWidth < (writer + 20)) { + symbolWidth = writer + 20; + } + + /* Calculate check digit from Annex A and place human readable text */ + + readable = new StringBuilder("(01)"); + hrt = new StringBuilder(); + for (i = content.length(); i < 13; i++) { + hrt.append("0"); + } + hrt.append(content); + + for (i = 0; i < 13; i++) { + count += (hrt.charAt(i) - '0'); + + if ((i & 1) == 0) { + count += 2 * (hrt.charAt(i) - '0'); + } + } + + check_digit = 10 - (count % 10); + if (check_digit == 10) { + check_digit = 0; + } + + hrt.append((char) (check_digit + '0')); + readable.append(hrt); + + if (linkageFlag) { + compositeOffset = 1; + } + + rowCount = 1 + compositeOffset; + rowHeight = new int[1 + compositeOffset]; + rowHeight[compositeOffset] = -1; + pattern = new String[1 + compositeOffset]; + pattern[compositeOffset] = "0:" + bin2pat(bin.toString()); + + if (linkageFlag) { + // Add composite symbol seperator + notbin = new StringBuilder(notbin.substring(4, 70)); + rowHeight[0] = 1; + pattern[0] = "0:04" + bin2pat(notbin.toString()); + } + + plotSymbol(); + return true; + } + + private int getCombinations(int n, int r) { + int i, j; + int maxDenom, minDenom; + int val; + + if (n - r > r) { + minDenom = r; + maxDenom = n - r; + } else { + minDenom = n - r; + maxDenom = r; + } + val = 1; + j = 1; + for (i = n; i > maxDenom; i--) { + val *= i; + if (j <= minDenom) { + val /= j; + j++; + } + } + for (; j <= minDenom; j++) { + val /= j; + } + return (val); + } + + private void getWidths(int val, int n, int elements, int maxWidth, int noNarrow) { + int bar; + int elmWidth; + int mxwElement; + int subVal, lessVal; + int narrowMask = 0; + for (bar = 0; bar < elements - 1; bar++) { + for (elmWidth = 1, narrowMask |= (1 << bar); ; + elmWidth++, narrowMask &= ~(1 << bar)) { + /* get all combinations */ + subVal = getCombinations(n - elmWidth - 1, elements - bar - 2); + /* less combinations with no single-module element */ + if ((noNarrow == 0) && (narrowMask == 0) + && (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) { + subVal -= getCombinations(n - elmWidth - (elements - bar), elements - bar - 2); + } + /* less combinations with elements > maxVal */ + if (elements - bar - 1 > 1) { + lessVal = 0; + for (mxwElement = n - elmWidth - (elements - bar - 2); + mxwElement > maxWidth; + mxwElement--) { + lessVal += getCombinations(n - elmWidth - mxwElement - 1, elements - bar - 3); + } + subVal -= lessVal * (elements - 1 - bar); + } else if (n - elmWidth > maxWidth) { + subVal--; + } + val -= subVal; + if (val < 0) break; + } + val += subVal; + n -= elmWidth; + widths[bar] = elmWidth; + } + widths[bar] = n; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/DataMatrix.java b/barcode/src/main/java/org/xbib/graphics/barcode/DataMatrix.java new file mode 100755 index 0000000..774db2a --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/DataMatrix.java @@ -0,0 +1,1636 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; + +/** + * Implements Data Matrix ECC 200 bar code symbology according to ISO/IEC + * 16022:2006. + * Data Matrix is a 2D matrix symbology capable of encoding characters in the + * ISO/IEC 8859-1 (Latin-1) character set. + */ +public class DataMatrix extends Symbol { + + private static final int[] c40_shift = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3 + }; + + private static final int[] c40_value = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, + 17, 18, 19, 20, 21, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 22, 23, 24, 25, 26, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 + }; + + private static final int[] text_shift = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 3, 3, 3, 3 + }; + + private static final int[] text_value = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, + 17, 18, 19, 20, 21, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 22, 23, 24, 25, 26, 0, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 27, 28, 29, 30, 31 + }; + + private static final int[] intsymbol = { + 0, 1, 3, 5, 7, 8, 10, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 2, 4, 6, 9, 11, 14 + }; + + private static final int[] matrixH = { + 10, 12, 8, 14, 8, 16, 12, 18, 20, 12, 22, 16, 24, 26, 16, 32, 36, 40, + 44, 48, 52, 64, 72, 80, 88, 96, 104, 120, 132, 144 + }; + + private static final int[] matrixW = { + 10, 12, 18, 14, 32, 16, 26, 18, 20, 36, 22, 36, 24, 26, 48, 32, 36, 40, + 44, 48, 52, 64, 72, 80, 88, 96, 104, 120, 132, 144 + }; + + private static final int[] matrixFH = { + 10, 12, 8, 14, 8, 16, 12, 18, 20, 12, 22, 16, 24, 26, 16, 16, 18, 20, + 22, 24, 26, 16, 18, 20, 22, 24, 26, 20, 22, 24 + }; + + private static final int[] matrixFW = { + 10, 12, 18, 14, 16, 16, 26, 18, 20, 18, 22, 18, 24, 26, 24, 16, 18, 20, + 22, 24, 26, 16, 18, 20, 22, 24, 26, 20, 22, 24 + }; + + private static final int[] matrixbytes = { + 3, 5, 5, 8, 10, 12, 16, 18, 22, 22, 30, 32, 36, 44, 49, 62, 86, 114, + 144, 174, 204, 280, 368, 456, 576, 696, 816, 1050, 1304, 1558 + }; + + private static final int[] matrixdatablock = { + 3, 5, 5, 8, 10, 12, 16, 18, 22, 22, 30, 32, 36, 44, 49, 62, 86, 114, + 144, 174, 102, 140, 92, 114, 144, 174, 136, 175, 163, 156 + }; + + private static final int[] matrixrsblock = { + 5, 7, 7, 10, 11, 12, 14, 14, 18, 18, 20, 24, 24, 28, 28, 36, 42, 48, 56, + 68, 42, 56, 36, 48, 56, 68, 56, 68, 62, 62 + }; + private int[] target = new int[2200]; + private int[] binary = new int[2200]; + private int binary_length; + private dm_mode last_mode; + private int[] places; + private boolean isSquare; + private int[] inputData; + private int preferredSize = 0; + private int process_p; + private int[] process_buffer = new int[8]; + + public DataMatrix() { + isSquare = true; + } + + /** + * Override selection of symbol size. When set as false the + * symbol will be the smallest available for the amount of data given. When + * set as true the encoding will not use rectangular symbols. + * + * @param input Forces a square symbol when set to true + */ + public void forceSquare(boolean input) { + isSquare = input; + } + + /** + * Set the prefereed symbol size according to the values in the following + * table. Values may be ignored if the data is too big to fit in the + * specified symbol, or if forceSquare mode has been invoked. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Available Data Matrix symbol sizes

+ * Input

+ * Symbol Size

+ * Input

+ * Symbol Size

+ * 1

+ * 10 x 10

+ * 16

+ * 64 x 64

+ * 2

+ * 12 x 12

+ * 17

+ * 72 x 72

+ * 3

+ * 14 x 14

+ * 18

+ * 80 x 80

+ * 4

+ * 16 x 16

+ * 19

+ * 88 x 88

+ * 5

+ * 18 x 18

+ * 20

+ * 96 x 96

+ * 6

+ * 20 x 20

+ * 21

+ * 104 x 104

+ * 7

+ * 22 x 22

+ * 22

+ * 120 x 120

+ * 8

+ * 24 x 24

+ * 23

+ * 132 x 132

+ * 9

+ * 26 x 26

+ * 24

+ * 144 x 144

+ * 10

+ * 32 x 32

+ * 25

+ * 8 x 18

+ * 11

+ * 36 x 36

+ * 26

+ * 8 x 32

+ * 12

+ * 40 x 40

+ * 27

+ * 12 x 26

+ * 13

+ * 44 x 44

+ * 28

+ * 12 x 36

+ * 14

+ * 48 x 48

+ * 29

+ * 16 x 36

+ * 15

+ * 52 x 52

+ * 30

+ * 16 x 48

+ * + * @param size Symbol size + */ + public void setPreferredSize(int size) { + preferredSize = size; + } + + @Override + public boolean encode() { + int i, binlen, skew = 0; + int symbolsize, optionsize, calcsize; + int taillength; + int H, W, FH, FW, datablock, bytes, rsblock; + int x, y, NC, NR, v; + int[] grid; + StringBuilder bin; + + eciProcess(); // Get ECI mode + + inputData = new int[content.length()]; + for (i = 0; i < content.length(); i++) { + inputData[i] = inputBytes[i] & 0xFF; + } + + binlen = generateCodewords(); + + if (binlen == 0) { + errorMsg.append("Data too long to fit in symbol"); + return false; + } + + if ((preferredSize >= 1) && (preferredSize <= 30)) { + optionsize = intsymbol[preferredSize - 1]; + } else { + optionsize = -1; + } + + calcsize = 29; + for (i = 29; i > -1; i--) { + if (matrixbytes[i] >= (binlen + process_p)) { + calcsize = i; + } + } + + if (isSquare) { + // Force to use square symbol + switch (calcsize) { + case 2: + case 4: + case 6: + case 9: + case 11: + case 14: + calcsize++; + break; + default: + break; + } + } + + symbolsize = optionsize; + if (calcsize > optionsize) { + symbolsize = calcsize; + if (optionsize != -1) { + /* flag an error */ + errorMsg.append("Data does not fit in selected symbol size"); + return false; + } + } + + // Now we know the symbol size we can handle the remaining data in the process buffer. + if (process_p != 0) { + binlen = encodeRemainder(matrixbytes[symbolsize] - binlen, binlen); + } + + H = matrixH[symbolsize]; + W = matrixW[symbolsize]; + FH = matrixFH[symbolsize]; + FW = matrixFW[symbolsize]; + bytes = matrixbytes[symbolsize]; + datablock = matrixdatablock[symbolsize]; + rsblock = matrixrsblock[symbolsize]; + + taillength = bytes - binlen; + + if (taillength != 0) { + addPadBits(binlen, taillength); + } + + // ecc code + if (symbolsize == 29) { + skew = 1; + } + calculateErrorCorrection(bytes, datablock, rsblock, skew); + NC = W - 2 * (W / FW); + NR = H - 2 * (H / FH); + places = new int[NC * NR]; + placeData(NR, NC); + grid = new int[W * H]; + for (i = 0; i < (W * H); i++) { + grid[i] = 0; + } + for (y = 0; y < H; y += FH) { + for (x = 0; x < W; x++) { + grid[y * W + x] = 1; + } + for (x = 0; x < W; x += 2) { + grid[(y + FH - 1) * W + x] = 1; + } + } + for (x = 0; x < W; x += FW) { + for (y = 0; y < H; y++) { + grid[y * W + x] = 1; + } + for (y = 0; y < H; y += 2) { + grid[y * W + x + FW - 1] = 1; + } + } + for (y = 0; y < NR; y++) { + for (x = 0; x < NC; x++) { + v = places[(NR - y - 1) * NC + x]; + if (v == 1 || (v > 7 && (target[(v >> 3) - 1] & (1 << (v & 7))) != 0)) { + grid[(1 + y + 2 * (y / (FH - 2))) * W + 1 + x + 2 * (x / (FW - 2))] = 1; + } + } + } + + readable = new StringBuilder(); + pattern = new String[H]; + rowCount = H; + rowHeight = new int[H]; + for (y = H - 1; y >= 0; y--) { + bin = new StringBuilder(); + for (x = 0; x < W; x++) { + if (grid[W * y + x] == 1) { + bin.append("1"); + } else { + bin.append("0"); + } + } + pattern[(H - y) - 1] = bin2pat(bin.toString()); + rowHeight[(H - y) - 1] = 1; + } + + encodeInfo.append("Grid Size: ").append(W).append(" X ").append(H).append("\n"); + encodeInfo.append("Data Codewords: ").append(datablock).append("\n"); + encodeInfo.append("ECC Codewords: ").append(rsblock).append("\n"); + + plotSymbol(); + return true; + } + + private int generateCodewords() { + /* Encodes data using ASCII, C40, Text, X12, EDIFACT or Base 256 modes as appropriate */ + /* Supports encoding FNC1 in supporting systems */ + /* Supports ECI encoding for whole message only, not inline switching */ + + encodeInfo.append("Encoding: "); + int sp, tp, i; + dm_mode current_mode, next_mode; + int inputlen = content.length(); + + sp = 0; + tp = 0; + process_p = 0; + + for (i = 0; i < 8; i++) { + process_buffer[i] = 0; + } + binary_length = 0; + + /* step (a) */ + current_mode = dm_mode.DM_ASCII; + next_mode = dm_mode.DM_ASCII; + + if (inputDataType == DataType.GS1) { + target[tp] = 232; + tp++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("FNC1 "); + } /* FNC1 */ + + if (eciMode != 3) { + target[tp] = 241; // ECI + tp++; + + if (eciMode <= 126) { + target[tp] = eciMode + 1; + tp++; + } + + if ((eciMode >= 127) && (eciMode <= 16382)) { + target[tp] = ((eciMode - 127) / 254) + 128; + tp++; + target[tp] = ((eciMode - 127) % 254) + 1; + tp++; + } + + if (eciMode >= 16383) { + target[tp] = ((eciMode - 16383) / 64516) + 192; + tp++; + target[tp] = (((eciMode - 16383) / 254) % 254) + 1; + tp++; + target[tp] = ((eciMode - 16383) & 254) + 1; + tp++; + } + + encodeInfo.append("ECI "); + } + + if (readerInit) { + if (inputDataType == DataType.GS1) { + errorMsg.append("Cannot encode in GS1 mode and Reader Initialisation at the same time"); + return 0; + } else { + target[tp] = 234; + tp++; /* Reader Programming */ + + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("RP "); + } + } + + /* Check for Macro05/Macro06 */ + /* "[)>[RS]05[GS]...[RS][EOT]" -> CW 236 */ + /* "[)>[RS]06[GS]...[RS][EOT]" -> CW 237 */ + if (tp == 0 & sp == 0 && inputlen >= 9) { + if (inputData[0] == '[' && inputData[1] == ')' && inputData[2] == '>' + && inputData[3] == '\u001e' && inputData[4] == '0' + && (inputData[5] == '5' || inputData[5] == '6') + && inputData[6] == '\u001d' + && inputData[inputlen - 2] == '\u001e' + && inputData[inputlen - 1] == '\u0004') { + /* Output macro Codeword */ + if (inputData[5] == '5') { + target[tp] = 236; + encodeInfo.append("Micro05 "); + } else { + target[tp] = 237; + encodeInfo.append("Macro06 "); + } + tp++; + binary[binary_length] = ' '; + binary_length++; + /* Remove macro characters from input string */ + sp = 7; + inputlen -= 2; + } + } + + while (sp < inputlen) { + + current_mode = next_mode; + + /* step (b) - ASCII encodation */ + if (current_mode == dm_mode.DM_ASCII) { + next_mode = dm_mode.DM_ASCII; + + for (i = 0; i < 8; i++) { + process_buffer[i] = 0; + } + + if (isTwoDigits(sp)) { + target[tp] = (10 * Character.getNumericValue(inputData[sp])) + + Character.getNumericValue(inputData[sp + 1]) + 130; + encodeInfo.append(Integer.toString(target[tp] - 130)).append(" "); + tp++; + binary[binary_length] = ' '; + binary_length++; + sp += 2; + } else { + next_mode = lookAheadTest(sp, current_mode); + + if (next_mode != dm_mode.DM_ASCII) { + switch (next_mode) { + case DM_C40: + target[tp] = 230; + tp++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("C40 "); + break; + case DM_TEXT: + target[tp] = 239; + tp++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("TEX "); + break; + case DM_X12: + target[tp] = 238; + tp++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("X12 "); + break; + case DM_EDIFACT: + target[tp] = 240; + tp++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("EDI "); + break; + case DM_BASE256: + target[tp] = 231; + tp++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("BAS "); + break; + } + } else { + if (inputData[sp] > 127) { + target[tp] = 235; /* FNC4 */ + + encodeInfo.append("FNC4 "); + tp++; + target[tp] = (inputData[sp] - 128) + 1; + encodeInfo.append(Integer.toString(target[tp] - 1)).append(" "); + tp++; + binary[binary_length] = ' '; + binary_length++; + binary[binary_length] = ' '; + binary_length++; + } else { + if ((inputDataType == DataType.GS1) && (inputData[sp] == '[')) { + target[tp] = 232; /* FNC1 */ + encodeInfo.append("FNC1 "); + } else { + target[tp] = inputData[sp] + 1; + encodeInfo.append(Integer.toString(target[tp] - 1)).append(" "); + } + tp++; + binary[binary_length] = ' '; + binary_length++; + } + sp++; + } + } + + } + + /* step (c) C40 encodation */ + if (current_mode == dm_mode.DM_C40) { + int shift_set, value; + + next_mode = dm_mode.DM_C40; + if (process_p == 0) { + next_mode = lookAheadTest(sp, current_mode); + } + + if (next_mode != dm_mode.DM_C40) { + target[tp] = 254; + tp++; + binary[binary_length] = ' '; + binary_length++; /* Unlatch */ + + next_mode = dm_mode.DM_ASCII; + encodeInfo.append("ASC "); + } else { + if (inputData[sp] > 127) { + process_buffer[process_p] = 1; + process_p++; + process_buffer[process_p] = 30; + process_p++; /* Upper Shift */ + + shift_set = c40_shift[inputData[sp] - 128]; + value = c40_value[inputData[sp] - 128]; + } else { + shift_set = c40_shift[inputData[sp]]; + value = c40_value[inputData[sp]]; + } + + if ((inputDataType == DataType.GS1) && (inputData[sp] == '[')) { + shift_set = 2; + value = 27; /* FNC1 */ + + } + + if (shift_set != 0) { + process_buffer[process_p] = shift_set - 1; + process_p++; + } + process_buffer[process_p] = value; + process_p++; + + if (process_p >= 3) { + int iv; + + iv = (1600 * process_buffer[0]) + (40 * process_buffer[1]) + + (process_buffer[2]) + 1; + target[tp] = iv / 256; + tp++; + target[tp] = iv % 256; + tp++; + binary[binary_length] = ' '; + binary_length++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("(").append(Integer.toString(process_buffer[0])) + .append(" ").append(Integer.toString(process_buffer[1])) + .append(" ").append(Integer.toString(process_buffer[2])) + .append(") "); + + process_buffer[0] = process_buffer[3]; + process_buffer[1] = process_buffer[4]; + process_buffer[2] = process_buffer[5]; + process_buffer[3] = 0; + process_buffer[4] = 0; + process_buffer[5] = 0; + process_p -= 3; + } + sp++; + } + } + + /* step (d) Text encodation */ + if (current_mode == dm_mode.DM_TEXT) { + int shift_set, value; + + next_mode = dm_mode.DM_TEXT; + if (process_p == 0) { + next_mode = lookAheadTest(sp, current_mode); + } + + if (next_mode != dm_mode.DM_TEXT) { + target[tp] = 254; + tp++; + binary[binary_length] = ' '; + binary_length++; /* Unlatch */ + + next_mode = dm_mode.DM_ASCII; + encodeInfo.append("ASC "); + } else { + if (inputData[sp] > 127) { + process_buffer[process_p] = 1; + process_p++; + process_buffer[process_p] = 30; + process_p++; /* Upper Shift */ + + shift_set = text_shift[inputData[sp] - 128]; + value = text_value[inputData[sp] - 128]; + } else { + shift_set = text_shift[inputData[sp]]; + value = text_value[inputData[sp]]; + } + + if ((inputDataType == DataType.GS1) && (inputData[sp] == '[')) { + shift_set = 2; + value = 27; /* FNC1 */ + + } + + if (shift_set != 0) { + process_buffer[process_p] = shift_set - 1; + process_p++; + } + process_buffer[process_p] = value; + process_p++; + + if (process_p >= 3) { + int iv; + + iv = (1600 * process_buffer[0]) + (40 * process_buffer[1]) + + (process_buffer[2]) + 1; + target[tp] = iv / 256; + tp++; + target[tp] = iv % 256; + tp++; + binary[binary_length] = ' '; + binary_length++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("(").append(Integer.toString(process_buffer[0])).append(" ") + .append(Integer.toString(process_buffer[1])).append(" ") + .append(Integer.toString(process_buffer[2])).append(") "); + + process_buffer[0] = process_buffer[3]; + process_buffer[1] = process_buffer[4]; + process_buffer[2] = process_buffer[5]; + process_buffer[3] = 0; + process_buffer[4] = 0; + process_buffer[5] = 0; + process_p -= 3; + } + sp++; + } + } + + /* step (e) X12 encodation */ + if (current_mode == dm_mode.DM_X12) { + int value = 0; + + next_mode = dm_mode.DM_X12; + if (process_p == 0) { + next_mode = lookAheadTest(sp, current_mode); + } + + if (next_mode != dm_mode.DM_X12) { + target[tp] = 254; + tp++; + binary[binary_length] = ' '; + binary_length++; /* Unlatch */ + + next_mode = dm_mode.DM_ASCII; + encodeInfo.append("ASC "); + } else { + if (inputData[sp] == 13) { + value = 0; + } + if (inputData[sp] == '*') { + value = 1; + } + if (inputData[sp] == '>') { + value = 2; + } + if (inputData[sp] == ' ') { + value = 3; + } + if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) { + value = (inputData[sp] - '0') + 4; + } + if ((inputData[sp] >= 'A') && (inputData[sp] <= 'Z')) { + value = (inputData[sp] - 'A') + 14; + } + + process_buffer[process_p] = value; + process_p++; + + if (process_p >= 3) { + int iv; + + iv = (1600 * process_buffer[0]) + (40 * process_buffer[1]) + + (process_buffer[2]) + 1; + target[tp] = iv / 256; + tp++; + target[tp] = iv % 256; + tp++; + binary[binary_length] = ' '; + binary_length++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("(").append(Integer.toString(process_buffer[0])).append(" ") + .append(Integer.toString(process_buffer[1])).append(" ") + .append(Integer.toString(process_buffer[2])).append(") "); + + process_buffer[0] = process_buffer[3]; + process_buffer[1] = process_buffer[4]; + process_buffer[2] = process_buffer[5]; + process_buffer[3] = 0; + process_buffer[4] = 0; + process_buffer[5] = 0; + process_p -= 3; + } + sp++; + } + } + + /* step (f) EDIFACT encodation */ + if (current_mode == dm_mode.DM_EDIFACT) { + int value = 0; + + next_mode = dm_mode.DM_EDIFACT; + if (process_p == 3) { + next_mode = lookAheadTest(sp, current_mode); + } + + if (next_mode != dm_mode.DM_EDIFACT) { + process_buffer[process_p] = 31; + process_p++; + next_mode = dm_mode.DM_ASCII; + } else { + if ((inputData[sp] >= '@') && (inputData[sp] <= '^')) { + value = inputData[sp] - '@'; + } + if ((inputData[sp] >= ' ') && (inputData[sp] <= '?')) { + value = inputData[sp]; + } + + process_buffer[process_p] = value; + process_p++; + sp++; + } + + if (process_p >= 4) { + target[tp] = (process_buffer[0] << 2) + + ((process_buffer[1] & 0x30) >> 4); + tp++; + target[tp] = ((process_buffer[1] & 0x0f) << 4) + + ((process_buffer[2] & 0x3c) >> 2); + tp++; + target[tp] = ((process_buffer[2] & 0x03) << 6) + + process_buffer[3]; + tp++; + binary[binary_length] = ' '; + binary_length++; + binary[binary_length] = ' '; + binary_length++; + binary[binary_length] = ' '; + binary_length++; + encodeInfo.append("(").append(Integer.toString(process_buffer[0])).append(" ") + .append(Integer.toString(process_buffer[1])).append(" ") + .append(Integer.toString(process_buffer[2])).append(") "); + + process_buffer[0] = process_buffer[4]; + process_buffer[1] = process_buffer[5]; + process_buffer[2] = process_buffer[6]; + process_buffer[3] = process_buffer[7]; + process_buffer[4] = 0; + process_buffer[5] = 0; + process_buffer[6] = 0; + process_buffer[7] = 0; + process_p -= 4; + } + } + + /* step (g) Base 256 encodation */ + if (current_mode == dm_mode.DM_BASE256) { + next_mode = lookAheadTest(sp, current_mode); + + if (next_mode == dm_mode.DM_BASE256) { + target[tp] = inputData[sp]; + encodeInfo.append(Integer.toString(target[tp])).append(" "); + tp++; + sp++; + binary[binary_length] = 'b'; + binary_length++; + } else { + next_mode = dm_mode.DM_ASCII; + encodeInfo.append("ASC "); + } + } + + if (tp > 1558) { + return 0; + } + + } /* while */ + + /* Add length and randomising algorithm to b256 */ + i = 0; + while (i < tp) { + if (binary[i] == 'b') { + if ((i == 0) || ((i != 0) && (binary[i - 1] != 'b'))) { + /* start of binary data */ + int binary_count; /* length of b256 data */ + + for (binary_count = 0; binary[binary_count + i] == 'b'; + binary_count++) + ; + + if (binary_count <= 249) { + insertAt(i, 'b'); + insertValueAt(i, tp, (char) binary_count); + tp++; + } else { + insertAt(i, 'b'); + insertAt(i + 1, 'b'); + insertValueAt(i, tp, (char) ((binary_count / 250) + 249)); + tp++; + insertValueAt(i + 1, tp, (char) (binary_count % 250)); + tp++; + } + } + } + i++; + } + + for (i = 0; i < tp; i++) { + if (binary[i] == 'b') { + int prn, temp; + + prn = ((149 * (i + 1)) % 255) + 1; + temp = target[i] + prn; + if (temp <= 255) { + target[i] = temp; + } else { + target[i] = temp - 256; + } + } + } + + encodeInfo.append("\n"); + + encodeInfo.append("Codewords: "); + for (i = 0; i < tp; i++) { + encodeInfo.append(Integer.toString(target[i])).append(" "); + } + encodeInfo.append("\n"); + last_mode = current_mode; + return tp; + } + + private int encodeRemainder(int symbols_left, int target_length) { + + int inputlen = content.length(); + + switch (last_mode) { + case DM_C40: + case DM_TEXT: + if (symbols_left == process_p) { // No unlatch required! + + if (process_p == 1) { // 1 data character left to encode. + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + + if (process_p == 2) { // 2 data characters left to encode. + + // Pad with shift 1 value (0) and encode as double. + int intValue = (1600 * process_buffer[0]) + (40 * process_buffer[1]) + 1; // ie (0 + 1). + target[target_length] = intValue / 256; + target_length++; + target[target_length] = intValue % 256; + target_length++; + } + } + + if (symbols_left > process_p) { + target[target_length] = (254); + target_length++; // Unlatch and encode remaining data in ascii. + if (process_p == 1 || (process_p == 2 && process_buffer[0] < 3)) { // Check for a shift value. + + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } else if (process_p == 2) { + target[target_length] = inputData[inputlen - 2] + 1; + target_length++; + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + } + break; + + case DM_X12: + if (symbols_left == process_p) { // Unlatch not required! + + if (process_p == 1) { // 1 data character left to encode. + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + + if (process_p == 2) { + // Encode last 2 bytes as ascii. + target[target_length] = inputData[inputlen - 2] + 1; + target_length++; + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + } + + if (symbols_left > process_p) { // Unlatch and encode remaining data in ascii. + + target[target_length] = (254); + target_length++; // Unlatch. + + if (process_p == 1) { + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + + if (process_p == 2) { + target[target_length] = inputData[inputlen - 2] + 1; + target_length++; + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + } + break; + + case DM_EDIFACT: + if (symbols_left == process_p) // Unlatch not required! + { + if (process_p == 1) { + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + + if (process_p == 2) { + target[target_length] = inputData[inputlen - 2] + 1; + target_length++; + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + + if (process_p == 3) { // Append edifact unlatch value (31) and encode as triple. + target[target_length] = (process_buffer[0] << 2) + + ((process_buffer[1] & 0x30) >> 4); + target_length++; + target[target_length] = ((process_buffer[1] & 0x0f) << 4) + + ((process_buffer[2] & 0x3c) >> 2); + target_length++; + target[target_length] = ((process_buffer[2] & 0x03) << 6) + + 31; + target_length++; + } + } + + if (symbols_left > process_p) // Unlatch and encode remaining data in ascii. + { + // Edifact unlatch. + if (symbols_left < 3) { + target[target_length] = 31; + target_length++; + } else { + target[target_length] = (31 << 2); + target_length++; + } + + if (process_p == 1) { + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + + if (process_p == 2) { + target[target_length] = inputData[inputlen - 2] + 1; + target_length++; + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + + if (process_p == 3) { + target[target_length] = inputData[inputlen - 3] + 1; + target_length++; + target[target_length] = inputData[inputlen - 2] + 1; + target_length++; + target[target_length] = inputData[inputlen - 1] + 1; + target_length++; + } + } + break; + } + + return target_length; + } + + private boolean isTwoDigits(int pos) { + if (Character.isDigit((char) inputData[pos])) { + if (pos + 1 >= content.length()) { + return false; + } + if (Character.isDigit((char) inputData[pos + 1])) { + return true; + } + return false; + } + return false; + } + + private dm_mode lookAheadTest(int position, dm_mode current_mode) { + /* 'look ahead test' from Annex P */ + + double ascii_count, c40_count, text_count, x12_count, edf_count, b256_count, best_count; + int sp; + int sourcelen = content.length(); + dm_mode best_scheme = dm_mode.NULL; + + /* step (j) */ + if (current_mode == dm_mode.DM_ASCII) { + ascii_count = 0.0; + c40_count = 1.0; + text_count = 1.0; + x12_count = 1.0; + edf_count = 1.0; + b256_count = 1.25; + } else { + ascii_count = 1.0; + c40_count = 2.0; + text_count = 2.0; + x12_count = 2.0; + edf_count = 2.0; + b256_count = 2.25; + } + + switch (current_mode) { + case DM_C40: // (j)(2) + c40_count = 0.0; + break; + case DM_TEXT: // (j)(3) + text_count = 0.0; + break; + case DM_X12: // (j)(4) + x12_count = 0.0; + break; + case DM_EDIFACT: // (j)(5) + edf_count = 0.0; + break; + case DM_BASE256: // (j)(6) + b256_count = 0.0; + break; + } + + sp = position; + + do { + if (sp == (sourcelen - 1)) { + /* At the end of data ... step (k) */ + ascii_count = Math.ceil(ascii_count); + b256_count = Math.ceil(b256_count); + edf_count = Math.ceil(edf_count); + text_count = Math.ceil(text_count); + x12_count = Math.ceil(x12_count); + c40_count = Math.ceil(c40_count); + + best_count = c40_count; + best_scheme = dm_mode.DM_C40; // (k)(7) + + if (x12_count < best_count) { + best_count = x12_count; + best_scheme = dm_mode.DM_X12; // (k)(6) + } + + if (text_count < best_count) { + best_count = text_count; + best_scheme = dm_mode.DM_TEXT; // (k)(5) + } + + if (edf_count < best_count) { + best_count = edf_count; + best_scheme = dm_mode.DM_EDIFACT; // (k)(4) + } + + if (b256_count < best_count) { + best_count = b256_count; + best_scheme = dm_mode.DM_BASE256; // (k)(3) + } + + if (ascii_count <= best_count) { + best_scheme = dm_mode.DM_ASCII; // (k)(2) + } + } else { + + /* ascii ... step (l) */ + if ((inputData[sp] >= '0') && (inputData[sp] <= '9')) { + ascii_count += 0.5; // (l)(1) + } else { + if (inputData[sp] > 127) { + ascii_count = Math.ceil(ascii_count) + 2.0; // (l)(2) + } else { + ascii_count = Math.ceil(ascii_count) + 1.0; // (l)(3) + } + } + + /* c40 ... step (m) */ + if ((inputData[sp] == ' ') || + (((inputData[sp] >= '0') && (inputData[sp] <= '9')) || + ((inputData[sp] >= 'A') && (inputData[sp] <= 'Z')))) { + c40_count += (2.0 / 3.0); // (m)(1) + } else { + if (inputData[sp] > 127) { + c40_count += (8.0 / 3.0); // (m)(2) + } else { + c40_count += (4.0 / 3.0); // (m)(3) + } + } + + /* text ... step (n) */ + if ((inputData[sp] == ' ') || + (((inputData[sp] >= '0') && (inputData[sp] <= '9')) || + ((inputData[sp] >= 'a') && (inputData[sp] <= 'z')))) { + text_count += (2.0 / 3.0); // (n)(1) + } else { + if (inputData[sp] > 127) { + text_count += (8.0 / 3.0); // (n)(2) + } else { + text_count += (4.0 / 3.0); // (n)(3) + } + } + + /* x12 ... step (o) */ + if (isX12(inputData[sp])) { + x12_count += (2.0 / 3.0); // (o)(1) + } else { + if (inputData[sp] > 127) { + x12_count += (13.0 / 3.0); // (o)(2) + } else { + x12_count += (10.0 / 3.0); // (o)(3) + } + } + + /* edifact ... step (p) */ + if ((inputData[sp] >= ' ') && (inputData[sp] <= '^')) { + edf_count += (3.0 / 4.0); // (p)(1) + } else { + if (inputData[sp] > 127) { + edf_count += (17.0 / 4.0); // (p)(2) + } else { + edf_count += (13.0 / 4.0); // (p)(3) + } + } + if ((inputDataType == DataType.GS1) && (inputData[sp] == '[')) { + edf_count += 6.0; + } + + /* base 256 ... step (q) */ + if ((inputDataType == DataType.GS1) && (inputData[sp] == '[')) { + b256_count += 4.0; // (q)(1) + } else { + b256_count += 1.0; // (q)(2) + } + } + + + if (sp > (position + 3)) { + /* 4 data characters processed ... step (r) */ + + /* step (r)(6) */ + if (((c40_count + 1.0) < ascii_count) && + ((c40_count + 1.0) < b256_count) && + ((c40_count + 1.0) < edf_count) && + ((c40_count + 1.0) < text_count)) { + + if (c40_count < x12_count) { + best_scheme = dm_mode.DM_C40; + } + + if (c40_count == x12_count) { + if (p_r_6_2_1(sp, sourcelen)) { + // Test (r)(6)(ii)(i) + best_scheme = dm_mode.DM_X12; + } else { + best_scheme = dm_mode.DM_C40; + } + } + } + + /* step (r)(5) */ + if (((x12_count + 1.0) < ascii_count) && + ((x12_count + 1.0) < b256_count) && + ((x12_count + 1.0) < edf_count) && + ((x12_count + 1.0) < text_count) && + ((x12_count + 1.0) < c40_count)) { + best_scheme = dm_mode.DM_X12; + } + + /* step (r)(4) */ + if (((text_count + 1.0) < ascii_count) && + ((text_count + 1.0) < b256_count) && + ((text_count + 1.0) < edf_count) && + ((text_count + 1.0) < x12_count) && + ((text_count + 1.0) < c40_count)) { + best_scheme = dm_mode.DM_TEXT; + } + + /* step (r)(3) */ + if (((edf_count + 1.0) < ascii_count) && + ((edf_count + 1.0) < b256_count) && + ((edf_count + 1.0) < text_count) && + ((edf_count + 1.0) < x12_count) && + ((edf_count + 1.0) < c40_count)) { + best_scheme = dm_mode.DM_EDIFACT; + } + + /* step (r)(2) */ + if (((b256_count + 1.0) <= ascii_count) || + (((b256_count + 1.0) < edf_count) && + ((b256_count + 1.0) < text_count) && + ((b256_count + 1.0) < x12_count) && + ((b256_count + 1.0) < c40_count))) { + best_scheme = dm_mode.DM_BASE256; + } + + /* step (r)(1) */ + if (((ascii_count + 1.0) <= b256_count) && + ((ascii_count + 1.0) <= edf_count) && + ((ascii_count + 1.0) <= text_count) && + ((ascii_count + 1.0) <= x12_count) && + ((ascii_count + 1.0) <= c40_count)) { + best_scheme = dm_mode.DM_ASCII; + } + } + + sp++; + } while (best_scheme == dm_mode.NULL); // step (s) + + return best_scheme; + } + + private boolean p_r_6_2_1(int position, int sourcelen) { + /* Annex P section (r)(6)(ii)(I) + "If one of the three X12 terminator/separator characters first + occurs in the yet to be processed data before a non-X12 character..." + */ + + int i; + int nonX12Position = 0; + int specialX12Position = 0; + boolean retval = false; + + for (i = position; i < sourcelen; i++) { + if (nonX12Position == 0 && !isX12(i)) { + nonX12Position = i; + } + + if (specialX12Position == 0) { + if ((inputData[i] == (char) 13) || + (inputData[i] == '*') || + (inputData[i] == '>')) { + specialX12Position = i; + } + } + } + + if ((nonX12Position != 0) && (specialX12Position != 0)) { + if (specialX12Position < nonX12Position) { + retval = true; + } + } + + return retval; + } + + private boolean isX12(int source) { + if (source == 13) { + return true; + } + if (source == 42) { + return true; + } + if (source == 62) { + return true; + } + if (source == 32) { + return true; + } + if ((source >= '0') && (source <= '9')) { + return true; + } + if ((source >= 'A') && (source <= 'Z')) { + return true; + } + + return false; + } + + private void calculateErrorCorrection(int bytes, int datablock, int rsblock, int skew) { + // calculate and append ecc code, and if necessary interleave + int blocks = (bytes + 2) / datablock, b; + int n, p; + ReedSolomon rs = new ReedSolomon(); + + rs.init_gf(0x12d); + rs.init_code(rsblock, 1); + + for (b = 0; b < blocks; b++) { + int[] buf = new int[256]; + int[] ecc = new int[256]; + + p = 0; + for (n = b; n < bytes; n += blocks) { + buf[p++] = target[n]; + } + rs.encode(p, buf); + for (n = 0; n < rsblock; n++) { + ecc[n] = rs.getResult(n); + } + p = rsblock - 1; // comes back reversed + for (n = b; n < rsblock * blocks; n += blocks) { + if (skew == 1) { + /* Rotate ecc data to make 144x144 size symbols acceptable */ + /* See http://groups.google.com/group/postscriptbarcode/msg/5ae8fda7757477da */ + if (b < 8) { + target[bytes + n + 2] = ecc[p--]; + } else { + target[bytes + n - 8] = ecc[p--]; + } + } else { + target[bytes + n] = ecc[p--]; + } + } + } + } + + private void insertAt(int pos, char newbit) { + /* Insert a character into the middle of a string at position posn */ + int i; + + for (i = binary_length; i > pos; i--) { + binary[i] = binary[i - 1]; + } + binary[pos] = newbit; + } + + private void insertValueAt(int posn, int streamlen, char newbit) { + int i; + + for (i = streamlen; i > posn; i--) { + target[i] = target[i - 1]; + } + target[posn] = newbit; + } + + private void addPadBits(int tp, int tail_length) { + /* adds unlatch and pad bits */ + int i, prn, temp; + + switch (last_mode) { + case DM_C40: + case DM_TEXT: + case DM_X12: + target[tp] = 254; + tp++; /* Unlatch */ + + tail_length--; + } + + for (i = tail_length; i > 0; i--) { + if (i == tail_length) { + target[tp] = 129; + tp++; /* Pad */ + + } else { + prn = ((149 * (tp + 1)) % 253) + 1; + temp = 129 + prn; + if (temp <= 254) { + target[tp] = temp; + tp++; + } else { + target[tp] = temp - 254; + tp++; + } + } + } + } + + private void placeData(int NR, int NC) { + int r, c, p; + // invalidate + for (r = 0; r < NR; r++) { + for (c = 0; c < NC; c++) { + places[r * NC + c] = 0; + } + } + // start + p = 1; + r = 4; + c = 0; + do { + // check corner + if (r == NR && (c == 0)) { + placeCornerA(NR, NC, p++); + } + if (r == NR - 2 && (c == 0) && ((NC % 4) != 0)) { + placeCornerB(NR, NC, p++); + } + if (r == NR - 2 && (c == 0) && (NC % 8) == 4) { + placeCornerC(NR, NC, p++); + } + if (r == NR + 4 && c == 2 && ((NC % 8) == 0)) { + placeCornerD(NR, NC, p++); + } + // up/right + do { + if (r < NR && c >= 0 && (places[r * NC + c] == 0)) { + placeBlock(NR, NC, r, c, p++); + } + r -= 2; + c += 2; + } while (r >= 0 && c < NC); + r++; + c += 3; + // down/left + do { + if (r >= 0 && c < NC && (places[r * NC + c] == 0)) { + placeBlock(NR, NC, r, c, p++); + } + r += 2; + c -= 2; + } while (r < NR && c >= 0); + r += 3; + c++; + } while (r < NR || c < NC); + // unfilled corner + if (places[NR * NC - 1] == 0) { + places[NR * NC - 1] = places[NR * NC - NC - 2] = 1; + } + } + + private void placeCornerA(int NR, int NC, int p) { + placeBit(NR, NC, NR - 1, 0, p, 7); + placeBit(NR, NC, NR - 1, 1, p, 6); + placeBit(NR, NC, NR - 1, 2, p, 5); + placeBit(NR, NC, 0, NC - 2, p, 4); + placeBit(NR, NC, 0, NC - 1, p, 3); + placeBit(NR, NC, 1, NC - 1, p, 2); + placeBit(NR, NC, 2, NC - 1, p, 1); + placeBit(NR, NC, 3, NC - 1, p, 0); + } + + private void placeCornerB(int NR, int NC, int p) { + placeBit(NR, NC, NR - 3, 0, p, 7); + placeBit(NR, NC, NR - 2, 0, p, 6); + placeBit(NR, NC, NR - 1, 0, p, 5); + placeBit(NR, NC, 0, NC - 4, p, 4); + placeBit(NR, NC, 0, NC - 3, p, 3); + placeBit(NR, NC, 0, NC - 2, p, 2); + placeBit(NR, NC, 0, NC - 1, p, 1); + placeBit(NR, NC, 1, NC - 1, p, 0); + } + + private void placeCornerC(int NR, int NC, int p) { + placeBit(NR, NC, NR - 3, 0, p, 7); + placeBit(NR, NC, NR - 2, 0, p, 6); + placeBit(NR, NC, NR - 1, 0, p, 5); + placeBit(NR, NC, 0, NC - 2, p, 4); + placeBit(NR, NC, 0, NC - 1, p, 3); + placeBit(NR, NC, 1, NC - 1, p, 2); + placeBit(NR, NC, 2, NC - 1, p, 1); + placeBit(NR, NC, 3, NC - 1, p, 0); + } + + private void placeCornerD(int NR, int NC, int p) { + placeBit(NR, NC, NR - 1, 0, p, 7); + placeBit(NR, NC, NR - 1, NC - 1, p, 6); + placeBit(NR, NC, 0, NC - 3, p, 5); + placeBit(NR, NC, 0, NC - 2, p, 4); + placeBit(NR, NC, 0, NC - 1, p, 3); + placeBit(NR, NC, 1, NC - 3, p, 2); + placeBit(NR, NC, 1, NC - 2, p, 1); + placeBit(NR, NC, 1, NC - 1, p, 0); + } + + private void placeBlock(int NR, int NC, int r, int c, int p) { + placeBit(NR, NC, r - 2, c - 2, p, 7); + placeBit(NR, NC, r - 2, c - 1, p, 6); + placeBit(NR, NC, r - 1, c - 2, p, 5); + placeBit(NR, NC, r - 1, c - 1, p, 4); + placeBit(NR, NC, r - 1, c, p, 3); + placeBit(NR, NC, r, c - 2, p, 2); + placeBit(NR, NC, r, c - 1, p, 1); + placeBit(NR, NC, r, c, p, 0); + } + + private void placeBit(int NR, int NC, int r, int c, int p, int b) { + if (r < 0) { + r += NR; + c += 4 - ((NR + 4) % 8); + } + if (c < 0) { + c += NC; + r += 4 - ((NC + 4) % 8); + } + places[r * NC + c] = (p << 3) + b; + } + + private enum dm_mode { + + NULL, DM_ASCII, DM_C40, DM_TEXT, DM_X12, DM_EDIFACT, DM_BASE256 + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Ean.java b/barcode/src/main/java/org/xbib/graphics/barcode/Ean.java new file mode 100755 index 0000000..d378a06 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Ean.java @@ -0,0 +1,366 @@ +package org.xbib.graphics.barcode; + +import static org.xbib.graphics.barcode.HumanReadableLocation.NONE; +import static org.xbib.graphics.barcode.HumanReadableLocation.TOP; +import org.xbib.graphics.barcode.util.AddOn; +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Rectangle2D; + +/** + * Implements EAN bar code symbology according to BS EN 797:1996. + * European Article Number data can be encoded in EAN-8 or EAN-13 format + * requiring a 7-digit or 12-digit input respectively. EAN-13 numbers map to + * Global Trade Identification Numbers (GTIN) whereas EAN-8 symbols are + * generally for internal use only. Check digit is calculated and should not + * be in input data. Leading zeroes are added as required. + */ +public class Ean extends Symbol { + + private boolean useAddOn; + + private String addOnContent; + private Mode mode; + private boolean linkageFlag; + private String[] EAN13Parity = { + "AAAAAA", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA", + "ABABAB", "ABABBA", "ABBABA" + }; + private String[] EANsetA = { + "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213", + "3112" + }; + private String[] EANsetB = { + "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121", + "2113" + }; + + public Ean() { + mode = Mode.EAN13; + useAddOn = false; + addOnContent = ""; + linkageFlag = false; + } + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + protected void setLinkageFlag() { + linkageFlag = true; + } + + @Override + public void setHumanReadableLocation(HumanReadableLocation humanReadableLocation) { + if (humanReadableLocation == TOP) { + throw new IllegalArgumentException("Cannot display human-readable text above EAN bar codes."); + } else { + super.setHumanReadableLocation(humanReadableLocation); + } + } + + @Override + public boolean encode() { + boolean retval = false; + AddOn addOn = new AddOn(); + String addOnData; + + separateContent(); + + if (content.length() == 0) { + errorMsg.append("Missing EAN data"); + retval = false; + } else { + switch (mode) { + case EAN8: + retval = ean8(); + break; + case EAN13: + retval = ean13(); + break; + } + } + + if ((retval) && (useAddOn)) { + addOnData = addOn.calcAddOn(addOnContent); + if (addOnData.length() == 0) { + errorMsg.append("Invalid Add-On data"); + retval = false; + } else { + pattern[0] = pattern[0] + "9" + addOnData; + + //add leading zeroes to add-on text + if (addOnContent.length() == 1) { + addOnContent = "0" + addOnContent; + } + if (addOnContent.length() == 3) { + addOnContent = "0" + addOnContent; + } + if (addOnContent.length() == 4) { + addOnContent = "0" + addOnContent; + } + } + } + + if (retval) { + plotSymbol(); + } + + return retval; + } + + private void separateContent() { + int splitPoint; + + splitPoint = content.indexOf('+'); + if (splitPoint != -1) { + // There is a '+' in the input data, use an add-on EAN2 or EAN5 + useAddOn = true; + addOnContent = content.substring(splitPoint + 1); + content = content.substring(0, splitPoint); + } + } + + private boolean ean13() { + StringBuilder accumulator = new StringBuilder(); + StringBuilder dest; + String parity; + int i; + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (content.length() > 12) { + errorMsg.append("Input data too long"); + return false; + } + + for (i = content.length(); i < 12; i++) { + accumulator.append("0"); + } + accumulator.append(content); + + accumulator.append(calcDigit(accumulator.toString())); + + parity = EAN13Parity[accumulator.charAt(0) - '0']; + + encodeInfo.append("Parity Digit: ").append(accumulator.charAt(0)).append("\n"); + + /* Start character */ + dest = new StringBuilder("111"); + + for (i = 1; i < 13; i++) { + if (i == 7) { + dest.append("11111"); + } + + if ((i >= 1) && (i <= 6)) { + if (parity.charAt(i - 1) == 'B') { + dest.append(EANsetB[accumulator.charAt(i) - '0']); + } else { + dest.append(EANsetA[accumulator.charAt(i) - '0']); + } + } else { + dest.append(EANsetA[accumulator.charAt(i) - '0']); + } + } + + dest.append("111"); + + readable = new StringBuilder(accumulator.toString()); + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + return true; + } + + private boolean ean8() { + StringBuilder accumulator = new StringBuilder(); + int i; + StringBuilder dest; + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (content.length() > 7) { + errorMsg.append("Input data too long"); + return false; + } + + for (i = content.length(); i < 7; i++) { + accumulator.append("0"); + } + accumulator.append(content); + + accumulator.append(calcDigit(accumulator.toString())); + + dest = new StringBuilder("111"); + for (i = 0; i < 8; i++) { + if (i == 4) { + dest.append("11111"); + } + dest.append(EANsetA[Character.getNumericValue(accumulator.charAt(i))]); + } + dest.append("111"); + + readable = new StringBuilder(accumulator.toString()); + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + return true; + } + + private char calcDigit(String x) { + int count = 0; + int c, cdigit; + int p = 0; + for (int i = x.length() - 1; i >= 0; i--) { + c = Character.getNumericValue(x.charAt(i)); + if ((p % 2) == 0) { + c = c * 3; + } + count += c; + p++; + } + cdigit = 10 - (count % 10); + if (cdigit == 10) { + cdigit = 0; + } + + encodeInfo.append("Check Digit: ").append(cdigit).append("\n"); + + return (char) (cdigit + '0'); + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + boolean black; + int compositeOffset = 0; + int shortLongDiff = 5; + getRectangles().clear(); + getTexts().clear(); + black = true; + x = 0; + if (linkageFlag) { + compositeOffset = 6; + } + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + if (black) { + y = 0; + black = false; + w = pattern[0].charAt(xBlock) - '0'; + h = defaultHeight; + /* Add extension to guide bars */ + if (mode == Mode.EAN13) { + if ((x < 3) || (x > 91)) { + h += shortLongDiff; + } + if ((x > 45) && (x < 49)) { + h += shortLongDiff; + } + if (x > 95) { + // Drop add-on + h -= 8; + y = 8; + } + if (linkageFlag) { + if ((x == 0) || (x == 94)) { + h += 2; + y -= 2; + } + } + } + if (mode == Mode.EAN8) { + if ((x < 3) || (x > 62)) { + h += shortLongDiff; + } + if ((x > 30) && (x < 35)) { + h += shortLongDiff; + } + if (x > 66) { + // Drop add-on + h -= 8; + y = 8; + } + if (linkageFlag) { + if ((x == 0) || (x == 66)) { + h += 2; + y -= 2; + } + } + } + Rectangle2D.Double rect = new Rectangle2D.Double(x + 6, y + compositeOffset, w, h); + getRectangles().add(rect); + if ((x + w + 12) > symbolWidth) { + symbolWidth = x + w + 12; + } + } else { + black = true; + } + x += (double) (pattern[0].charAt(xBlock) - '0'); + + } + + if (linkageFlag) { + // Add separator for composite symbology + if (mode == Mode.EAN13) { + getRectangles().add(new Rectangle2D.Double(6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(94 + 6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(-1 + 6, 2, 1, 2)); + getRectangles().add(new Rectangle2D.Double(95 + 6, 2, 1, 2)); + } else { + getRectangles().add(new Rectangle2D.Double(6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(66 + 6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(-1 + 6, 2, 1, 2)); + getRectangles().add(new Rectangle2D.Double(67 + 6, 2, 1, 2)); + } + } + + symbolHeight = defaultHeight + 5; + + /* Now add the text */ + if (getHumanReadableLocation() != NONE) { + double baseline = getHeight() + fontSize - shortLongDiff + compositeOffset; + double addOnBaseline = 6.0 + compositeOffset; + if (mode == Mode.EAN13) { + getTexts().add(new TextBox(3, baseline, readable.substring(0, 1))); + getTexts().add(new TextBox(30, baseline, readable.substring(1, 7))); + getTexts().add(new TextBox(77, baseline, readable.substring(7, 13))); + if (useAddOn) { + if (addOnContent.length() == 2) { + getTexts().add(new TextBox(118, addOnBaseline, addOnContent)); + } else { + getTexts().add(new TextBox(133, addOnBaseline, addOnContent)); + } + } + } else { // EAN8 + getTexts().add(new TextBox(23, baseline, readable.substring(0, 4))); + getTexts().add(new TextBox(55, baseline, readable.substring(4, 8))); + if (useAddOn) { + if (addOnContent.length() == 2) { + getTexts().add(new TextBox(93, addOnBaseline, addOnContent)); + } else { + getTexts().add(new TextBox(105, addOnBaseline, addOnContent)); + } + } + } + } + } + + public enum Mode { + EAN8, EAN13 + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/GridMatrix.java b/barcode/src/main/java/org/xbib/graphics/barcode/GridMatrix.java new file mode 100755 index 0000000..59835e0 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/GridMatrix.java @@ -0,0 +1,2015 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; +import java.io.UnsupportedEncodingException; + +/** + * Implements Grid Matrix bar code symbology according to AIMD014. + * Grid Matrix is a matrix symbology which can encode characters in the ISO/IEC + * 8859-1 (Latin-1) character set as well as those in the GB-2312 character set. + * Input is assumed to be formatted as a UTF string. + */ +public class GridMatrix extends Symbol { + + private final char[] shift_set = { + /* From Table 7 - Encoding of control characters */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /* NULL -> SI */ + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* DLE -> US */ + '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', + ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~' + }; + + private final int[] gm_recommend_cw = { + 9, 30, 59, 114, 170, 237, 315, 405, 506, 618, 741, 875, 1021 + }; + private final int[] gm_max_cw = { + 11, 40, 79, 146, 218, 305, 405, 521, 650, 794, 953, 1125, 1313 + }; + + private final int[] gm_data_codewords = { + 0, 15, 13, 11, 9, + 45, 40, 35, 30, 25, + 89, 79, 69, 59, 49, + 146, 130, 114, 98, 81, + 218, 194, 170, 146, 121, + 305, 271, 237, 203, 169, + 405, 360, 315, 270, 225, + 521, 463, 405, 347, 289, + 650, 578, 506, 434, 361, + 794, 706, 618, 530, 441, + 953, 847, 741, 635, 529, + 1125, 1000, 875, 750, 625, + 1313, 1167, 1021, 875, 729 + }; + + private final int[] gm_n1 = { + 18, 50, 98, 81, 121, 113, 113, 116, 121, 126, 118, 125, 122 + }; + private final int[] gm_b1 = { + 1, 1, 1, 2, 2, 2, 2, 3, 2, 7, 5, 10, 6 + }; + private final int[] gm_b2 = { + 0, 0, 0, 0, 0, 1, 2, 2, 4, 0, 4, 0, 6 + }; + + private final int[] gm_ebeb = { + /* E1 B3 E2 B4 */ + 0, 0, 0, 0, // version 1 + 3, 1, 0, 0, + 5, 1, 0, 0, + 7, 1, 0, 0, + 9, 1, 0, 0, + 5, 1, 0, 0, // version 2 + 10, 1, 0, 0, + 15, 1, 0, 0, + 20, 1, 0, 0, + 25, 1, 0, 0, + 9, 1, 0, 0, // version 3 + 19, 1, 0, 0, + 29, 1, 0, 0, + 39, 1, 0, 0, + 49, 1, 0, 0, + 8, 2, 0, 0, // version 4 + 16, 2, 0, 0, + 24, 2, 0, 0, + 32, 2, 0, 0, + 41, 1, 10, 1, + 12, 2, 0, 0, // version 5 + 24, 2, 0, 0, + 36, 2, 0, 0, + 48, 2, 0, 0, + 61, 1, 60, 1, + 11, 3, 0, 0, // version 6 + 23, 1, 22, 2, + 34, 2, 33, 1, + 45, 3, 0, 0, + 57, 1, 56, 2, + 12, 1, 11, 3, // version 7 + 23, 2, 22, 2, + 34, 3, 33, 1, + 45, 4, 0, 0, + 57, 1, 56, 3, + 12, 2, 11, 3, // version 8 + 23, 5, 0, 0, + 35, 3, 34, 2, + 47, 1, 46, 4, + 58, 4, 57, 1, + 12, 6, 0, 0, // version 9 + 24, 6, 0, 0, + 36, 6, 0, 0, + 48, 6, 0, 0, + 61, 1, 60, 5, + 13, 4, 12, 3, // version 10 + 26, 1, 25, 6, + 38, 5, 37, 2, + 51, 2, 50, 5, + 63, 7, 0, 0, + 12, 6, 11, 3, // version 11 + 24, 4, 23, 5, + 36, 2, 35, 7, + 47, 9, 0, 0, + 59, 7, 58, 2, + 13, 5, 12, 5, // version 12 + 25, 10, 0, 0, + 38, 5, 37, 5, + 50, 10, 0, 0, + 63, 5, 62, 5, + 13, 1, 12, 11, //version 13 + 25, 3, 24, 9, + 37, 5, 36, 7, + 49, 7, 48, 5, + 61, 9, 60, 3 + }; + + private final int[] gm_macro_matrix = { + 728, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, + 727, 624, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 651, + 726, 623, 528, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 553, 652, + 725, 622, 527, 440, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 463, 554, 653, + 724, 621, 526, 439, 360, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 381, 464, 555, 654, + 723, 620, 525, 438, 359, 288, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 307, 382, 465, 556, 655, + 722, 619, 524, 437, 358, 287, 224, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 241, 308, 383, 466, 557, 656, + 721, 618, 523, 436, 357, 286, 223, 168, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 183, 242, 309, 384, 467, 558, 657, + 720, 617, 522, 435, 356, 285, 222, 167, 120, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 133, 184, 243, 310, 385, 468, 559, 658, + 719, 616, 521, 434, 355, 284, 221, 166, 119, 80, 49, 50, 51, 52, 53, 54, 55, 56, 91, 134, 185, 244, 311, 386, 469, 560, 659, + 718, 615, 520, 433, 354, 283, 220, 165, 118, 79, 48, 25, 26, 27, 28, 29, 30, 57, 92, 135, 186, 245, 312, 387, 470, 561, 660, + 717, 614, 519, 432, 353, 282, 219, 164, 117, 78, 47, 24, 9, 10, 11, 12, 31, 58, 93, 136, 187, 246, 313, 388, 471, 562, 661, + 716, 613, 518, 431, 352, 281, 218, 163, 116, 77, 46, 23, 8, 1, 2, 13, 32, 59, 94, 137, 188, 247, 314, 389, 472, 563, 662, + 715, 612, 517, 430, 351, 280, 217, 162, 115, 76, 45, 22, 7, 0, 3, 14, 33, 60, 95, 138, 189, 248, 315, 390, 473, 564, 663, + 714, 611, 516, 429, 350, 279, 216, 161, 114, 75, 44, 21, 6, 5, 4, 15, 34, 61, 96, 139, 190, 249, 316, 391, 474, 565, 664, + 713, 610, 515, 428, 349, 278, 215, 160, 113, 74, 43, 20, 19, 18, 17, 16, 35, 62, 97, 140, 191, 250, 317, 392, 475, 566, 665, + 712, 609, 514, 427, 348, 277, 214, 159, 112, 73, 42, 41, 40, 39, 38, 37, 36, 63, 98, 141, 192, 251, 318, 393, 476, 567, 666, + 711, 608, 513, 426, 347, 276, 213, 158, 111, 72, 71, 70, 69, 68, 67, 66, 65, 64, 99, 142, 193, 252, 319, 394, 477, 568, 667, + 710, 607, 512, 425, 346, 275, 212, 157, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 143, 194, 253, 320, 395, 478, 569, 668, + 709, 606, 511, 424, 345, 274, 211, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 146, 145, 144, 195, 254, 321, 396, 479, 570, 669, + 708, 605, 510, 423, 344, 273, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 255, 322, 397, 480, 571, 670, + 707, 604, 509, 422, 343, 272, 271, 270, 269, 268, 267, 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 323, 398, 481, 572, 671, + 706, 603, 508, 421, 342, 341, 340, 339, 338, 337, 336, 335, 334, 333, 332, 331, 330, 329, 328, 327, 326, 325, 324, 399, 482, 573, 672, + 705, 602, 507, 420, 419, 418, 417, 416, 415, 414, 413, 412, 411, 410, 409, 408, 407, 406, 405, 404, 403, 402, 401, 400, 483, 574, 673, + 704, 601, 506, 505, 504, 503, 502, 501, 500, 499, 498, 497, 496, 495, 494, 493, 492, 491, 490, 489, 488, 487, 486, 485, 484, 575, 674, + 703, 600, 599, 598, 597, 596, 595, 594, 593, 592, 591, 590, 589, 588, 587, 586, 585, 584, 583, 582, 581, 580, 579, 578, 577, 576, 675, + 702, 701, 700, 699, 698, 697, 696, 695, 694, 693, 692, 691, 690, 689, 688, 687, 686, 685, 684, 683, 682, 681, 680, 679, 678, 677, 676 + }; + + private final char[] MIXED_ALPHANUM_SET = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', ' ' + }; + private int[] inputIntArray; + + private StringBuilder binary = new StringBuilder("1"); + private int[] word = new int[1460]; + private boolean[] grid; + private gmMode appxDnextSection = gmMode.NULL; + private gmMode appxDlastSection = gmMode.NULL; + private int preferredVersion = 0; + private int preferredEccLevel = -1; + + /** + * Set preferred size, or "version" of the symbol according to the following + * table. This value may be ignored if the data to be encoded does not fit + * into a symbol of the selected size. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Available Grid Matrix symbol sizes

+ * Input

+ * Size

+ * 1

+ * 18 x 18

+ * 2

+ * 30 x 30

+ * 3

+ * 42 x 42

+ * 4

+ * 54 x 54

+ * 5

+ * 66 x 66

+ * 6

+ * 78 x 78

+ * 7

+ * 90x 90

+ * 8

+ * 102 x 102

+ * 9

+ * 114 x 114

+ * 10

+ * 126 x 126

+ * 11

+ * 138 x 138

+ * 12

+ * 150 x 150

+ * 13

+ * 162 x 162

+ * + * @param version Symbol version + */ + public void setPreferredVersion(int version) { + preferredVersion = version; + } + + /** + * Set the preferred amount of the symbol which should be dedicated to error + * correction data. Values should be selected from the following table: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Available options for error correction capacity

+ * Mode

+ * Error Correction Capacity

+ * 1

+ * Approximately 10%

+ * 2

+ * Approximately 20%

+ * 3

+ * Approximately 30%

+ * 4

+ * Approximately 40%

+ * 5

+ * Approximately 50%

+ * + * @param eccLevel Error correction mode + */ + public void setPreferredEccLevel(int eccLevel) { + preferredEccLevel = eccLevel; + } + + @Override + public boolean encode() { + int size, modules, dark, error_number; + int auto_layers, min_layers, layers, auto_ecc_level, min_ecc_level, ecc_level; + int x, y, i; + int data_cw, input_latch = 0; + int data_max; + int length; + StringBuilder bin; + int qmarksBefore, qmarksAfter; + + for (i = 0; i < 1460; i++) { + word[i] = 0; + } + + try { + /* Try converting to GB2312 */ + qmarksBefore = 0; + for (i = 0; i < content.length(); i++) { + if (content.charAt(i) == '?') { + qmarksBefore++; + } + } + inputBytes = content.getBytes("EUC_CN"); + qmarksAfter = 0; + for (i = 0; i < inputBytes.length; i++) { + if (inputBytes[i] == '?') { + qmarksAfter++; + } + } + if (qmarksBefore == qmarksAfter) { + /* GB2312 encoding worked, use chinese compaction */ + inputIntArray = new int[inputBytes.length]; + length = 0; + for (i = 0; i < inputBytes.length; i++) { + if (((inputBytes[i] & 0xFF) >= 0xA1) && ((inputBytes[i] & 0xFF) <= 0xF7)) { + /* Double byte character */ + inputIntArray[length] = ((inputBytes[i] & 0xFF) * 256) + (inputBytes[i + 1] & 0xFF); + i++; + length++; + } else { + /* Single byte character */ + inputIntArray[length] = inputBytes[i] & 0xFF; + length++; + } + } + encodeInfo.append("Using GB2312 character encoding\n"); + eciMode = 29; + } else { + /* GB2312 encoding didn't work, use other ECI mode */ + eciProcess(); // Get ECI mode + length = inputBytes.length; + inputIntArray = new int[length]; + for (i = 0; i < length; i++) { + inputIntArray[i] = inputBytes[i] & 0xFF; + } + } + } catch (UnsupportedEncodingException e) { + errorMsg.append("Byte conversion encoding error"); + return false; + } + + error_number = encodeGridMatrixBinary(length, readerInit); + if (error_number != 0) { + errorMsg.append("Input data too long"); + return false; + } + + /* Determine the size of the symbol */ + data_cw = binary.length() / 7; + + auto_layers = 1; + for (i = 0; i < 13; i++) { + if (gm_recommend_cw[i] < data_cw) { + auto_layers = i + 1; + } + } + + min_layers = 13; + for (i = 12; i > 0; i--) { + if (gm_max_cw[(i - 1)] >= data_cw) { + min_layers = i; + } + } + layers = auto_layers; + auto_ecc_level = 3; + if (layers == 1) { + auto_ecc_level = 5; + } + if ((layers == 2) || (layers == 3)) { + auto_ecc_level = 4; + } + min_ecc_level = 1; + if (layers == 1) { + min_ecc_level = 4; + } + if ((layers == 2) || (layers == 3)) { + min_ecc_level = 2; + } + ecc_level = auto_ecc_level; + + if ((preferredVersion >= 1) && (preferredVersion <= 13)) { + input_latch = 1; + if (preferredVersion > min_layers) { + layers = preferredVersion; + } else { + layers = min_layers; + } + } + + if (input_latch == 1) { + auto_ecc_level = 3; + if (layers == 1) { + auto_ecc_level = 5; + } + if ((layers == 2) || (layers == 3)) { + auto_ecc_level = 4; + } + ecc_level = auto_ecc_level; + if (data_cw > gm_data_codewords[(5 * (layers - 1)) + (ecc_level - 1)]) { + layers++; + } + } + + if (input_latch == 0) { + if ((preferredEccLevel >= 1) && (preferredEccLevel <= 5)) { + if (preferredEccLevel > min_ecc_level) { + ecc_level = preferredEccLevel; + } else { + ecc_level = min_ecc_level; + } + } + if (data_cw > gm_data_codewords[(5 * (layers - 1)) + (ecc_level - 1)]) { + do { + layers++; + } while ((data_cw > gm_data_codewords[(5 * (layers - 1)) + (ecc_level - 1)]) && (layers <= 13)); + } + } + + data_max = 1313; + switch (ecc_level) { + case 2: + data_max = 1167; + break; + case 3: + data_max = 1021; + break; + case 4: + data_max = 875; + break; + case 5: + data_max = 729; + break; + } + + if (data_cw > data_max) { + errorMsg.append("Input data too long"); + return false; + } + + addErrorCorrection(data_cw, layers, ecc_level); + size = 6 + (layers * 12); + modules = 1 + (layers * 2); + + encodeInfo.append("Layers: ").append(layers).append("\n"); + encodeInfo.append("ECC Level: ").append(ecc_level).append("\n"); + encodeInfo.append("Data Codewords: ").append(data_cw).append("\n"); + encodeInfo.append("ECC Codewords: ").append(gm_data_codewords[((layers - 1) * 5) + + (ecc_level - 1)]).append("\n"); + encodeInfo.append("Grid Size: ").append(modules).append(" X ").append(modules).append("\n"); + + grid = new boolean[size * size]; + + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + grid[(y * size) + x] = false; + } + } + + placeDataInGrid(modules, size); + addLayerId(size, layers, modules, ecc_level); + + /* Add macromodule frames */ + for (x = 0; x < modules; x++) { + dark = 1 - (x & 1); + for (y = 0; y < modules; y++) { + if (dark == 1) { + for (i = 0; i < 5; i++) { + grid[((y * 6) * size) + (x * 6) + i] = true; + grid[(((y * 6) + 5) * size) + (x * 6) + i] = true; + grid[(((y * 6) + i) * size) + (x * 6)] = true; + grid[(((y * 6) + i) * size) + (x * 6) + 5] = true; + } + grid[(((y * 6) + 5) * size) + (x * 6) + 5] = true; + dark = 0; + } else { + dark = 1; + } + } + } + + /* Copy values to symbol */ + symbolWidth = size; + rowCount = size; + rowHeight = new int[rowCount]; + pattern = new String[rowCount]; + + for (x = 0; x < size; x++) { + bin = new StringBuilder(); + for (y = 0; y < size; y++) { + if (grid[(x * size) + y]) { + bin.append("1"); + } else { + bin.append("0"); + } + } + rowHeight[x] = 1; + pattern[x] = bin2pat(bin.toString()); + } + + plotSymbol(); + return true; + } + + private int encodeGridMatrixBinary(int length, boolean reader) { + /* Create a binary stream representation of the input data. + 7 sets are defined - Chinese characters, Numerals, Lower case letters, Upper case letters, + Mixed numerals and latters, Control characters and 8-bit binary data */ + int sp, glyph = 0; + gmMode current_mode, next_mode, last_mode; + int c1, c2; + boolean done; + int p = 0, ppos; + int punt = 0; + int number_pad_posn; + int byte_count_posn = 0, byte_count = 0; + int shift, i; + int[] numbuf = new int[3]; + StringBuilder temp_binary; + gmMode[] modeMap = calculateModeMap(length); + + binary = new StringBuilder(); + + sp = 0; + current_mode = gmMode.NULL; + number_pad_posn = 0; + + encodeInfo.append("Encoding: "); + + if (reader) { + binary.append("1010"); /* FNC3 - Reader Initialisation */ + encodeInfo.append("INIT "); + } + + if ((eciMode != 3) && (eciMode != 29)) { + binary.append("1100"); /* ECI */ + + if ((eciMode >= 0) && (eciMode <= 1023)) { + binary.append("0"); + for (i = 0x200; i > 0; i = i >> 1) { + if ((eciMode & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + } + + if ((eciMode >= 1024) && (eciMode <= 32767)) { + binary.append("10"); + for (i = 0x4000; i > 0; i = i >> 1) { + if ((eciMode & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + } + + if ((eciMode >= 32768) && (eciMode <= 811799)) { + binary.append("11"); + for (i = 0x80000; i > 0; i = i >> 1) { + if ((eciMode & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + } + + encodeInfo.append("ECI ").append(Integer.toString(eciMode)).append(" "); + } + + do { + next_mode = modeMap[sp]; + + if (next_mode != current_mode) { + switch (current_mode) { + case NULL: + switch (next_mode) { + case GM_CHINESE: + binary.append("0001"); + break; + case GM_NUMBER: + binary.append("0010"); + break; + case GM_LOWER: + binary.append("0011"); + break; + case GM_UPPER: + binary.append("0100"); + break; + case GM_MIXED: + binary.append("0101"); + break; + case GM_BYTE: + binary.append("0111"); + break; + } + break; + case GM_CHINESE: + switch (next_mode) { + case GM_NUMBER: + binary.append("1111111100001"); + break; // 8161 + case GM_LOWER: + binary.append("1111111100010"); + break; // 8162 + case GM_UPPER: + binary.append("1111111100011"); + break; // 8163 + case GM_MIXED: + binary.append("1111111100100"); + break; // 8164 + case GM_BYTE: + binary.append("1111111100101"); + break; // 8165 + } + break; + case GM_NUMBER: + /* add numeric block padding value */ + temp_binary = new StringBuilder(binary.substring(0, number_pad_posn)); + switch (p) { + case 1: + temp_binary.append("10"); + break; // 2 pad digits + case 2: + temp_binary.append("01"); + break; // 1 pad digit + case 3: + temp_binary.append("00"); + break; // 0 pad digits + } + temp_binary.append(binary.substring(number_pad_posn, binary.length())); + binary = temp_binary; + + switch (next_mode) { + case GM_CHINESE: + binary.append("1111111011"); + break; // 1019 + case GM_LOWER: + binary.append("1111111100"); + break; // 1020 + case GM_UPPER: + binary.append("1111111101"); + break; // 1021 + case GM_MIXED: + binary.append("1111111110"); + break; // 1022 + case GM_BYTE: + binary.append("1111111111"); + break; // 1023 + } + break; + case GM_LOWER: + case GM_UPPER: + switch (next_mode) { + case GM_CHINESE: + binary.append("11100"); + break; // 28 + case GM_NUMBER: + binary.append("11101"); + break; // 29 + case GM_LOWER: + case GM_UPPER: + binary.append("11110"); + break; // 30 + case GM_MIXED: + binary.append("1111100"); + break; // 124 + case GM_BYTE: + binary.append("1111110"); + break; // 126 + } + break; + case GM_MIXED: + switch (next_mode) { + case GM_CHINESE: + binary.append("1111110001"); + break; // 1009 + case GM_NUMBER: + binary.append("1111110010"); + break; // 1010 + case GM_LOWER: + binary.append("1111110011"); + break; // 1011 + case GM_UPPER: + binary.append("1111110100"); + break; // 1012 + case GM_BYTE: + binary.append("1111110111"); + break; // 1015 + } + break; + case GM_BYTE: + /* add byte block length indicator */ + addByteCount(byte_count_posn, byte_count); + byte_count = 0; + switch (next_mode) { + case GM_CHINESE: + binary.append("0001"); + break; // 1 + case GM_NUMBER: + binary.append("0010"); + break; // 2 + case GM_LOWER: + binary.append("0011"); + break; // 3 + case GM_UPPER: + binary.append("0100"); + break; // 4 + case GM_MIXED: + binary.append("0101"); + break; // 5 + } + break; + } + + switch (next_mode) { + case GM_CHINESE: + encodeInfo.append("CHIN "); + break; + case GM_NUMBER: + encodeInfo.append("NUMB "); + break; + case GM_LOWER: + encodeInfo.append("LOWR "); + break; + case GM_UPPER: + encodeInfo.append("UPPR "); + break; + case GM_MIXED: + encodeInfo.append("MIXD "); + break; + case GM_BYTE: + encodeInfo.append("BYTE "); + break; + } + + } + last_mode = current_mode; + current_mode = next_mode; + + switch (current_mode) { + case GM_CHINESE: + done = false; + if (inputIntArray[sp] > 0xff) { + /* GB2312 character */ + c1 = (inputIntArray[sp] & 0xff00) >> 8; + c2 = inputIntArray[sp] & 0xff; + + if ((c1 >= 0xa0) && (c1 <= 0xa9)) { + glyph = (0x60 * (c1 - 0xa1)) + (c2 - 0xa0); + } + if ((c1 >= 0xb0) && (c1 <= 0xf7)) { + glyph = (0x60 * (c1 - 0xb0 + 9)) + (c2 - 0xa0); + } + done = true; + } + if (!(done)) { + if (sp != (length - 1)) { + if ((inputIntArray[sp] == 13) && (inputIntArray[sp + 1] == 10)) { + /* End of Line */ + glyph = 7776; + sp++; + } + done = true; + } + } + if (!(done)) { + if (sp != (length - 1)) { + if (((inputIntArray[sp] >= '0') && (inputIntArray[sp] <= '9')) && ((inputIntArray[sp + 1] >= '0') && (inputIntArray[sp + 1] <= '9'))) { + /* Two digits */ + glyph = 8033 + (10 * (inputIntArray[sp] - '0')) + (inputIntArray[sp + 1] - '0'); + sp++; + } + } + } + if (!(done)) { + /* Byte value */ + glyph = 7777 + inputIntArray[sp]; + } + + encodeInfo.append(Integer.toString(glyph)).append(" "); + + for (i = 0x1000; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + sp++; + break; + + case GM_NUMBER: + if (last_mode != current_mode) { + /* Reserve a space for numeric digit padding value (2 bits) */ + number_pad_posn = binary.length(); + } + p = 0; + ppos = -1; + + /* Numeric compression can also include certain combinations of + non-numeric character */ + numbuf[0] = '0'; + numbuf[1] = '0'; + numbuf[2] = '0'; + do { + if ((inputIntArray[sp] >= '0') && (inputIntArray[sp] <= '9')) { + numbuf[p] = inputIntArray[sp]; + p++; + } + switch (inputIntArray[sp]) { + case ' ': + case '+': + case '-': + case '.': + case ',': + punt = inputIntArray[sp]; + ppos = p; + break; + } + if (sp < (length - 1)) { + if ((inputIntArray[sp] == 13) && (inputIntArray[sp + 1] == 10)) { + /* */ + punt = inputIntArray[sp]; + sp++; + ppos = p; + } + } + sp++; + } while ((p < 3) && (sp < length)); + + if (ppos != -1) { + switch (punt) { + case ' ': + glyph = 0; + break; + case '+': + glyph = 3; + break; + case '-': + glyph = 6; + break; + case '.': + glyph = 9; + break; + case ',': + glyph = 12; + break; + case 0x13: + glyph = 15; + break; + } + glyph += ppos; + glyph += 1000; + + encodeInfo.append(Integer.toString(glyph)).append(" "); + + for (i = 0x200; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + } + + glyph = (100 * (numbuf[0] - '0')) + (10 * (numbuf[1] - '0')) + (numbuf[2] - '0'); + encodeInfo.append(Integer.toString(glyph)).append(" "); + + for (i = 0x200; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + break; + + case GM_BYTE: + if (last_mode != current_mode) { + /* Reserve space for byte block length indicator (9 bits) */ + byte_count_posn = binary.length(); + } + if (byte_count == 512) { + /* Maximum byte block size is 512 bytes. If longer is needed then start a new block */ + addByteCount(byte_count_posn, byte_count); + binary.append("0111"); + byte_count_posn = binary.length(); + byte_count = 0; + } + + glyph = inputIntArray[sp]; + encodeInfo.append(Integer.toString(glyph)).append(" "); + for (i = 0x80; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + sp++; + byte_count++; + break; + + case GM_MIXED: + shift = 1; + if ((inputIntArray[sp] >= '0') && (inputIntArray[sp] <= '9')) { + shift = 0; + } + if ((inputIntArray[sp] >= 'A') && (inputIntArray[sp] <= 'Z')) { + shift = 0; + } + if ((inputIntArray[sp] >= 'a') && (inputIntArray[sp] <= 'z')) { + shift = 0; + } + if (inputIntArray[sp] == ' ') { + shift = 0; + } + + if (shift == 0) { + /* Mixed Mode character */ + glyph = positionOf((char) inputIntArray[sp], MIXED_ALPHANUM_SET); + encodeInfo.append(Integer.toString(glyph)).append(" "); + + for (i = 0x20; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + } else { + /* Shift Mode character */ + binary.append("1111110110"); /* 1014 - shift indicator */ + + addShiftCharacter(inputIntArray[sp]); + } + + sp++; + break; + + case GM_UPPER: + shift = 1; + if ((inputIntArray[sp] >= 'A') && (inputIntArray[sp] <= 'Z')) { + shift = 0; + } + if (inputIntArray[sp] == ' ') { + shift = 0; + } + + if (shift == 0) { + /* Upper Case character */ + glyph = positionOf((char) inputIntArray[sp], MIXED_ALPHANUM_SET) - 10; + if (glyph == 52) { + // Space character + glyph = 26; + } + encodeInfo.append(Integer.toString(glyph)).append(" "); + + for (i = 0x10; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + + } else { + /* Shift Mode character */ + binary.append("1111101"); /* 127 - shift indicator */ + + addShiftCharacter(inputIntArray[sp]); + } + + sp++; + break; + + case GM_LOWER: + shift = 1; + if ((inputIntArray[sp] >= 'a') && (inputIntArray[sp] <= 'z')) { + shift = 0; + } + if (inputIntArray[sp] == ' ') { + shift = 0; + } + + if (shift == 0) { + /* Lower Case character */ + glyph = positionOf((char) inputIntArray[sp], MIXED_ALPHANUM_SET) - 36; + encodeInfo.append(Integer.toString(glyph)).append(" "); + + for (i = 0x10; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + + } else { + /* Shift Mode character */ + binary.append("1111101"); /* 127 - shift indicator */ + + addShiftCharacter(inputIntArray[sp]); + } + + sp++; + break; + } + if (binary.length() > 9191) { + return 1; + } + + } while (sp < length); + + encodeInfo.append("\n"); + + if (current_mode == gmMode.GM_NUMBER) { + /* add numeric block padding value */ + temp_binary = new StringBuilder(binary.substring(0, number_pad_posn)); + switch (p) { + case 1: + temp_binary.append("10"); + break; // 2 pad digits + case 2: + temp_binary.append("01"); + break; // 1 pad digit + case 3: + temp_binary.append("00"); + break; // 0 pad digits + } + temp_binary.append(binary.substring(number_pad_posn, binary.length())); + binary = temp_binary; + } + + if (current_mode == gmMode.GM_BYTE) { + /* Add byte block length indicator */ + addByteCount(byte_count_posn, byte_count); + } + + /* Add "end of data" character */ + switch (current_mode) { + case GM_CHINESE: + binary.append("1111111100000"); + break; // 8160 + case GM_NUMBER: + binary.append("1111111010"); + break; // 1018 + case GM_LOWER: + case GM_UPPER: + binary.append("11011"); + break; // 27 + case GM_MIXED: + binary.append("1111110000"); + break; // 1008 + case GM_BYTE: + binary.append("0000"); + break; // 0 + } + + /* Add padding bits if required */ + p = 7 - (binary.length() % 7); + if (p == 7) { + p = 0; + } + for (i = 0; i < p; i++) { + binary.append("0"); + } + + if (binary.length() > 9191) { + return 1; + } + + return 0; + } + + private gmMode[] calculateModeMap(int length) { + gmMode[] modeMap = new gmMode[length]; + int i; + int digitStart, digitLength; + boolean digits; + int spaceStart, spaceLength; + boolean spaces; + int[] segmentLength; + gmMode[] segmentType; + int[] segmentStart; + int segmentCount; + + // Step 1 + // Characters in GB2312 are encoded as Chinese characters + for (i = 0; i < length; i++) { + modeMap[i] = gmMode.NULL; + if (inputIntArray[i] > 0xFF) { + modeMap[i] = gmMode.GM_CHINESE; + } + } + + // Consecutive characters, if preceeded by or followed + // by chinese characters, are encoded as chinese characters. + if (length > 3) { + i = 1; + do { + if ((inputIntArray[i] == 13) && (inputIntArray[i + 1] == 10)) { + // End of line (CR/LF) + + if (modeMap[i - 1] == gmMode.GM_CHINESE) { + modeMap[i] = gmMode.GM_CHINESE; + modeMap[i + 1] = gmMode.GM_CHINESE; + } + i += 2; + } else { + i++; + } + } while (i < length - 1); + + i = length - 3; + do { + if ((inputIntArray[i] == 13) && (inputIntArray[i + 1] == 10)) { + // End of line (CR/LF) + if (modeMap[i + 2] == gmMode.GM_CHINESE) { + modeMap[i] = gmMode.GM_CHINESE; + modeMap[i + 1] = gmMode.GM_CHINESE; + } + i -= 2; + } else { + i--; + } + } while (i > 0); + } + + // Digit pairs between chinese characters encode as chinese characters. + digits = false; + digitLength = 0; + digitStart = 0; + for (i = 1; i < length - 1; i++) { + if ((inputIntArray[i] >= 48) && (inputIntArray[i] <= 57)) { + // '0' to '9' + if (!digits) { + digits = true; + digitLength = 1; + digitStart = i; + } else { + digitLength++; + } + } else { + if (digits) { + if ((digitLength % 2) == 0) { + if ((modeMap[digitStart - 1] == gmMode.GM_CHINESE) && + (modeMap[i] == gmMode.GM_CHINESE)) { + for (int j = 0; j < digitLength; j++) { + modeMap[i - j - 1] = gmMode.GM_CHINESE; + } + } + } + digits = false; + } + } + } + + // Step 2: all characters 'a' to 'z' are lowercase. + for (i = 0; i < length; i++) { + if ((inputIntArray[i] >= 97) && (inputIntArray[i] <= 122)) { + modeMap[i] = gmMode.GM_LOWER; + } + } + + // Step 3: all characters 'A' to 'Z' are uppercase. + for (i = 0; i < length; i++) { + if ((inputIntArray[i] >= 65) && (inputIntArray[i] <= 90)) { + modeMap[i] = gmMode.GM_UPPER; + } + } + + // Step 4: find consecutive characters preceeded or followed + // by uppercase or lowercase. + spaces = false; + spaceLength = 0; + spaceStart = 0; + for (i = 1; i < length - 1; i++) { + if (inputIntArray[i] == 32) { + if (!spaces) { + spaces = true; + spaceLength = 1; + spaceStart = i; + } else { + spaceLength++; + } + } else { + if (spaces) { + + gmMode modeX = modeMap[spaceStart - 1]; + gmMode modeY = modeMap[i]; + + if ((modeX == gmMode.GM_LOWER) || (modeX == gmMode.GM_UPPER)) { + for (int j = 0; j < spaceLength; j++) { + modeMap[i - j - 1] = modeX; + } + } else { + if ((modeY == gmMode.GM_LOWER) || (modeY == gmMode.GM_UPPER)) { + for (int j = 0; j < spaceLength; j++) { + modeMap[i - j - 1] = modeY; + } + } + } + spaces = false; + } + } + } + + // Step 5: Unassigned characters '0' to '9' are assigned as numerals. + // Non-numeric characters in table 7 are also assigned as numerals. + for (i = 0; i < length; i++) { + if (modeMap[i] == gmMode.NULL) { + if ((inputIntArray[i] >= 48) && (inputIntArray[i] <= 57)) { + // '0' to '9' + modeMap[i] = gmMode.GM_NUMBER; + } else { + switch (inputIntArray[i]) { + case 32: // Space + case 43: // '+' + case 45: // '-' + case 46: // "." + case 44: // "," + modeMap[i] = gmMode.GM_NUMBER; + break; + case 13: // CR + if (i < length - 1) { + if (inputIntArray[i + 1] == 10) { // LF + // + modeMap[i] = gmMode.GM_NUMBER; + modeMap[i + 1] = gmMode.GM_NUMBER; + } + } + } + } + } + } + + // Step 6: The remining unassigned bytes are assigned as 8-bit binary + for (i = 0; i < length; i++) { + if (modeMap[i] == gmMode.NULL) { + modeMap[i] = gmMode.GM_BYTE; + } + } + + // break into segments + segmentLength = new int[length]; + segmentType = new gmMode[length]; + segmentStart = new int[length]; + + segmentCount = 0; + segmentLength[0] = 1; + segmentType[0] = modeMap[0]; + segmentStart[0] = 0; + for (i = 1; i < length; i++) { + if (modeMap[i] == modeMap[i - 1]) { + segmentLength[segmentCount]++; + } else { + segmentCount++; + segmentLength[segmentCount] = 1; + segmentType[segmentCount] = modeMap[i]; + segmentStart[segmentCount] = i; + } + } + + // A segment can be a control segment if + // a) It is not the start segment of the data stream + // b) All characters are control characters + // c) The length of the segment is no more than 3 + // d) The previous segment is not chinese + if (segmentCount > 1) { + for (i = 1; i < segmentCount; i++) { // (a) + if ((segmentLength[i] <= 3) && (segmentType[i - 1] != gmMode.GM_CHINESE)) { // (c) and (d) + boolean controlLatch = true; + for (int j = 0; j < segmentLength[i]; j++) { + boolean thischarLatch = false; + for (int k = 0; k < 63; k++) { + if (inputIntArray[segmentStart[i] + j] == shift_set[k]) { + thischarLatch = true; + } + } + + if (!(thischarLatch)) { + // This character is not a control character + controlLatch = false; + } + } + + if (controlLatch) { // (b) + segmentType[i] = gmMode.GM_CONTROL; + } + } + } + } + + // Stages 7 to 9 + if (segmentCount >= 3) { + for (i = 0; i < segmentCount - 1; i++) { + gmMode pm, tm, nm, lm; + int tl, nl, ll, position; + boolean lastSegment = false; + + if (i == 0) { + pm = gmMode.NULL; + } else { + pm = segmentType[i - 1]; + } + + tm = segmentType[i]; + tl = segmentLength[i]; + + nm = segmentType[i + 1]; + nl = segmentLength[i + 1]; + + lm = segmentType[i + 2]; + ll = segmentLength[i + 2]; + + position = segmentStart[i]; + + if (i + 2 == segmentCount) { + lastSegment = true; + } + + segmentType[i] = getBestMode(pm, tm, nm, lm, tl, nl, ll, position, lastSegment); + + if (segmentType[i] == gmMode.GM_CONTROL) { + segmentType[i] = segmentType[i - 1]; + } + } + + segmentType[i] = appxDnextSection; + segmentType[i + 1] = appxDlastSection; + + if (segmentType[i] == gmMode.GM_CONTROL) { + segmentType[i] = segmentType[i - 1]; + } + if (segmentType[i + 1] == gmMode.GM_CONTROL) { + segmentType[i + 1] = segmentType[i]; + } + +// Uncomment these lines to override mode selection and generate symbol as shown +// in image D.1 for the test data "AAT2556 电池充电器+é™åŽ‹è½¬æ¢å™¨ 200mA至2A tel:86 019 82512738" +// segmentType[9] = gmMode.GM_LOWER; +// segmentType[10] = gmMode.GM_LOWER; + } + + // Copy segments back to modeMap + for (i = 0; i < segmentCount; i++) { + for (int j = 0; j < segmentLength[i]; j++) { + modeMap[segmentStart[i] + j] = segmentType[i]; + } + } + + return modeMap; + } + + private boolean isTransitionValid(gmMode previousMode, gmMode thisMode) { + // Filters possible encoding data types from table D.1 + boolean isValid = false; + + switch (previousMode) { + case GM_CHINESE: + switch (thisMode) { + case GM_CHINESE: + case GM_BYTE: + isValid = true; + break; + } + break; + case GM_NUMBER: + switch (thisMode) { + case GM_NUMBER: + case GM_MIXED: + case GM_BYTE: + case GM_CHINESE: + isValid = true; + break; + } + break; + case GM_LOWER: + switch (thisMode) { + case GM_LOWER: + case GM_MIXED: + case GM_BYTE: + case GM_CHINESE: + isValid = true; + break; + } + break; + case GM_UPPER: + switch (thisMode) { + case GM_UPPER: + case GM_MIXED: + case GM_BYTE: + case GM_CHINESE: + isValid = true; + break; + } + break; + case GM_CONTROL: + switch (thisMode) { + case GM_CONTROL: + case GM_BYTE: + case GM_CHINESE: + isValid = true; + break; + } + break; + case GM_BYTE: + switch (thisMode) { + case GM_BYTE: + case GM_CHINESE: + isValid = true; + break; + } + break; + } + + return isValid; + } + + private gmMode intToMode(int input) { + gmMode retVal; + + switch (input) { + case 1: + retVal = gmMode.GM_CHINESE; + break; + case 2: + retVal = gmMode.GM_BYTE; + break; + case 3: + retVal = gmMode.GM_CONTROL; + break; + case 4: + retVal = gmMode.GM_MIXED; + break; + case 5: + retVal = gmMode.GM_UPPER; + break; + case 6: + retVal = gmMode.GM_LOWER; + break; + case 7: + retVal = gmMode.GM_NUMBER; + break; + default: + retVal = gmMode.NULL; + break; + } + + return retVal; + } + + private gmMode getBestMode(gmMode pm, gmMode tm, gmMode nm, gmMode lm, int tl, int nl, int ll, int position, boolean lastSegment) { + int tmi, nmi, lmi; + gmMode bestMode = tm; + int binaryLength; + int bestBinaryLength = Integer.MAX_VALUE; + + for (tmi = 1; tmi < 8; tmi++) { + if (isTransitionValid(tm, intToMode(tmi))) { + for (nmi = 1; nmi < 8; nmi++) { + if (isTransitionValid(nm, intToMode(nmi))) { + for (lmi = 1; lmi < 8; lmi++) { + if (isTransitionValid(lm, intToMode(lmi))) { + binaryLength = getBinaryLength(pm, intToMode(tmi), intToMode(nmi), intToMode(lmi), tl, nl, ll, position, lastSegment); + + if (binaryLength <= bestBinaryLength) { + bestMode = intToMode(tmi); + appxDnextSection = intToMode(nmi); + appxDlastSection = intToMode(lmi); + bestBinaryLength = binaryLength; + } + } + } + } + } + } + } + + return bestMode; + } + + private int getBinaryLength(gmMode pm, gmMode tm, gmMode nm, gmMode lm, int tl, int nl, int ll, int position, boolean lastSegment) { + int binaryLength; + + binaryLength = getChunkLength(pm, tm, tl, position); + binaryLength += getChunkLength(tm, nm, nl, (position + tl)); + binaryLength += getChunkLength(nm, lm, ll, (position + tl + nl)); + + if (lastSegment) { + switch (lm) { + case GM_CHINESE: + binaryLength += 13; + break; + case GM_NUMBER: + binaryLength += 10; + break; + case GM_LOWER: + case GM_UPPER: + binaryLength += 5; + break; + case GM_MIXED: + binaryLength += 10; + break; + case GM_BYTE: + binaryLength += 4; + break; + } + } + + return binaryLength; + } + + private int getChunkLength(gmMode lastMode, gmMode thisMode, int thisLength, int position) { + int byteLength; + + switch (thisMode) { + case GM_CHINESE: + byteLength = calcChineseLength(position, thisLength); + break; + case GM_NUMBER: + byteLength = calcNumberLength(position, thisLength); + break; + case GM_LOWER: + byteLength = 5 * thisLength; + break; + case GM_UPPER: + byteLength = 5 * thisLength; + break; + case GM_MIXED: + byteLength = calcMixedLength(position, thisLength); + break; + case GM_CONTROL: + byteLength = 6 * thisLength; + break; + default: + //case GM_BYTE: + byteLength = calcByteLength(position, thisLength); + break; + } + + switch (lastMode) { + case NULL: + byteLength += 4; + break; + case GM_CHINESE: + if ((thisMode != gmMode.GM_CHINESE) && (thisMode != gmMode.GM_CONTROL)) { + byteLength += 13; + } + break; + case GM_NUMBER: + if ((thisMode != gmMode.GM_CHINESE) && (thisMode != gmMode.GM_CONTROL)) { + byteLength += 10; + } + break; + case GM_LOWER: + switch (thisMode) { + case GM_CHINESE: + case GM_NUMBER: + case GM_UPPER: + byteLength += 5; + break; + case GM_MIXED: + case GM_CONTROL: + case GM_BYTE: + byteLength += 7; + break; + } + break; + case GM_UPPER: + switch (thisMode) { + case GM_CHINESE: + case GM_NUMBER: + case GM_LOWER: + byteLength += 5; + break; + case GM_MIXED: + case GM_CONTROL: + case GM_BYTE: + byteLength += 7; + break; + } + break; + case GM_MIXED: + if (thisMode != gmMode.GM_MIXED) { + byteLength += 10; + } + break; + case GM_BYTE: + if (thisMode != gmMode.GM_BYTE) { + byteLength += 4; + } + break; + } + + if ((lastMode != gmMode.GM_BYTE) && (thisMode == gmMode.GM_BYTE)) { + byteLength += 9; + } + + if ((lastMode != gmMode.GM_NUMBER) && (thisMode == gmMode.GM_NUMBER)) { + byteLength += 2; + } + + return byteLength; + } + + private int calcChineseLength(int position, int length) { + int i = 0; + int bits = 0; + + do { + bits += 13; + + if (i < length) { + if ((inputIntArray[position + i] == 13) && (inputIntArray[position + i + 1] == 10)) { + // + i++; + } + + if (((inputIntArray[position + i] >= 48) && (inputIntArray[position + i] <= 57)) && + ((inputIntArray[position + i + 1] >= 48) && (inputIntArray[position + i + 1] <= 57))) { + // two digits + i++; + } + } + i++; + } while (i < length); + + return bits; + } + + private int calcMixedLength(int position, int length) { + int bits = 0; + int i; + + for (i = 0; i < length; i++) { + bits += 6; + for (int k = 0; k < 63; k++) { + if (inputIntArray[position + i] == shift_set[k]) { + bits += 10; + } + } + } + + return bits; + } + + private int calcNumberLength(int position, int length) { + int i; + int bits = 0; + int numbers = 0; + int nonnumbers = 0; + + for (i = 0; i < length; i++) { + if ((inputIntArray[position + i] >= 48) && (inputIntArray[position + i] <= 57)) { + numbers++; + } else { + nonnumbers++; + } + + if (i != 0) { + if ((inputIntArray[position + i] == 10) && (inputIntArray[position + i - 1] == 13)) { + // + nonnumbers--; + } + } + + if (numbers == 3) { + if (nonnumbers == 1) { + bits += 20; + } else { + bits += 10; + } + if (nonnumbers > 1) { + // Invalid encoding + bits += 100; + } + numbers = 0; + nonnumbers = 0; + } + } + + if (numbers > 0) { + if (nonnumbers == 1) { + bits += 20; + } else { + bits += 10; + } + } + + if (nonnumbers > 1) { + // Invalid + bits += 100; + } + + if (!((inputIntArray[position + i - 1] >= 48) && (inputIntArray[position + i - 1] <= 57))) { + // Data must end with a digit + bits += 100; + } + + + return bits; + } + + private int calcByteLength(int position, int length) { + int i; + int bits = 0; + + for (i = 0; i < length; i++) { + if (inputIntArray[position + i] <= 0xFF) { + bits += 8; + } else { + bits += 16; + } + } + + return bits; + } + + private void addByteCount(int byte_count_posn, int byte_count) { + /* Add the length indicator for byte encoded blocks */ + int i; + StringBuilder temp_binary; + + temp_binary = new StringBuilder(binary.substring(0, byte_count_posn)); + for (i = 0; i < 9; i++) { + if ((byte_count & (0x100 >> i)) != 0) { + temp_binary.append("0"); + } else { + temp_binary.append("1"); + } + } + temp_binary.append(binary.substring(byte_count_posn, binary.length())); + binary = temp_binary; + } + + void addShiftCharacter(int shifty) { + /* Add a control character to the data stream */ + int i; + int glyph = 0; + + for (i = 0; i < 64; i++) { + if (shift_set[i] == shifty) { + glyph = i; + } + } + + encodeInfo.append("SHT/").append(Integer.toString(glyph)).append(" "); + + for (i = 0x20; i > 0; i = i >> 1) { + if ((glyph & i) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + } + + private void addErrorCorrection(int data_posn, int layers, int ecc_level) { + int data_cw, i, j, wp; + int n1, b1, n2, b2, e1, b3, e2; + int block_size, data_size, ecc_size; + int[] data = new int[1320]; + int[] block = new int[130]; + int[] data_block = new int[115]; + int[] ecc_block = new int[70]; + ReedSolomon rs = new ReedSolomon(); + + data_cw = gm_data_codewords[((layers - 1) * 5) + (ecc_level - 1)]; + + for (i = 0; i < 1320; i++) { + data[i] = 0; + } + + /* Convert from binary sream to 7-bit codewords */ + for (i = 0; i < data_posn; i++) { + for (j = 0; j < 7; j++) { + if (binary.charAt((i * 7) + j) == '1') { + data[i] += 0x40 >> j; + } + } + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < data_posn; i++) { + encodeInfo.append(Integer.toString(data[i])).append(" "); + } + encodeInfo.append("\n"); + + /* Add padding codewords */ + data[data_posn] = 0x00; + for (i = (data_posn + 1); i < data_cw; i++) { + if ((i & 1) != 0) { + data[i] = 0x7e; + } else { + data[i] = 0x00; + } + } + + /* Get block sizes */ + n1 = gm_n1[(layers - 1)]; + b1 = gm_b1[(layers - 1)]; + n2 = n1 - 1; + b2 = gm_b2[(layers - 1)]; + e1 = gm_ebeb[((layers - 1) * 20) + ((ecc_level - 1) * 4)]; + b3 = gm_ebeb[((layers - 1) * 20) + ((ecc_level - 1) * 4) + 1]; + e2 = gm_ebeb[((layers - 1) * 20) + ((ecc_level - 1) * 4) + 2]; + + /* Split the data into blocks */ + wp = 0; + for (i = 0; i < (b1 + b2); i++) { + if (i < b1) { + block_size = n1; + } else { + block_size = n2; + } + if (i < b3) { + ecc_size = e1; + } else { + ecc_size = e2; + } + data_size = block_size - ecc_size; + + /* printf("block %d/%d: data %d / ecc %d\n", i + 1, (b1 + b2), data_size, ecc_size);*/ + for (j = 0; j < data_size; j++) { + data_block[j] = data[wp]; + wp++; + } + + /* Calculate ECC data for this block */ + rs.init_gf(0x89); + rs.init_code(ecc_size, 1); + rs.encode(data_size, data_block); + for (j = 0; j < ecc_size; j++) { + ecc_block[j] = rs.getResult(j); + } + + /* Correct error correction data but in reverse order */ + for (j = 0; j < data_size; j++) { + block[j] = data_block[j]; + } + for (j = 0; j < ecc_size; j++) { + block[(j + data_size)] = ecc_block[ecc_size - j - 1]; + } + + for (j = 0; j < n2; j++) { + word[((b1 + b2) * j) + i] = block[j]; + } + if (block_size == n1) { + word[((b1 + b2) * (n1 - 1)) + i] = block[(n1 - 1)]; + } + } + } + + private void placeDataInGrid(int modules, int size) { + int x, y, macromodule, offset; + + offset = 13 - ((modules - 1) / 2); + for (y = 0; y < modules; y++) { + for (x = 0; x < modules; x++) { + macromodule = gm_macro_matrix[((y + offset) * 27) + (x + offset)]; + placeMacroModule(x, y, word[macromodule * 2], word[(macromodule * 2) + 1], size); + } + } + } + + private void placeMacroModule(int x, int y, int word1, int word2, int size) { + int i, j; + + i = (x * 6) + 1; + j = (y * 6) + 1; + + if ((word2 & 0x40) != 0) { + grid[(j * size) + i + 2] = true; + } + if ((word2 & 0x20) != 0) { + grid[(j * size) + i + 3] = true; + } + if ((word2 & 0x10) != 0) { + grid[((j + 1) * size) + i] = true; + } + if ((word2 & 0x08) != 0) { + grid[((j + 1) * size) + i + 1] = true; + } + if ((word2 & 0x04) != 0) { + grid[((j + 1) * size) + i + 2] = true; + } + if ((word2 & 0x02) != 0) { + grid[((j + 1) * size) + i + 3] = true; + } + if ((word2 & 0x01) != 0) { + grid[((j + 2) * size) + i] = true; + } + if ((word1 & 0x40) != 0) { + grid[((j + 2) * size) + i + 1] = true; + } + if ((word1 & 0x20) != 0) { + grid[((j + 2) * size) + i + 2] = true; + } + if ((word1 & 0x10) != 0) { + grid[((j + 2) * size) + i + 3] = true; + } + if ((word1 & 0x08) != 0) { + grid[((j + 3) * size) + i] = true; + } + if ((word1 & 0x04) != 0) { + grid[((j + 3) * size) + i + 1] = true; + } + if ((word1 & 0x02) != 0) { + grid[((j + 3) * size) + i + 2] = true; + } + if ((word1 & 0x01) != 0) { + grid[((j + 3) * size) + i + 3] = true; + } + } + + private void addLayerId(int size, int layers, int modules, int ecc_level) { + /* Place the layer ID into each macromodule */ + + int i, j, layer, start, stop; + int[] layerid = new int[layers + 1]; + int[] id = new int[modules * modules]; + + + /* Calculate Layer IDs */ + for (i = 0; i <= layers; i++) { + if (ecc_level == 1) { + layerid[i] = 3 - (i % 4); + } else { + layerid[i] = (i + 5 - ecc_level) % 4; + } + } + + for (i = 0; i < modules; i++) { + for (j = 0; j < modules; j++) { + id[(i * modules) + j] = 0; + } + } + + /* Calculate which value goes in each macromodule */ + start = modules / 2; + stop = modules / 2; + for (layer = 0; layer <= layers; layer++) { + for (i = start; i <= stop; i++) { + id[(start * modules) + i] = layerid[layer]; + id[(i * modules) + start] = layerid[layer]; + id[((modules - start - 1) * modules) + i] = layerid[layer]; + id[(i * modules) + (modules - start - 1)] = layerid[layer]; + } + start--; + stop++; + } + + /* Place the data in the grid */ + for (i = 0; i < modules; i++) { + for (j = 0; j < modules; j++) { + if ((id[(i * modules) + j] & 0x02) != 0) { + grid[(((i * 6) + 1) * size) + (j * 6) + 1] = true; + } + if ((id[(i * modules) + j] & 0x01) != 0) { + grid[(((i * 6) + 1) * size) + (j * 6) + 2] = true; + } + } + } + } + + private enum gmMode { + + NULL, GM_NUMBER, GM_LOWER, GM_UPPER, GM_MIXED, GM_CONTROL, GM_BYTE, GM_CHINESE + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/HumanReadableLocation.java b/barcode/src/main/java/org/xbib/graphics/barcode/HumanReadableLocation.java new file mode 100755 index 0000000..423f4df --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/HumanReadableLocation.java @@ -0,0 +1,22 @@ +package org.xbib.graphics.barcode; + +/** + * The location of a bar code's human-readable text. + */ +public enum HumanReadableLocation { + + /** + * Display the human-readable text below the bar code. + */ + BOTTOM, + + /** + * Display the human-readable text above the bar code. + */ + TOP, + + /** + * Do not display the human-readable text. + */ + NONE +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/JapanPost.java b/barcode/src/main/java/org/xbib/graphics/barcode/JapanPost.java new file mode 100755 index 0000000..ebfab19 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/JapanPost.java @@ -0,0 +1,138 @@ +package org.xbib.graphics.barcode; + +import java.awt.geom.Rectangle2D; +import java.util.Locale; + +/** + * Implements the Japanese Postal Code symbology as used to encode address + * data for mail items in Japan. Valid input characters are digits 0-9, + * characters A-Z and the dash (-) character. A modulo-19 check digit is + * added and should not be included in the input data. + */ +public class JapanPost extends Symbol { + + private static final String[] JAPAN_TABLE = { + "FFT", "FDA", "DFA", "FAD", "FTF", "DAF", "AFD", "ADF", "TFF", "FTT", + "TFT", "DAT", "DTA", "ADT", "TDA", "ATD", "TAD", "TTF", "FFF" + }; + + private static final char[] KASUT_SET = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', 'a', 'b', 'c', + 'd', 'e', 'f', 'g', 'h' + }; + + private static final char[] CH_KASUT_SET = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', 'a', 'b', 'c', + 'd', 'e', 'f', 'g', 'h' + }; + + @Override + public boolean encode() { + StringBuilder dest; + StringBuilder inter; + int i, sum, check; + char c; + + content = content.toUpperCase(Locale.ENGLISH); + if (!(content.matches("[0-9A-Z\\-]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + + inter = new StringBuilder(); + + for (i = 0; + (i < content.length()) && (inter.length() < 20); i++) { + c = content.charAt(i); + + if ((c >= '0') && (c <= '9')) { + inter.append(c); + } + if (c == '-') { + inter.append(c); + } + if ((c >= 'A') && (c <= 'J')) { + inter.append('a'); + inter.append(CH_KASUT_SET[(c - 'A')]); + } + + if ((c >= 'K') && (c <= 'O')) { + inter.append('b'); + inter.append(CH_KASUT_SET[(c - 'K')]); + } + + if ((c >= 'U') && (c <= 'Z')) { + inter.append('c'); + inter.append(CH_KASUT_SET[(c - 'U')]); + } + } + + for (i = inter.length(); i < 20; i++) { + inter.append("d"); + } + + dest = new StringBuilder("FD"); + + sum = 0; + for (i = 0; i < 20; i++) { + dest.append(JAPAN_TABLE[positionOf(inter.charAt(i), KASUT_SET)]); + sum += positionOf(inter.charAt(i), CH_KASUT_SET); + } + + /* Calculate check digit */ + check = 19 - (sum % 19); + if (check == 19) { + check = 0; + } + dest.append(JAPAN_TABLE[positionOf(CH_KASUT_SET[check], KASUT_SET)]); + dest.append("DF"); + + encodeInfo.append("Encoding: ").append(dest).append("\n"); + encodeInfo.append("Check Digit: ").append(check).append("\n"); + + readable = new StringBuilder(); + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + + plotSymbol(); + + return true; + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + getRectangles().clear(); + x = 0; + w = 1; + y = 0; + h = 0; + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + switch (pattern[0].charAt(xBlock)) { + case 'A': + y = 0; + h = 5; + break; + case 'D': + y = 3; + h = 5; + break; + case 'F': + y = 0; + h = 8; + break; + case 'T': + y = 3; + h = 2; + break; + } + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + x += 2; + } + symbolWidth = pattern[0].length() * 3; + symbolHeight = 8; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/KixCode.java b/barcode/src/main/java/org/xbib/graphics/barcode/KixCode.java new file mode 100755 index 0000000..faecb34 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/KixCode.java @@ -0,0 +1,95 @@ +package org.xbib.graphics.barcode; + +import java.awt.geom.Rectangle2D; +import java.util.Locale; + +/** + * Implements Dutch Post KIX Code as used by Royal Dutch TPG Post + * (Netherlands). Input data can consist of digits 0-9 and characters A-Z. + * Input should be 11 characters in length. No check digit is added. + */ +public class KixCode extends Symbol { + + /* Handles Dutch Post TNT KIX symbols */ + /* The same as RM4SCC but without check digit */ + /* Specification at http://www.tntpost.nl/zakelijk/klantenservice/downloads/kIX_code/download.aspx */ + + private final String[] RoyalTable = { + "TTFF", "TDAF", "TDFA", "DTAF", "DTFA", "DDAA", "TADF", "TFTF", "TFDA", + "DATF", "DADA", "DFTA", "TAFD", "TFAD", "TFFT", "DAAD", "DAFT", "DFAT", + "ATDF", "ADTF", "ADDA", "FTTF", "FTDA", "FDTA", "ATFD", "ADAD", "ADFT", + "FTAD", "FTFT", "FDAT", "AADD", "AFTD", "AFDT", "FATD", "FADT", "FFTT" + }; + + private final char[] krSet = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + }; + + @Override + public boolean encode() { + StringBuilder dest; + int i; + + content = content.toUpperCase(Locale.ENGLISH); + + if (!(content.matches("[0-9A-Z]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + + dest = new StringBuilder(); + + for (i = 0; i < content.length(); i++) { + dest.append(RoyalTable[positionOf(content.charAt(i), krSet)]); + } + + encodeInfo.append("Encoding: ").append(dest).append("\n"); + + readable = new StringBuilder(); + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + getRectangles().clear(); + x = 0; + w = 1; + y = 0; + h = 0; + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + switch (pattern[0].charAt(xBlock)) { + case 'A': + y = 0; + h = 5; + break; + case 'D': + y = 3; + h = 5; + break; + case 'F': + y = 0; + h = 8; + break; + case 'T': + y = 3; + h = 2; + break; + } + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + x += 2; + } + symbolWidth = pattern[0].length() * 3; + symbolHeight = 8; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/KoreaPost.java b/barcode/src/main/java/org/xbib/graphics/barcode/KoreaPost.java new file mode 100755 index 0000000..6ca94e3 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/KoreaPost.java @@ -0,0 +1,61 @@ +package org.xbib.graphics.barcode; + +/** + * Implements Korea Post Barcode. Input should consist of of a six-digit + * number. A Modulo-10 check digit is calculated and added, and should not form + * part of the input data. + */ +public class KoreaPost extends Symbol { + + String[] koreaTable = { + "1313150613", "0713131313", "0417131313", "1506131313", "0413171313", + "17171313", "1315061313", "0413131713", "17131713", "13171713" + }; + + @Override + public boolean encode() { + StringBuilder accumulator = new StringBuilder(); + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (content.length() > 6) { + errorMsg.append("Input data too long"); + return false; + } + + StringBuilder add_zero = new StringBuilder(); + int i, j, total = 0, checkd; + + for (i = 0; i < (6 - content.length()); i++) { + add_zero.append("0"); + } + add_zero.append(content); + + + for (i = 0; i < add_zero.length(); i++) { + j = Character.getNumericValue(add_zero.charAt(i)); + accumulator.append(koreaTable[j]); + total += j; + } + + checkd = 10 - (total % 10); + if (checkd == 10) { + checkd = 0; + } + encodeInfo.append("Check Digit: ").append(checkd).append("\n"); + + accumulator.append(koreaTable[checkd]); + + readable.append(add_zero).append(checkd); + pattern = new String[1]; + pattern[0] = accumulator.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Logmars.java b/barcode/src/main/java/org/xbib/graphics/barcode/Logmars.java new file mode 100755 index 0000000..71ea571 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Logmars.java @@ -0,0 +1,97 @@ +package org.xbib.graphics.barcode; + +/** + * Implements the LOGMARS (Logistics Applications of Automated Marking + * and Reading Symbols) standard used by the US Department of Defense. + * Input data can be of any length and supports the characters 0-9, A-Z, dash + * (-), full stop (.), space, dollar ($), slash (/), plus (+) and percent (%). + * A Modulo-43 check digit is calculated and added, and should not form part + * of the input data. + */ +public class Logmars extends Symbol { + + private static final String[] CODE39LM = { + "1113313111", "3113111131", "1133111131", "3133111111", "1113311131", + "3113311111", "1133311111", "1113113131", "3113113111", "1133113111", + "3111131131", "1131131131", "3131131111", "1111331131", "3111331111", + "1131331111", "1111133131", "3111133111", "1131133111", "1111333111", + "3111111331", "1131111331", "3131111311", "1111311331", "3111311311", + "1131311311", "1111113331", "3111113311", "1131113311", "1111313311", + "3311111131", "1331111131", "3331111111", "1311311131", "3311311111", + "1331311111", "1311113131", "3311113111", "1331113111", "1313131111", + "1313111311", "1311131311", "1113131311" + }; + + private static final char[] LOOKUP = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', + '%' + }; + + /** + * Ratio of wide bar width to narrow bar width. + */ + private double moduleWidthRatio = 3; + + /** + * Returns the ratio of wide bar width to narrow bar width. + * + * @return the ratio of wide bar width to narrow bar width + */ + public double getModuleWidthRatio() { + return moduleWidthRatio; + } + + /** + * Sets the ratio of wide bar width to narrow bar width. Valid values are usually + * between {@code 2} and {@code 3}. The default value is {@code 3}. + * + * @param moduleWidthRatio the ratio of wide bar width to narrow bar width + */ + public void setModuleWidthRatio(double moduleWidthRatio) { + this.moduleWidthRatio = moduleWidthRatio; + } + + @Override + protected double getModuleWidth(int originalWidth) { + if (originalWidth == 1) { + return 1; + } else { + return moduleWidthRatio; + } + } + + @Override + public boolean encode() { + + if (!(content.matches("[0-9A-Z\\. \\-$/+%]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + StringBuilder p = new StringBuilder(); + int l = content.length(); + int charval, counter = 0; + char thischar; + char checkDigit; + for (int i = 0; i < l; i++) { + thischar = content.charAt(i); + charval = positionOf(thischar, LOOKUP); + counter += charval; + p.append(CODE39LM[charval]); + } + + counter = counter % 43; + checkDigit = LOOKUP[counter]; + encodeInfo.append("Check Digit: ").append(checkDigit).append("\n"); + p.append(CODE39LM[counter]); + + readable = new StringBuilder(content).append(checkDigit); + pattern = new String[]{"1311313111" + p + "131131311"}; + rowCount = 1; + rowHeight = new int[]{-1}; + plotSymbol(); + return true; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/MaxiCode.java b/barcode/src/main/java/org/xbib/graphics/barcode/MaxiCode.java new file mode 100755 index 0000000..dc7ecb2 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/MaxiCode.java @@ -0,0 +1,890 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.Hexagon; +import org.xbib.graphics.barcode.util.ReedSolomon; +import java.awt.geom.Ellipse2D; +import java.util.Arrays; + +/** + * Implements MaxiCode according to ISO 16023:2000. + * MaxiCode employs a pattern of hexagons around a central 'bulls-eye' + * finder pattern. Encoding in several modes is supported, but encoding in + * Mode 2 and 3 require primary messages to be set. Input characters can be + * any from the ISO 8859-1 (Latin-1) character set. + * TODO: Add ECI functionality. + */ +public class MaxiCode extends Symbol { + + /** + * MaxiCode module sequence, from ISO/IEC 16023 Figure 5 (30 x 33 data grid). + */ + private static final int[] MAXICODE_GRID = { + 122, 121, 128, 127, 134, 133, 140, 139, 146, 145, 152, 151, 158, 157, 164, 163, 170, 169, 176, 175, 182, 181, 188, 187, 194, 193, 200, 199, 0, 0, + 124, 123, 130, 129, 136, 135, 142, 141, 148, 147, 154, 153, 160, 159, 166, 165, 172, 171, 178, 177, 184, 183, 190, 189, 196, 195, 202, 201, 817, 0, + 126, 125, 132, 131, 138, 137, 144, 143, 150, 149, 156, 155, 162, 161, 168, 167, 174, 173, 180, 179, 186, 185, 192, 191, 198, 197, 204, 203, 819, 818, + 284, 283, 278, 277, 272, 271, 266, 265, 260, 259, 254, 253, 248, 247, 242, 241, 236, 235, 230, 229, 224, 223, 218, 217, 212, 211, 206, 205, 820, 0, + 286, 285, 280, 279, 274, 273, 268, 267, 262, 261, 256, 255, 250, 249, 244, 243, 238, 237, 232, 231, 226, 225, 220, 219, 214, 213, 208, 207, 822, 821, + 288, 287, 282, 281, 276, 275, 270, 269, 264, 263, 258, 257, 252, 251, 246, 245, 240, 239, 234, 233, 228, 227, 222, 221, 216, 215, 210, 209, 823, 0, + 290, 289, 296, 295, 302, 301, 308, 307, 314, 313, 320, 319, 326, 325, 332, 331, 338, 337, 344, 343, 350, 349, 356, 355, 362, 361, 368, 367, 825, 824, + 292, 291, 298, 297, 304, 303, 310, 309, 316, 315, 322, 321, 328, 327, 334, 333, 340, 339, 346, 345, 352, 351, 358, 357, 364, 363, 370, 369, 826, 0, + 294, 293, 300, 299, 306, 305, 312, 311, 318, 317, 324, 323, 330, 329, 336, 335, 342, 341, 348, 347, 354, 353, 360, 359, 366, 365, 372, 371, 828, 827, + 410, 409, 404, 403, 398, 397, 392, 391, 80, 79, 0, 0, 14, 13, 38, 37, 3, 0, 45, 44, 110, 109, 386, 385, 380, 379, 374, 373, 829, 0, + 412, 411, 406, 405, 400, 399, 394, 393, 82, 81, 41, 0, 16, 15, 40, 39, 4, 0, 0, 46, 112, 111, 388, 387, 382, 381, 376, 375, 831, 830, + 414, 413, 408, 407, 402, 401, 396, 395, 84, 83, 42, 0, 0, 0, 0, 0, 6, 5, 48, 47, 114, 113, 390, 389, 384, 383, 378, 377, 832, 0, + 416, 415, 422, 421, 428, 427, 104, 103, 56, 55, 17, 0, 0, 0, 0, 0, 0, 0, 21, 20, 86, 85, 434, 433, 440, 439, 446, 445, 834, 833, + 418, 417, 424, 423, 430, 429, 106, 105, 58, 57, 0, 0, 0, 0, 0, 0, 0, 0, 23, 22, 88, 87, 436, 435, 442, 441, 448, 447, 835, 0, + 420, 419, 426, 425, 432, 431, 108, 107, 60, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 90, 89, 438, 437, 444, 443, 450, 449, 837, 836, + 482, 481, 476, 475, 470, 469, 49, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 54, 53, 464, 463, 458, 457, 452, 451, 838, 0, + 484, 483, 478, 477, 472, 471, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 466, 465, 460, 459, 454, 453, 840, 839, + 486, 485, 480, 479, 474, 473, 52, 51, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 43, 468, 467, 462, 461, 456, 455, 841, 0, + 488, 487, 494, 493, 500, 499, 98, 97, 62, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 92, 91, 506, 505, 512, 511, 518, 517, 843, 842, + 490, 489, 496, 495, 502, 501, 100, 99, 64, 63, 0, 0, 0, 0, 0, 0, 0, 0, 29, 28, 94, 93, 508, 507, 514, 513, 520, 519, 844, 0, + 492, 491, 498, 497, 504, 503, 102, 101, 66, 65, 18, 0, 0, 0, 0, 0, 0, 0, 19, 30, 96, 95, 510, 509, 516, 515, 522, 521, 846, 845, + 560, 559, 554, 553, 548, 547, 542, 541, 74, 73, 33, 0, 0, 0, 0, 0, 0, 11, 68, 67, 116, 115, 536, 535, 530, 529, 524, 523, 847, 0, + 562, 561, 556, 555, 550, 549, 544, 543, 76, 75, 0, 0, 8, 7, 36, 35, 12, 0, 70, 69, 118, 117, 538, 537, 532, 531, 526, 525, 849, 848, + 564, 563, 558, 557, 552, 551, 546, 545, 78, 77, 0, 34, 10, 9, 26, 25, 0, 0, 72, 71, 120, 119, 540, 539, 534, 533, 528, 527, 850, 0, + 566, 565, 572, 571, 578, 577, 584, 583, 590, 589, 596, 595, 602, 601, 608, 607, 614, 613, 620, 619, 626, 625, 632, 631, 638, 637, 644, 643, 852, 851, + 568, 567, 574, 573, 580, 579, 586, 585, 592, 591, 598, 597, 604, 603, 610, 609, 616, 615, 622, 621, 628, 627, 634, 633, 640, 639, 646, 645, 853, 0, + 570, 569, 576, 575, 582, 581, 588, 587, 594, 593, 600, 599, 606, 605, 612, 611, 618, 617, 624, 623, 630, 629, 636, 635, 642, 641, 648, 647, 855, 854, + 728, 727, 722, 721, 716, 715, 710, 709, 704, 703, 698, 697, 692, 691, 686, 685, 680, 679, 674, 673, 668, 667, 662, 661, 656, 655, 650, 649, 856, 0, + 730, 729, 724, 723, 718, 717, 712, 711, 706, 705, 700, 699, 694, 693, 688, 687, 682, 681, 676, 675, 670, 669, 664, 663, 658, 657, 652, 651, 858, 857, + 732, 731, 726, 725, 720, 719, 714, 713, 708, 707, 702, 701, 696, 695, 690, 689, 684, 683, 678, 677, 672, 671, 666, 665, 660, 659, 654, 653, 859, 0, + 734, 733, 740, 739, 746, 745, 752, 751, 758, 757, 764, 763, 770, 769, 776, 775, 782, 781, 788, 787, 794, 793, 800, 799, 806, 805, 812, 811, 861, 860, + 736, 735, 742, 741, 748, 747, 754, 753, 760, 759, 766, 765, 772, 771, 778, 777, 784, 783, 790, 789, 796, 795, 802, 801, 808, 807, 814, 813, 862, 0, + 738, 737, 744, 743, 750, 749, 756, 755, 762, 761, 768, 767, 774, 773, 780, 779, 786, 785, 792, 791, 798, 797, 804, 803, 810, 809, 816, 815, 864, 863 + }; + + /** + * ASCII character to Code Set mapping, from ISO/IEC 16023 Appendix A. + * 1 = Set A, 2 = Set B, 3 = Set C, 4 = Set D, 5 = Set E. + * 0 refers to special characters that fit into more than one set (e.g. GS). + */ + private static final int[] MAXICODE_SET = { + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, + 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 4, 5, 5, 5, 5, 5, 5, 4, 5, 3, 4, 3, 5, 5, 4, 4, 3, 3, 3, + 4, 3, 5, 4, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 + }; + + /** + * ASCII character to symbol value, from ISO/IEC 16023 Appendix A. + */ + private static final int[] MAXICODE_SYMBOL_CHAR = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 30, 28, 29, 30, 35, 32, 53, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 37, + 38, 39, 40, 41, 52, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 42, 43, 44, 45, 46, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 32, 54, 34, 35, 36, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 36, + 37, 37, 38, 39, 40, 41, 42, 43, 38, 44, 37, 39, 38, 45, 46, 40, 41, 39, 40, 41, + 42, 42, 47, 43, 44, 43, 44, 45, 45, 46, 47, 46, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, + 33, 34, 35, 36, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 33, 34, 35, 36 + }; + + private int mode; + private int structuredAppendPosition = 1; + private int structuredAppendTotal = 1; + private String primaryData = ""; + private int[] codewords; + private int[] source; + private final int[] set = new int[144]; + private final int[] character = new int[144]; + private final boolean[][] grid = new boolean[33][30]; + + /** + * Returns the primary message codewords for mode 2. + * + * @param postcode the postal code + * @param country the country code + * @param service the service code + * @return the primary message, as codewords + */ + private static int[] getMode2PrimaryCodewords(String postcode, int country, int service) { + for (int i = 0; i < postcode.length(); i++) { + if (postcode.charAt(i) < '0' || postcode.charAt(i) > '9') { + postcode = postcode.substring(0, i); + break; + } + } + int postcodeNum = Integer.parseInt(postcode); + int[] primary = new int[10]; + primary[0] = ((postcodeNum & 0x03) << 4) | 2; + primary[1] = ((postcodeNum & 0xfc) >> 2); + primary[2] = ((postcodeNum & 0x3f00) >> 8); + primary[3] = ((postcodeNum & 0xfc000) >> 14); + primary[4] = ((postcodeNum & 0x3f00000) >> 20); + primary[5] = ((postcodeNum & 0x3c000000) >> 26) | ((postcode.length() & 0x3) << 4); + primary[6] = ((postcode.length() & 0x3c) >> 2) | ((country & 0x3) << 4); + primary[7] = (country & 0xfc) >> 2; + primary[8] = ((country & 0x300) >> 8) | ((service & 0xf) << 2); + primary[9] = ((service & 0x3f0) >> 4); + + return primary; + } + + /** + * Returns the primary message codewords for mode 3. + * + * @param postcode the postal code + * @param country the country code + * @param service the service code + * @return the primary message, as codewords + */ + private static int[] getMode3PrimaryCodewords(String postcode, int country, int service) { + + int[] postcodeNums = new int[postcode.length()]; + + postcode = postcode.toUpperCase(); + for (int i = 0; i < postcodeNums.length; i++) { + postcodeNums[i] = postcode.charAt(i); + if (postcode.charAt(i) >= 'A' && postcode.charAt(i) <= 'Z') { + // (Capital) letters shifted to Code Set A values + postcodeNums[i] -= 64; + } + if (postcodeNums[i] == 27 || postcodeNums[i] == 31 || postcodeNums[i] == 33 || postcodeNums[i] >= 59) { + // Not a valid postal code character, use space instead + postcodeNums[i] = 32; + } + // Input characters lower than 27 (NUL - SUB) in postal code are interpreted as capital + // letters in Code Set A (e.g. LF becomes 'J') + } + + int[] primary = new int[10]; + primary[0] = ((postcodeNums[5] & 0x03) << 4) | 3; + primary[1] = ((postcodeNums[4] & 0x03) << 4) | ((postcodeNums[5] & 0x3c) >> 2); + primary[2] = ((postcodeNums[3] & 0x03) << 4) | ((postcodeNums[4] & 0x3c) >> 2); + primary[3] = ((postcodeNums[2] & 0x03) << 4) | ((postcodeNums[3] & 0x3c) >> 2); + primary[4] = ((postcodeNums[1] & 0x03) << 4) | ((postcodeNums[2] & 0x3c) >> 2); + primary[5] = ((postcodeNums[0] & 0x03) << 4) | ((postcodeNums[1] & 0x3c) >> 2); + primary[6] = ((postcodeNums[0] & 0x3c) >> 2) | ((country & 0x3) << 4); + primary[7] = (country & 0xfc) >> 2; + primary[8] = ((country & 0x300) >> 8) | ((service & 0xf) << 2); + primary[9] = ((service & 0x3f0) >> 4); + + return primary; + } + + /** + * Returns the error correction codewords for the specified data codewords. + * + * @param codewords the codewords that we need error correction codewords for + * @param ecclen the number of error correction codewords needed + * @return the error correction codewords for the specified data codewords + */ + private static int[] getErrorCorrection(int[] codewords, int ecclen) { + + ReedSolomon rs = new ReedSolomon(); + rs.init_gf(0x43); + rs.init_code(ecclen, 1); + rs.encode(codewords.length, codewords); + + int[] results = new int[ecclen]; + for (int i = 0; i < ecclen; i++) { + results[i] = rs.getResult(results.length - 1 - i); + } + + return results; + } + + /** + * Returns the MaxiCode mode being used. Only modes 2 to 6 are supported. + * + * @return the MaxiCode mode being used + */ + public int getMode() { + return mode; + } + + /** + * Sets the MaxiCode mode to use. Only modes 2 to 6 are supported. + * + * @param mode the MaxiCode mode to use + */ + public void setMode(int mode) { + if (mode < 2 || mode > 6) { + throw new IllegalArgumentException("Invalid MaxiCode mode: " + mode); + } + this.mode = mode; + } + + /** + * Returns the position of this MaxiCode symbol in a series of symbols using structured append. If this symbol is not part of + * such a series, this method will return 1. + * + * @return the position of this MaxiCode symbol in a series of symbols using structured append + */ + public int getStructuredAppendPosition() { + return structuredAppendPosition; + } + + /** + * If this MaxiCode symbol is part of a series of MaxiCode symbols appended in a structured format, this method sets the + * position of this symbol in the series. Valid values are 1 through 8 inclusive. + * + * @param position the position of this MaxiCode symbol in the structured append series + */ + public void setStructuredAppendPosition(int position) { + if (position < 1 || position > 8) { + throw new IllegalArgumentException("Invalid MaxiCode structured append position: " + position); + } + this.structuredAppendPosition = position; + } + + /** + * Returns the size of the series of MaxiCode symbols using structured append that this symbol is part of. If this symbol is + * not part of a structured append series, this method will return 1. + * + * @return size of the series that this symbol is part of + */ + public int getStructuredAppendTotal() { + return structuredAppendTotal; + } + + /** + * If this MaxiCode symbol is part of a series of MaxiCode symbols appended in a structured format, this method sets the total + * number of symbols in the series. Valid values are 1 through 8 inclusive. A value of 1 indicates that this symbol is not + * part of a structured append series. + * + * @param total the total number of MaxiCode symbols in the structured append series + */ + public void setStructuredAppendTotal(int total) { + if (total < 1 || total > 8) { + throw new IllegalArgumentException("Invalid MaxiCode structured append total: " + total); + } + this.structuredAppendTotal = total; + } + + /** + * Returns the primary data for this MaxiCode symbol. Should only be used for modes 2 and 3. + * + * @return the primary data for this MaxiCode symbol + */ + public String getPrimary() { + return primaryData; + } + + /** + * Sets the primary data. Should only be used for modes 2 and 3. Must conform to the following structure: + * + * + * + * + * + *
Expected primary data structure
CharactersMeaning
1-9Postal code data which can consist of up to 9 digits (for mode 2) or up to 6 + * alphanumeric characters (for mode 3). Remaining unused characters should be + * filled with the SPACE character (ASCII 32).
10-12Three-digit country code according to ISO-3166.
13-15Three digit service code. This depends on your parcel courier.
+ * + * @param primary the primary data + */ + public void setPrimary(String primary) { + primaryData = primary; + } + + @Override + public boolean encode() { + + // copy input data over into source + int sourcelen = content.length(); + source = new int[sourcelen]; + + eciProcess(); + + for (int i = 0; i < sourcelen; i++) { + source[i] = inputBytes[i] & 0xFF; + } + + // mode 2 -> mode 3 if postal code isn't strictly numeric + if (mode == 2) { + for (int i = 0; i < 10 && i < primaryData.length(); i++) { + if ((primaryData.charAt(i) < '0') || (primaryData.charAt(i) > '9')) { + mode = 3; + break; + } + } + } + + // initialize the set and character arrays + if (!processText()) { + errorMsg.append("Input data too long"); + return false; + } + + // start building the codeword array, starting with a copy of the character data + // insert primary message if this is a structured carrier message; insert mode otherwise + codewords = Arrays.copyOf(character, character.length); + if (mode == 2 || mode == 3) { + int[] primary = getPrimaryCodewords(); + if (primary == null) { + return false; + } + codewords = insert(codewords, 0, primary); + } else { + codewords = insert(codewords, 0, new int[]{mode}); + } + + // insert structured append flag if necessary + if (structuredAppendTotal > 1) { + + int[] flag = new int[2]; + flag[0] = 33; // padding + flag[1] = ((structuredAppendPosition - 1) << 3) | (structuredAppendTotal - 1); // position + total + + int index; + if (mode == 2 || mode == 3) { + index = 10; // first two data symbols in the secondary message + } else { + index = 1; // first two data symbols in the primary message (first symbol at index 0 isn't a data symbol) + } + + codewords = insert(codewords, index, flag); + } + + int secondaryMax, secondaryECMax; + if (mode == 5) { + // 68 data codewords, 56 error corrections in secondary message + secondaryMax = 68; + secondaryECMax = 56; + } else { + // 84 data codewords, 40 error corrections in secondary message + secondaryMax = 84; + secondaryECMax = 40; + } + + // truncate data codewords to maximum data space available + int totalMax = secondaryMax + 10; + if (codewords.length > totalMax) { + codewords = Arrays.copyOfRange(codewords, 0, totalMax); + } + + // insert primary error correction between primary message and secondary message (always EEC) + int[] primary = Arrays.copyOfRange(codewords, 0, 10); + int[] primaryCheck = getErrorCorrection(primary, 10); + codewords = insert(codewords, 10, primaryCheck); + + // calculate secondary error correction + int[] secondary = Arrays.copyOfRange(codewords, 20, codewords.length); + int[] secondaryOdd = new int[secondary.length / 2]; + int[] secondaryEven = new int[secondary.length / 2]; + for (int i = 0; i < secondary.length; i++) { + if ((i & 1) != 0) { // odd + secondaryOdd[(i - 1) / 2] = secondary[i]; + } else { // even + secondaryEven[i / 2] = secondary[i]; + } + } + int[] secondaryECOdd = getErrorCorrection(secondaryOdd, secondaryECMax / 2); + int[] secondaryECEven = getErrorCorrection(secondaryEven, secondaryECMax / 2); + + // add secondary error correction after secondary message + codewords = Arrays.copyOf(codewords, codewords.length + secondaryECOdd.length + secondaryECEven.length); + for (int i = 0; i < secondaryECOdd.length; i++) { + codewords[20 + secondaryMax + (2 * i) + 1] = secondaryECOdd[i]; + } + for (int i = 0; i < secondaryECEven.length; i++) { + codewords[20 + secondaryMax + (2 * i)] = secondaryECEven[i]; + } + + encodeInfo.append("Mode: ").append(mode).append("\n"); + encodeInfo.append("ECC Codewords: ").append(secondaryECMax).append("\n"); + encodeInfo.append("Codewords: "); + for (int codeword : codewords) { + encodeInfo.append(Integer.toString(codeword)).append(" "); + } + encodeInfo.append("\n"); + + // copy data into symbol grid + int[] bit_pattern = new int[7]; + for (int i = 0; i < 33; i++) { + for (int j = 0; j < 30; j++) { + + int block = (MAXICODE_GRID[(i * 30) + j] + 5) / 6; + int bit = (MAXICODE_GRID[(i * 30) + j] + 5) % 6; + + if (block != 0) { + + bit_pattern[0] = (codewords[block - 1] & 0x20) >> 5; + bit_pattern[1] = (codewords[block - 1] & 0x10) >> 4; + bit_pattern[2] = (codewords[block - 1] & 0x8) >> 3; + bit_pattern[3] = (codewords[block - 1] & 0x4) >> 2; + bit_pattern[4] = (codewords[block - 1] & 0x2) >> 1; + bit_pattern[5] = (codewords[block - 1] & 0x1); + + grid[i][j] = bit_pattern[bit] != 0; + } + } + } + + // add orientation markings + grid[0][28] = true; // top right filler + grid[0][29] = true; + grid[9][10] = true; // top left marker + grid[9][11] = true; + grid[10][11] = true; + grid[15][7] = true; // left hand marker + grid[16][8] = true; + grid[16][20] = true; // right hand marker + grid[17][20] = true; + grid[22][10] = true; // bottom left marker + grid[23][10] = true; + grid[22][17] = true; // bottom right marker + grid[23][17] = true; + + // the following is provided for compatibility, but the results are not useful + rowCount = 33; + readable = new StringBuilder(); + pattern = new String[33]; + rowHeight = new int[33]; + for (int i = 0; i < 33; i++) { + StringBuilder bin = new StringBuilder(30); + for (int j = 0; j < 30; j++) { + if (grid[i][j]) { + bin.append("1"); + } else { + bin.append("0"); + } + } + pattern[i] = bin2pat(bin.toString()); + rowHeight[i] = 1; + } + symbolHeight = 72; + symbolWidth = 74; + + plotSymbol(); + + return true; + } + + /** + * Extracts the postal code, country code and service code from the primary data and returns the corresponding primary message + * codewords. + * + * @return the primary message codewords + */ + private int[] getPrimaryCodewords() { + + assert mode == 2 || mode == 3; + + if (primaryData.length() != 15) { + errorMsg.append("Invalid Primary String"); + return null; + } + + for (int i = 9; i < 15; i++) { /* check that country code and service are numeric */ + if ((primaryData.charAt(i) < '0') || (primaryData.charAt(i) > '9')) { + errorMsg.append("Invalid Primary String"); + return null; + } + } + + String postcode; + if (mode == 2) { + postcode = primaryData.substring(0, 9); + int index = postcode.indexOf(' '); + if (index != -1) { + postcode = postcode.substring(0, index); + } + } else { + // if (mode == 3) + postcode = primaryData.substring(0, 6); + } + + int country = Integer.parseInt(primaryData.substring(9, 12)); + int service = Integer.parseInt(primaryData.substring(12, 15)); + + if (mode == 2) { + return getMode2PrimaryCodewords(postcode, country, service); + } else { // mode == 3 + return getMode3PrimaryCodewords(postcode, country, service); + } + } + + /** + * Formats text according to Appendix A, populating the {@link #set} and {@link #character} arrays. + * + * @return true if the content fits in this symbol and was formatted; false otherwise + */ + private boolean processText() { + + int length = content.length(); + int i, j, count, current_set; + + if (length > 138) { + return false; + } + + for (i = 0; i < 144; i++) { + set[i] = -1; + character[i] = 0; + } + + for (i = 0; i < length; i++) { + /* Look up characters in table from Appendix A - this gives + value and code set for most characters */ + set[i] = MAXICODE_SET[source[i]]; + character[i] = MAXICODE_SYMBOL_CHAR[source[i]]; + } + + // If a character can be represented in more than one code set, pick which version to use. + if (set[0] == 0) { + if (character[0] == 13) { + character[0] = 0; + } + set[0] = 1; + } + + for (i = 1; i < length; i++) { + if (set[i] == 0) { + /* Special character that can be represented in more than one code set. */ + if (character[i] == 13) { + /* Carriage Return */ + set[i] = bestSurroundingSet(i, length, 1, 5); + if (set[i] == 5) { + character[i] = 13; + } else { + character[i] = 0; + } + } else if (character[i] == 28) { + /* FS */ + set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5); + if (set[i] == 5) { + character[i] = 32; + } + } else if (character[i] == 29) { + /* GS */ + set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5); + if (set[i] == 5) { + character[i] = 33; + } + } else if (character[i] == 30) { + /* RS */ + set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5); + if (set[i] == 5) { + character[i] = 34; + } + } else if (character[i] == 32) { + /* Space */ + set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5); + if (set[i] == 1) { + character[i] = 32; + } else if (set[i] == 2) { + character[i] = 47; + } else { + character[i] = 59; + } + } else if (character[i] == 44) { + /* Comma */ + set[i] = bestSurroundingSet(i, length, 1, 2); + if (set[i] == 2) { + character[i] = 48; + } + } else if (character[i] == 46) { + /* Full Stop */ + set[i] = bestSurroundingSet(i, length, 1, 2); + if (set[i] == 2) { + character[i] = 49; + } + } else if (character[i] == 47) { + /* Slash */ + set[i] = bestSurroundingSet(i, length, 1, 2); + if (set[i] == 2) { + character[i] = 50; + } + } else if (character[i] == 58) { + /* Colon */ + set[i] = bestSurroundingSet(i, length, 1, 2); + if (set[i] == 2) { + character[i] = 51; + } + } + } + } + + for (i = length; i < set.length; i++) { + /* Add the padding */ + if (set[length - 1] == 2) { + set[i] = 2; + } else { + set[i] = 1; + } + character[i] = 33; + } + + /* Find candidates for number compression (not allowed in primary message in modes 2 and 3). */ + if (mode == 2 || mode == 3) { + j = 9; + } else { + j = 0; + } + count = 0; + for (i = j; i < 143; i++) { + if (set[i] == 1 && character[i] >= 48 && character[i] <= 57) { + /* Character is a number */ + count++; + } else { + count = 0; + } + if (count == 9) { + /* Nine digits in a row can be compressed */ + set[i] = 6; + set[i - 1] = 6; + set[i - 2] = 6; + set[i - 3] = 6; + set[i - 4] = 6; + set[i - 5] = 6; + set[i - 6] = 6; + set[i - 7] = 6; + set[i - 8] = 6; + count = 0; + } + } + + /* Add shift and latch characters */ + current_set = 1; + i = 0; + do { + if ((set[i] != current_set) && (set[i] != 6)) { + switch (set[i]) { + case 1: + if (i + 1 < set.length && set[i + 1] == 1) { + if (i + 2 < set.length && set[i + 2] == 1) { + if (i + 3 < set.length && set[i + 3] == 1) { + /* Latch A */ + insert(i, 63); + current_set = 1; + length++; + i += 3; + } else { + /* 3 Shift A */ + insert(i, 57); + length++; + i += 2; + } + } else { + /* 2 Shift A */ + insert(i, 56); + length++; + i++; + } + } else { + /* Shift A */ + insert(i, 59); + length++; + } + break; + case 2: + if (i + 1 < set.length && set[i + 1] == 2) { + /* Latch B */ + insert(i, 63); + current_set = 2; + length++; + i++; + } else { + /* Shift B */ + insert(i, 59); + length++; + } + break; + case 3: + if (i + 3 < set.length && set[i + 1] == 3 && set[i + 2] == 3 && set[i + 3] == 3) { + /* Lock In C */ + insert(i, 60); + insert(i, 60); + current_set = 3; + length++; + i += 3; + } else { + /* Shift C */ + insert(i, 60); + length++; + } + break; + case 4: + if (i + 3 < set.length && set[i + 1] == 4 && set[i + 2] == 4 && set[i + 3] == 4) { + /* Lock In D */ + insert(i, 61); + insert(i, 61); + current_set = 4; + length++; + i += 3; + } else { + /* Shift D */ + insert(i, 61); + length++; + } + break; + case 5: + if (i + 3 < set.length && set[i + 1] == 5 && set[i + 2] == 5 && set[i + 3] == 5) { + /* Lock In E */ + insert(i, 62); + insert(i, 62); + current_set = 5; + length++; + i += 3; + } else { + /* Shift E */ + insert(i, 62); + length++; + } + break; + default: + throw new IllegalStateException("Unexpected set " + set[i] + " at index " + i + "."); + } + i++; + } + i++; + } while (i < set.length); + + /* Number compression has not been forgotten! It's handled below. */ + i = 0; + do { + if (set[i] == 6) { + /* Number compression */ + int value = 0; + for (j = 0; j < 9; j++) { + value *= 10; + value += (character[i + j] - '0'); + } + character[i] = 31; /* NS */ + character[i + 1] = (value & 0x3f000000) >> 24; + character[i + 2] = (value & 0xfc0000) >> 18; + character[i + 3] = (value & 0x3f000) >> 12; + character[i + 4] = (value & 0xfc0) >> 6; + character[i + 5] = (value & 0x3f); + i += 6; + for (j = i; j < 140; j++) { + set[j] = set[j + 3]; + character[j] = character[j + 3]; + } + length -= 3; + } else { + i++; + } + } while (i < set.length); + + /* Inject ECI codes to beginning of data, according to Table 3 */ + if (eciMode != 3) { + insert(0, 27); // ECI + + if ((eciMode >= 0) && (eciMode <= 31)) { + insert(1, eciMode & 0x1F); + length += 2; + } + + if ((eciMode >= 32) && (eciMode <= 1023)) { + insert(1, 0x20 + (eciMode >> 6)); + insert(2, eciMode & 0x3F); + length += 3; + } + + if ((eciMode >= 1024) && (eciMode <= 32767)) { + insert(1, 0x30 + (eciMode >> 12)); + insert(2, (eciMode >> 6) & 0x3F); + insert(3, eciMode & 0x3F); + length += 4; + } + + if ((eciMode >= 32768) && (eciMode <= 999999)) { + insert(1, 0x38 + (eciMode >> 18)); + insert(2, (eciMode >> 12) & 0x3F); + insert(3, (eciMode >> 6) & 0x3F); + insert(4, eciMode & 0x3F); + length += 5; + } + } + + /* Make sure we haven't exceeded the maximum data length. */ + return ((mode != 2 && mode != 3) || length <= 84) && ((mode != 4 && mode != 6) || length <= 93) && (mode != 5 || length <= 77); + } + + /** + * Guesses the best set to use at the specified index by looking at the surrounding sets. In general, characters in + * lower-numbered sets are more common, so we choose them if we can. If no good surrounding sets can be found, the default + * value returned is the first value from the valid set. + * + * @param index the current index + * @param length the maximum length to look at + * @param valid the valid sets for this index + * @return the best set to use at the specified index + */ + private int bestSurroundingSet(int index, int length, int... valid) { + int option1 = set[index - 1]; + if (index + 1 < length) { + // we have two options to check + int option2 = set[index + 1]; + if (contains(valid, option1) && contains(valid, option2)) { + return Math.min(option1, option2); + } else if (contains(valid, option1)) { + return option1; + } else if (contains(valid, option2)) { + return option2; + } else { + return valid[0]; + } + } else { + // we only have one option to check + if (contains(valid, option1)) { + return option1; + } else { + return valid[0]; + } + } + } + + /** + * Moves everything up so that the specified shift or latch character can be inserted. + * + * @param position the position beyond which everything needs to be shifted + * @param c the latch or shift character to insert at the specified position, after everything has been shifted + */ + private void insert(int position, int c) { + for (int i = 143; i > position; i--) { + set[i] = set[i - 1]; + character[i] = character[i - 1]; + } + character[position] = c; + } + + @Override + protected void plotSymbol() { + + // hexagons + for (int row = 0; row < 33; row++) { + for (int col = 0; col < 30; col++) { + if (grid[row][col]) { + double x = (2.46 * col) + 1.23; + if ((row & 1) != 0) { + x += 1.23; + } + double y = (2.135 * row) + 1.43; + getHexagons().add(new Hexagon(x, y)); + } + } + } + + // circles + double[] radii = {10.85, 8.97, 7.10, 5.22, 3.31, 1.43}; + for (double aRadii : radii) { + Ellipse2D.Double circle = new Ellipse2D.Double(); + circle.setFrameFromCenter(35.76, 35.60, 35.76 + aRadii, 35.60 + aRadii); + getTarget().add(circle); + } + } + + @Override + protected int[] getCodewords() { + return codewords; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/MicroQrCode.java b/barcode/src/main/java/org/xbib/graphics/barcode/MicroQrCode.java new file mode 100755 index 0000000..a66698c --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/MicroQrCode.java @@ -0,0 +1,1593 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; +import java.io.UnsupportedEncodingException; + +/** + * Implements Micro QR Code + * According to ISO/IEC 18004:2006 + * A miniature version of the QR Code symbol for short messages. + * QR Code symbols can encode characters in the Latin-1 set and Kanji + * characters which are members of the Shift-JIS encoding scheme. + */ +public class MicroQrCode extends Symbol { + /* Table 5 - Encoding/Decoding table for Alphanumeric mode */ + private static final char[] RHODIUM = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', + '.', '/', ':' + }; + private static final int[] QR_ANNEX_C1 = { + /* Micro QR Code format information */ + 0x4445, 0x4172, 0x4e2b, 0x4b1c, 0x55ae, 0x5099, 0x5fc0, 0x5af7, 0x6793, + 0x62a4, 0x6dfd, 0x68ca, 0x7678, 0x734f, 0x7c16, 0x7921, 0x06de, 0x03e9, + 0x0cb0, 0x0987, 0x1735, 0x1202, 0x1d5b, 0x186c, 0x2508, 0x203f, 0x2f66, + 0x2a51, 0x34e3, 0x31d4, 0x3e8d, 0x3bba + }; + private static final int[] MICRO_QR_SIZES = { + 11, 13, 15, 17 + }; + private qrMode[] inputMode; + private StringBuilder binary; + private int[] binaryCount = new int[4]; + private int[] grid; + private int[] eval; + private int preferredVersion; + private EccMode preferredEccLevel = EccMode.L; + + /** + * Sets the preferred symbol size. This value may be ignored if the + * data string is too large to fit into the specified symbol. Input + * values correspond to symbol sizes as shown in the following table. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Range of Micro QR symbol sizes
InputVersionSymbol Size
1M111 x 11
2M213 x 13
3M315 x 15
4M417 x 17
+ * + * @param version Symbol size + */ + public void setPreferredVersion(int version) { + preferredVersion = version; + } + + /** + * Set the amount of symbol space allocated to error correction. + * Levels are predefined according to the following table: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Micro QR Error correction levels
ECC LevelError Correction CapacityRecovery Capacity
L (default)Approx 20% of symbolApprox 7%
MApprox 37% of symbolApprox 15%
QApprox 55% of symbolApprox 25%
HApprox 65% of symbolApprox 30%
+ * + * @param eccMode Error correction level + */ + public void setEccMode(EccMode eccMode) { + preferredEccLevel = eccMode; + } + + @Override + public boolean encode() { + int i, j, size; + boolean[] version_valid = new boolean[4]; + int n_count, a_count; + EccMode ecc_level; + int version, autoversion; + int bitmask; + int format, format_full; + StringBuilder bin; + boolean byteModeUsed; + boolean alphanumModeUsed; + boolean kanjiModeUsed; + + if (content.length() > 35) { + errorMsg.append("Input data too long"); + return false; + } + + if (!inputCharCheck()) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + for (i = 0; i < 4; i++) { + version_valid[i] = true; + } + + inputMode = new qrMode[40]; + selectEncodingMode(); + + n_count = 0; + a_count = 0; + for (i = 0; i < content.length(); i++) { + if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) { + n_count++; + } + if (isAlphanumeric(content.charAt(i))) { + a_count++; + } + } + + if (a_count == content.length()) { + /* All data can be encoded in Alphanumeric mode */ + for (i = 0; i < content.length(); i++) { + inputMode[i] = qrMode.ALPHANUM; + } + } + + if (n_count == content.length()) { + /* All data can be encoded in Numeric mode */ + for (i = 0; i < content.length(); i++) { + inputMode[i] = qrMode.NUMERIC; + } + } + + byteModeUsed = false; + alphanumModeUsed = false; + kanjiModeUsed = false; + + for (i = 0; i < content.length(); i++) { + if (inputMode[i] == qrMode.BINARY) { + byteModeUsed = true; + } + + if (inputMode[i] == qrMode.ALPHANUM) { + alphanumModeUsed = true; + } + + if (inputMode[i] == qrMode.KANJI) { + kanjiModeUsed = true; + } + } + + getBinaryLength(); + + /* Eliminate possivle versions depending on type of content */ + if (byteModeUsed) { + version_valid[0] = false; + version_valid[1] = false; + } + + if (alphanumModeUsed) { + version_valid[0] = false; + } + + if (kanjiModeUsed) { + version_valid[0] = false; + version_valid[1] = false; + } + + /* Eliminate possible versions depending on length of binary data */ + if (binaryCount[0] > 20) { + version_valid[0] = false; + } + if (binaryCount[1] > 40) { + version_valid[1] = false; + } + if (binaryCount[2] > 84) { + version_valid[2] = false; + } + if (binaryCount[3] > 128) { + errorMsg.append("Input data too long"); + return false; + } + + /* Eliminate possible versions depending on error correction level specified */ + ecc_level = preferredEccLevel; + + if (ecc_level == EccMode.H) { + errorMsg.append("Error correction level H not available"); + return false; + } + + if (ecc_level == EccMode.Q) { + version_valid[0] = false; + version_valid[1] = false; + version_valid[2] = false; + if (binaryCount[3] > 80) { + errorMsg.append("Input data too long"); + return false; + } + } + + if (ecc_level == EccMode.M) { + version_valid[0] = false; + if (binaryCount[1] > 32) { + version_valid[1] = false; + } + if (binaryCount[2] > 68) { + version_valid[2] = false; + } + if (binaryCount[3] > 112) { + errorMsg.append("Input data too long"); + return false; + } + } + + autoversion = 3; + if (version_valid[2]) { + autoversion = 2; + } + if (version_valid[1]) { + autoversion = 1; + } + if (version_valid[0]) { + autoversion = 0; + } + + version = autoversion; + /* Get version from user */ + if ((preferredVersion >= 1) && (preferredVersion <= 4)) { + if (preferredVersion >= autoversion) { + version = preferredVersion; + } + } + + /* If there is enough unused space then increase the error correction level */ + if (version == 3) { + if (binaryCount[3] <= 112) { + ecc_level = EccMode.M; + } + if (binaryCount[3] <= 80) { + ecc_level = EccMode.Q; + } + } + + if (version == 2 && binaryCount[2] <= 68) { + ecc_level = EccMode.M; + } + + if (version == 1 && binaryCount[1] <= 32) { + ecc_level = EccMode.M; + } + + binary = new StringBuilder(); + generateBinary(version); + if (binary.length() > 128) { + errorMsg.append("Input data too long"); + return false; + } + + switch (version) { + case 0: + generateM1Symbol(); + encodeInfo.append("Version: M1\n"); + break; + case 1: + generateM2Symbol(ecc_level); + encodeInfo.append("Version: M2\n"); + encodeInfo.append("ECC Level: ").append(levelToLetter(ecc_level)).append("\n"); + break; + case 2: + generateM3Symbol(ecc_level); + encodeInfo.append("Version: M3\n"); + encodeInfo.append("ECC Level: ").append(levelToLetter(ecc_level)).append("\n"); + break; + case 3: + generateM4Symbol(ecc_level); + encodeInfo.append("Version: M4\n"); + encodeInfo.append("ECC Level: ").append(levelToLetter(ecc_level)).append("\n"); + break; + } + + size = MICRO_QR_SIZES[version]; + + grid = new int[size * size]; + + for (i = 0; i < size; i++) { + for (j = 0; j < size; j++) { + grid[(i * size) + j] = 0; + } + } + + setupBitGrid(size); + populateBitGrid(size); + bitmask = applyBitmask(size); + + encodeInfo.append("Mask Pattern: ").append(Integer.toBinaryString(bitmask)).append("\n"); + + /* Add format data */ + format = 0; + switch (version) { + case 1: + switch (ecc_level) { + case L: + format = 1; + break; + case M: + format = 2; + break; + } + break; + case 2: + switch (ecc_level) { + case L: + format = 3; + break; + case M: + format = 4; + break; + } + break; + case 3: + switch (ecc_level) { + case L: + format = 5; + break; + case M: + format = 6; + break; + case Q: + format = 7; + break; + } + break; + } + + format_full = QR_ANNEX_C1[(format << 2) + bitmask]; + + if ((format_full & 0x4000) != 0) { + grid[(8 * size) + 1] += 0x01; + } + if ((format_full & 0x2000) != 0) { + grid[(8 * size) + 2] += 0x01; + } + if ((format_full & 0x1000) != 0) { + grid[(8 * size) + 3] += 0x01; + } + if ((format_full & 0x800) != 0) { + grid[(8 * size) + 4] += 0x01; + } + if ((format_full & 0x400) != 0) { + grid[(8 * size) + 5] += 0x01; + } + if ((format_full & 0x200) != 0) { + grid[(8 * size) + 6] += 0x01; + } + if ((format_full & 0x100) != 0) { + grid[(8 * size) + 7] += 0x01; + } + if ((format_full & 0x80) != 0) { + grid[(8 * size) + 8] += 0x01; + } + if ((format_full & 0x40) != 0) { + grid[(7 * size) + 8] += 0x01; + } + if ((format_full & 0x20) != 0) { + grid[(6 * size) + 8] += 0x01; + } + if ((format_full & 0x10) != 0) { + grid[(5 * size) + 8] += 0x01; + } + if ((format_full & 0x08) != 0) { + grid[(4 * size) + 8] += 0x01; + } + if ((format_full & 0x04) != 0) { + grid[(3 * size) + 8] += 0x01; + } + if ((format_full & 0x02) != 0) { + grid[(2 * size) + 8] += 0x01; + } + if ((format_full & 0x01) != 0) { + grid[(size) + 8] += 0x01; + } + + readable = new StringBuilder(); + pattern = new String[size]; + rowCount = size; + rowHeight = new int[size]; + for (i = 0; i < size; i++) { + bin = new StringBuilder(); + for (j = 0; j < size; j++) { + if ((grid[(i * size) + j] & 0x01) != 0) { + bin.append("1"); + } else { + bin.append("0"); + } + } + pattern[i] = bin2pat(bin.toString()); + rowHeight[i] = 1; + } + + plotSymbol(); + return true; + } + + private boolean inputCharCheck() { + int qmarkBefore, qmarkAfter; + int i; + byte[] temp; + + /* Check that input includes valid characters */ + + if (content.matches("[\u0000-\u00FF]+")) { + /* All characters in ISO 8859-1 */ + return true; + } + + /* Otherwise check for Shift-JIS characters */ + qmarkBefore = 0; + for (i = 0; i < content.length(); i++) { + if (content.charAt(i) == '?') { + qmarkBefore++; + } + } + + try { + temp = content.getBytes("SJIS"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Character encoding error"); + return false; + } + + qmarkAfter = 0; + for (i = 0; i < temp.length; i++) { + if (temp[i] == '?') { + qmarkAfter++; + } + } + + /* If these values are the same, conversion was sucessful */ + return (qmarkBefore == qmarkAfter); + } + + private char levelToLetter(EccMode ecc_mode) { + switch (ecc_mode) { + case L: + return 'L'; + case M: + return 'M'; + case Q: + return 'Q'; + case H: + return 'H'; + default: + return ' '; + } + } + + private void selectEncodingMode() { + int i, j; + int mlen; + int length = content.length(); + + for (i = 0; i < length; i++) { + if (content.charAt(i) > 0xff) { + inputMode[i] = qrMode.KANJI; + } else { + inputMode[i] = qrMode.BINARY; + if (isAlphanumeric(content.charAt(i))) { + inputMode[i] = qrMode.ALPHANUM; + } + if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) { + inputMode[i] = qrMode.NUMERIC; + } + } + } + + /* If less than 6 numeric digits together then don't use numeric mode */ + for (i = 0; i < length; i++) { + if (inputMode[i] == qrMode.NUMERIC) { + if (((i != 0) && (inputMode[i - 1] != qrMode.NUMERIC)) + || (i == 0)) { + mlen = 0; + while (((mlen + i) < length) + && (inputMode[mlen + i] == qrMode.NUMERIC)) { + mlen++; + } + if (mlen < 6) { + for (j = 0; j < mlen; j++) { + inputMode[i + j] = qrMode.ALPHANUM; + } + } + } + } + } + + /* If less than 4 alphanumeric characters together then don't use alphanumeric mode */ + for (i = 0; i < length; i++) { + if (inputMode[i] == qrMode.ALPHANUM) { + if (((i != 0) && (inputMode[i - 1] != qrMode.ALPHANUM)) + || (i == 0)) { + mlen = 0; + while (((mlen + i) < length) + && (inputMode[mlen + i] == qrMode.ALPHANUM)) { + mlen++; + } + if (mlen < 6) { + for (j = 0; j < mlen; j++) { + inputMode[i + j] = qrMode.BINARY; + } + } + } + } + } + } + + private boolean isAlphanumeric(char cglyph) { + /* Returns true if input glyph is in the Alphanumeric set */ + boolean retval = false; + + if ((cglyph >= '0') && (cglyph <= '9')) { + retval = true; + } + if ((cglyph >= 'A') && (cglyph <= 'Z')) { + retval = true; + } + switch (cglyph) { + case ' ': + case '$': + case '%': + case '*': + case '+': + case '-': + case '.': + case '/': + case ':': + retval = true; + break; + } + + return retval; + } + + private String toBinary(int data, int h) { + StringBuilder argument = new StringBuilder(); + for (; + (h != 0); h >>= 1) { + if ((data & h) != 0) { + argument.append("1"); + } else { + argument.append("0"); + } + } + return argument.toString(); + } + + private void getBinaryLength() { + int i; + qrMode currentMode = qrMode.NULL; + int blockLength; + + /* Always include a terminator */ + for (i = 0; i < 4; i++) { + binaryCount[i] = 0; + } + + for (i = 0; i < content.length(); i++) { + if (currentMode != inputMode[i]) { + + blockLength = 0; + do { + blockLength++; + } while (((i + blockLength) < content.length()) + && (inputMode[i + blockLength] == inputMode[i])); + + switch (inputMode[i]) { + case KANJI: + binaryCount[2] += 5 + (blockLength * 13); + binaryCount[3] += 7 + (blockLength * 13); + + break; + case BINARY: + binaryCount[2] += 6 + (blockLength * 8); + binaryCount[3] += 8 + (blockLength * 8); + break; + case ALPHANUM: + int alphaLength; + + if ((blockLength % 2) == 1) { + /* Odd length block */ + alphaLength = ((blockLength - 1) / 2) * 11; + alphaLength += 6; + } else { + /* Even length block */ + alphaLength = (blockLength / 2) * 11; + } + + binaryCount[1] += 4 + alphaLength; + binaryCount[2] += 6 + alphaLength; + binaryCount[3] += 8 + alphaLength; + break; + case NUMERIC: + int numLength; + + switch (blockLength % 3) { + case 1: + /* one digit left over */ + numLength = ((blockLength - 1) / 3) * 10; + numLength += 4; + break; + case 2: + /* two digits left over */ + numLength = ((blockLength - 2) / 3) * 10; + numLength += 7; + break; + default: + /* blockLength is a multiple of 3 */ + numLength = (blockLength / 3) * 10; + break; + } + + binaryCount[0] += 3 + numLength; + binaryCount[1] += 5 + numLength; + binaryCount[2] += 7 + numLength; + binaryCount[3] += 9 + numLength; + break; + } + currentMode = inputMode[i]; + } + } + + /* Add terminator */ + if (binaryCount[1] < 37) { + binaryCount[1] += 5; + } + + if (binaryCount[2] < 81) { + binaryCount[2] += 7; + } + + if (binaryCount[3] < 125) { + binaryCount[3] += 9; + } + } + + private void generateBinary(int version) { + int position = 0; + int blockLength, i; + qrMode data_block; + int msb, lsb, prod, jis; + String oneChar; + byte[] jisBytes; + int count, first, second, third; + + encodeInfo.append("Encoding: "); + + do { + data_block = inputMode[position]; + blockLength = 0; + do { + blockLength++; + } while (((blockLength + position) < content.length()) + && (inputMode[position + blockLength] == data_block)); + + switch (data_block) { + case KANJI: + /* Kanji mode */ + /* Mode indicator */ + switch (version) { + case 2: + binary.append("11"); + break; + case 3: + binary.append("011"); + break; + } + + /* Character count indicator */ + binary.append(toBinary(blockLength, 1 << version)); /* version = 2..3 */ + + encodeInfo.append("KANJ (").append(Integer.toString(blockLength)).append(") "); + + /* Character representation */ + for (i = 0; i < blockLength; i++) { + oneChar = ""; + oneChar += content.charAt(position + i); + + /* Convert Unicode input to Shift-JIS */ + try { + jisBytes = oneChar.getBytes("SJIS"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Character encoding error"); + return; + } + + jis = ((jisBytes[0] & 0xFF) << 8) + (jisBytes[1] & 0xFF); + + if (jis > 0x9fff) { + jis -= 0xc140; + } else { + jis -= 0x8140; + } + msb = (jis & 0xff00) >> 8; + lsb = (jis & 0xff); + prod = (msb * 0xc0) + lsb; + + binary.append(toBinary(prod, 0x1000)); + + encodeInfo.append(Integer.toString(prod)).append(" "); + } + + break; + case BINARY: + /* Byte mode */ + /* Mode indicator */ + switch (version) { + case 2: + binary.append("10"); + break; + case 3: + binary.append("010"); + break; + } + + /* Character count indicator */ + binary.append(toBinary(blockLength, 2 << version)); /* version = 2..3 */ + + encodeInfo.append("BYTE (").append(Integer.toString(blockLength)).append(") "); + + /* Character representation */ + for (i = 0; i < blockLength; i++) { + int lbyte = content.charAt(position + i); + + binary.append(toBinary(lbyte, 0x80)); + + encodeInfo.append(Integer.toString(lbyte)).append(" "); + } + + break; + case ALPHANUM: + /* Alphanumeric mode */ + /* Mode indicator */ + switch (version) { + case 1: + binary.append("1"); + break; + case 2: + binary.append("01"); + break; + case 3: + binary.append("001"); + break; + } + + /* Character count indicator */ + binary.append(toBinary(blockLength, 2 << version)); /* version = 1..3 */ + + encodeInfo.append("ALPH (").append(Integer.toString(blockLength)).append(") "); + + /* Character representation */ + i = 0; + while (i < blockLength) { + first = positionOf(content.charAt(position + i), RHODIUM); + count = 1; + prod = first; + + if ((i + 1) < blockLength) { + if (inputMode[position + i + 1] == qrMode.ALPHANUM) { + second = positionOf(content.charAt(position + i + 1), RHODIUM); + count = 2; + prod = (first * 45) + second; + } + } + + binary.append(toBinary(prod, 1 << (5 * count))); /* count = 1..2 */ + + encodeInfo.append(Integer.toString(prod)).append(" "); + + i += 2; + } + + break; + case NUMERIC: + /* Numeric mode */ + /* Mode indicator */ + switch (version) { + case 1: + binary.append("0"); + break; + case 2: + binary.append("00"); + break; + case 3: + binary.append("000"); + break; + } + + /* Character count indicator */ + binary.append(toBinary(blockLength, 4 << version)); /* version = 0..3 */ + + encodeInfo.append("NUMB (").append(Integer.toString(blockLength)).append(") "); + + /* Character representation */ + i = 0; + while (i < blockLength) { + first = Character.getNumericValue(content.charAt(position + i)); + count = 1; + prod = first; + + if ((i + 1) < blockLength) { + if (inputMode[position + i + 1] == qrMode.NUMERIC) { + second = Character.getNumericValue(content.charAt(position + i + 1)); + count = 2; + prod = (prod * 10) + second; + } + } + + if ((i + 2) < blockLength) { + if (inputMode[position + i + 2] == qrMode.NUMERIC) { + third = Character.getNumericValue(content.charAt(position + i + 2)); + count = 3; + prod = (prod * 10) + third; + } + } + + binary.append(toBinary(prod, 1 << (3 * count))); /* count = 1..3 */ + + encodeInfo.append(Integer.toString(prod)).append(" "); + + i += 3; + } + break; + } + + position += blockLength; + } while (position < content.length() - 1); + + /* Add terminator */ + switch (version) { + case 0: + binary.append("000"); + break; + case 1: + if (binary.length() < 37) { + binary.append("00000"); + } + break; + case 2: + if (binary.length() < 81) { + binary.append("0000000"); + } + break; + case 3: + if (binary.length() < 125) { + binary.append("000000000"); + } + break; + } + + encodeInfo.append("\n"); + } + + private void generateM1Symbol() { + int i, latch; + int bits_total, bits_left, remainder; + int data_codewords, ecc_codewords; + int[] data_blocks = new int[4]; + int[] ecc_blocks = new int[3]; + ReedSolomon rs = new ReedSolomon(); + + bits_total = 20; + latch = 0; + + /* Manage last (4-bit) block */ + bits_left = bits_total - binary.length(); + if (bits_left <= 4) { + for (i = 0; i < bits_left; i++) { + binary.append("0"); + } + latch = 1; + } + + if (latch == 0) { + /* Complete current byte */ + remainder = 8 - (binary.length() % 8); + if (remainder == 8) { + remainder = 0; + } + for (i = 0; i < remainder; i++) { + binary.append("0"); + } + + /* Add padding */ + bits_left = bits_total - binary.length(); + if (bits_left > 4) { + remainder = (bits_left - 4) / 8; + for (i = 0; i < remainder; i++) { + if ((i & 1) != 0) { + binary.append("00010001"); + } else { + binary.append("11101100"); + } + } + } + binary.append("0000"); + } + + data_codewords = 3; + ecc_codewords = 2; + + /* Copy data into codewords */ + for (i = 0; i < (data_codewords - 1); i++) { + data_blocks[i] = 0; + if (binary.charAt(i * 8) == '1') { + data_blocks[i] += 0x80; + } + if (binary.charAt((i * 8) + 1) == '1') { + data_blocks[i] += 0x40; + } + if (binary.charAt((i * 8) + 2) == '1') { + data_blocks[i] += 0x20; + } + if (binary.charAt((i * 8) + 3) == '1') { + data_blocks[i] += 0x10; + } + if (binary.charAt((i * 8) + 4) == '1') { + data_blocks[i] += 0x08; + } + if (binary.charAt((i * 8) + 5) == '1') { + data_blocks[i] += 0x04; + } + if (binary.charAt((i * 8) + 6) == '1') { + data_blocks[i] += 0x02; + } + if (binary.charAt((i * 8) + 7) == '1') { + data_blocks[i] += 0x01; + } + } + data_blocks[2] = 0; + if (binary.charAt(16) == '1') { + data_blocks[2] += 0x08; + } + if (binary.charAt(17) == '1') { + data_blocks[2] += 0x04; + } + if (binary.charAt(18) == '1') { + data_blocks[2] += 0x02; + } + if (binary.charAt(19) == '1') { + data_blocks[2] += 0x01; + } + + encodeInfo.append("Codewords: "); + + for (i = 0; i < data_codewords; i++) { + encodeInfo.append(Integer.toString(data_blocks[i])).append(" "); + } + encodeInfo.append("\n"); + + /* Calculate Reed-Solomon error codewords */ + rs.init_gf(0x11d); + rs.init_code(ecc_codewords, 0); + rs.encode(data_codewords, data_blocks); + for (i = 0; i < ecc_codewords; i++) { + ecc_blocks[i] = rs.getResult(i); + } + + /* Add Reed-Solomon codewords to binary data */ + for (i = 0; i < ecc_codewords; i++) { + binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80)); + } + } + + private void generateM2Symbol(EccMode ecc_mode) { + int i; + int bits_total, bits_left, remainder; + int data_codewords, ecc_codewords; + int[] data_blocks = new int[6]; + int[] ecc_blocks = new int[7]; + ReedSolomon rs = new ReedSolomon(); + + bits_total = 40; // ecc_mode == EccMode.L + if (ecc_mode == EccMode.M) { + bits_total = 32; + } + + /* Complete current byte */ + remainder = 8 - (binary.length() % 8); + if (remainder == 8) { + remainder = 0; + } + for (i = 0; i < remainder; i++) { + binary.append("0"); + } + + /* Add padding */ + bits_left = bits_total - binary.length(); + remainder = bits_left / 8; + for (i = 0; i < remainder; i++) { + if ((i & 1) != 0) { + binary.append("00010001"); + } else { + binary.append("11101100"); + } + } + + data_codewords = 5; + ecc_codewords = 5; // ecc_mode == EccMode.L + if (ecc_mode == EccMode.M) { + data_codewords = 4; + ecc_codewords = 6; + } + + /* Copy data into codewords */ + for (i = 0; i < data_codewords; i++) { + data_blocks[i] = 0; + if (binary.charAt(i * 8) == '1') { + data_blocks[i] += 0x80; + } + if (binary.charAt((i * 8) + 1) == '1') { + data_blocks[i] += 0x40; + } + if (binary.charAt((i * 8) + 2) == '1') { + data_blocks[i] += 0x20; + } + if (binary.charAt((i * 8) + 3) == '1') { + data_blocks[i] += 0x10; + } + if (binary.charAt((i * 8) + 4) == '1') { + data_blocks[i] += 0x08; + } + if (binary.charAt((i * 8) + 5) == '1') { + data_blocks[i] += 0x04; + } + if (binary.charAt((i * 8) + 6) == '1') { + data_blocks[i] += 0x02; + } + if (binary.charAt((i * 8) + 7) == '1') { + data_blocks[i] += 0x01; + } + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < data_codewords; i++) { + encodeInfo.append(Integer.toString(data_blocks[i])).append(" "); + } + encodeInfo.append("\n"); + + /* Calculate Reed-Solomon error codewords */ + rs.init_gf(0x11d); + rs.init_code(ecc_codewords, 0); + rs.encode(data_codewords, data_blocks); + for (i = 0; i < ecc_codewords; i++) { + ecc_blocks[i] = rs.getResult(i); + } + + /* Add Reed-Solomon codewords to binary data */ + for (i = 0; i < ecc_codewords; i++) { + binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80)); + } + } + + private void generateM3Symbol(EccMode ecc_mode) { + int i, latch; + int bits_total, bits_left, remainder; + int data_codewords, ecc_codewords; + int[] data_blocks = new int[12]; + int[] ecc_blocks = new int[12]; + ReedSolomon rs = new ReedSolomon(); + + latch = 0; + + bits_total = 84; // ecc_mode == EccMode.L + if (ecc_mode == EccMode.M) { + bits_total = 68; + } + + /* Manage last (4-bit) block */ + bits_left = bits_total - binary.length(); + if (bits_left <= 4) { + for (i = 0; i < bits_left; i++) { + binary.append("0"); + } + latch = 1; + } + + if (latch == 0) { + /* Complete current byte */ + remainder = 8 - (binary.length() % 8); + if (remainder == 8) { + remainder = 0; + } + for (i = 0; i < remainder; i++) { + binary.append("0"); + } + + /* Add padding */ + bits_left = bits_total - binary.length(); + if (bits_left > 4) { + remainder = (bits_left - 4) / 8; + for (i = 0; i < remainder; i++) { + if ((i & 1) != 0) { + binary.append("00010001"); + } else { + binary.append("11101100"); + } + } + } + binary.append("0000"); + } + + data_codewords = 11; + ecc_codewords = 6; // ecc_mode == EccMode.L + if (ecc_mode == EccMode.M) { + data_codewords = 9; + ecc_codewords = 8; + } + + /* Copy data into codewords */ + for (i = 0; i < (data_codewords - 1); i++) { + data_blocks[i] = 0; + if (binary.charAt(i * 8) == '1') { + data_blocks[i] += 0x80; + } + if (binary.charAt((i * 8) + 1) == '1') { + data_blocks[i] += 0x40; + } + if (binary.charAt((i * 8) + 2) == '1') { + data_blocks[i] += 0x20; + } + if (binary.charAt((i * 8) + 3) == '1') { + data_blocks[i] += 0x10; + } + if (binary.charAt((i * 8) + 4) == '1') { + data_blocks[i] += 0x08; + } + if (binary.charAt((i * 8) + 5) == '1') { + data_blocks[i] += 0x04; + } + if (binary.charAt((i * 8) + 6) == '1') { + data_blocks[i] += 0x02; + } + if (binary.charAt((i * 8) + 7) == '1') { + data_blocks[i] += 0x01; + } + } + + if (ecc_mode == EccMode.L) { + data_blocks[10] = 0; + if (binary.charAt(80) == '1') { + data_blocks[10] += 0x08; + } + if (binary.charAt(81) == '1') { + data_blocks[10] += 0x04; + } + if (binary.charAt(82) == '1') { + data_blocks[10] += 0x02; + } + if (binary.charAt(83) == '1') { + data_blocks[10] += 0x01; + } + } + + if (ecc_mode == EccMode.M) { + data_blocks[8] = 0; + if (binary.charAt(64) == '1') { + data_blocks[8] += 0x08; + } + if (binary.charAt(65) == '1') { + data_blocks[8] += 0x04; + } + if (binary.charAt(66) == '1') { + data_blocks[8] += 0x02; + } + if (binary.charAt(67) == '1') { + data_blocks[8] += 0x01; + } + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < data_codewords; i++) { + encodeInfo.append(Integer.toString(data_blocks[i])).append(" "); + } + encodeInfo.append("\n"); + + /* Calculate Reed-Solomon error codewords */ + rs.init_gf(0x11d); + rs.init_code(ecc_codewords, 0); + rs.encode(data_codewords, data_blocks); + for (i = 0; i < ecc_codewords; i++) { + ecc_blocks[i] = rs.getResult(i); + } + + /* Add Reed-Solomon codewords to binary data */ + for (i = 0; i < ecc_codewords; i++) { + binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80)); + } + } + + private void generateM4Symbol(EccMode ecc_mode) { + int i; + int bits_total, bits_left, remainder; + int data_codewords, ecc_codewords; + int[] data_blocks = new int[17]; + int[] ecc_blocks = new int[15]; + ReedSolomon rs = new ReedSolomon(); + + bits_total = 128; // ecc_mode == EccMode.L + if (ecc_mode == EccMode.M) { + bits_total = 112; + } + if (ecc_mode == EccMode.Q) { + bits_total = 80; + } + + /* Complete current byte */ + remainder = 8 - (binary.length() % 8); + if (remainder == 8) { + remainder = 0; + } + for (i = 0; i < remainder; i++) { + binary.append("0"); + } + + /* Add padding */ + bits_left = bits_total - binary.length(); + remainder = bits_left / 8; + for (i = 0; i < remainder; i++) { + if ((i & 1) != 0) { + binary.append("00010001"); + } else { + binary.append("11101100"); + } + } + + data_codewords = 16; + ecc_codewords = 8; // ecc_mode == EccMode.L + if (ecc_mode == EccMode.M) { + data_codewords = 14; + ecc_codewords = 10; + } + if (ecc_mode == EccMode.Q) { + data_codewords = 10; + ecc_codewords = 14; + } + + /* Copy data into codewords */ + for (i = 0; i < data_codewords; i++) { + data_blocks[i] = 0; + if (binary.charAt(i * 8) == '1') { + data_blocks[i] += 0x80; + } + if (binary.charAt((i * 8) + 1) == '1') { + data_blocks[i] += 0x40; + } + if (binary.charAt((i * 8) + 2) == '1') { + data_blocks[i] += 0x20; + } + if (binary.charAt((i * 8) + 3) == '1') { + data_blocks[i] += 0x10; + } + if (binary.charAt((i * 8) + 4) == '1') { + data_blocks[i] += 0x08; + } + if (binary.charAt((i * 8) + 5) == '1') { + data_blocks[i] += 0x04; + } + if (binary.charAt((i * 8) + 6) == '1') { + data_blocks[i] += 0x02; + } + if (binary.charAt((i * 8) + 7) == '1') { + data_blocks[i] += 0x01; + } + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < data_codewords; i++) { + encodeInfo.append(Integer.toString(data_blocks[i])).append(" "); + } + encodeInfo.append("\n"); + + /* Calculate Reed-Solomon error codewords */ + rs.init_gf(0x11d); + rs.init_code(ecc_codewords, 0); + rs.encode(data_codewords, data_blocks); + for (i = 0; i < ecc_codewords; i++) { + ecc_blocks[i] = rs.getResult(i); + } + + /* Add Reed-Solomon codewords to binary data */ + for (i = 0; i < ecc_codewords; i++) { + binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80)); + } + } + + private void setupBitGrid(int size) { + int i, toggle = 1; + + /* Add timing patterns */ + for (i = 0; i < size; i++) { + if (toggle == 1) { + grid[i] = 0x21; + grid[(i * size)] = 0x21; + toggle = 0; + } else { + grid[i] = 0x20; + grid[(i * size)] = 0x20; + toggle = 1; + } + } + + /* Add finder patterns */ + placeFinderPattern(size, 0, 0); + + /* Add separators */ + for (i = 0; i < 7; i++) { + grid[(7 * size) + i] = 0x10; + grid[(i * size) + 7] = 0x10; + } + grid[(7 * size) + 7] = 0x10; + + + /* Reserve space for format information */ + for (i = 0; i < 8; i++) { + grid[(8 * size) + i] += 0x20; + grid[(i * size) + 8] += 0x20; + } + grid[(8 * size) + 8] += 0x20; + } + + private void placeFinderPattern(int size, int x, int y) { + int xp, yp; + + int[] finder = { + 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 0, 1, + 1, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1 + }; + + for (xp = 0; xp < 7; xp++) { + for (yp = 0; yp < 7; yp++) { + if (finder[xp + (7 * yp)] == 1) { + grid[((yp + y) * size) + (xp + x)] = 0x11; + } else { + grid[((yp + y) * size) + (xp + x)] = 0x10; + } + } + } + } + + private void populateBitGrid(int size) { + boolean goingUp = true; + int row = 0; /* right hand side */ + + int i, n, x, y; + + n = binary.length(); + y = size - 1; + i = 0; + do { + x = (size - 2) - (row * 2); + + if ((grid[(y * size) + (x + 1)] & 0xf0) == 0) { + if (binary.charAt(i) == '1') { + grid[(y * size) + (x + 1)] = 0x01; + } else { + grid[(y * size) + (x + 1)] = 0x00; + } + i++; + } + + if (i < n) { + if ((grid[(y * size) + x] & 0xf0) == 0) { + if (binary.charAt(i) == '1') { + grid[(y * size) + x] = 0x01; + } else { + grid[(y * size) + x] = 0x00; + } + i++; + } + } + + if (goingUp) { + y--; + } else { + y++; + } + if (y == 0) { + /* reached the top */ + row++; + y = 1; + goingUp = false; + } + if (y == size) { + /* reached the bottom */ + row++; + y = size - 1; + goingUp = true; + } + } while (i < n); + } + + private int applyBitmask(int size) { + int x, y; + int p; + int local_pattern; + int[] value = new int[8]; + int best_val, best_pattern; + int bit; + + int[] mask = new int[size * size]; + eval = new int[size * size]; + + /* Perform data masking */ + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + mask[(y * size) + x] = 0x00; + + if ((grid[(y * size) + x] & 0xf0) == 0) { + if ((y & 1) == 0) { + mask[(y * size) + x] += 0x01; + } + + if ((((y / 2) + (x / 3)) & 1) == 0) { + mask[(y * size) + x] += 0x02; + } + + if (((((y * x) & 1) + ((y * x) % 3)) & 1) == 0) { + mask[(y * size) + x] += 0x04; + } + + if (((((y + x) & 1) + ((y * x) % 3)) & 1) == 0) { + mask[(y * size) + x] += 0x08; + } + } + } + } + + + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + if ((grid[(y * size) + x] & 0x01) != 0) { + p = 0xff; + } else { + p = 0x00; + } + + eval[(y * size) + x] = mask[(y * size) + x] ^ p; + } + } + + /* Evaluate result */ + for (local_pattern = 0; local_pattern < 4; local_pattern++) { + value[local_pattern] = evaluateBitmask(size, local_pattern); + } + + best_pattern = 0; + best_val = value[0]; + for (local_pattern = 1; local_pattern < 4; local_pattern++) { + if (value[local_pattern] > best_val) { + best_pattern = local_pattern; + best_val = value[local_pattern]; + } + } + + /* Apply mask */ + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + bit = 0; + switch (best_pattern) { + case 0: + if ((mask[(y * size) + x] & 0x01) != 0) { + bit = 1; + } + break; + case 1: + if ((mask[(y * size) + x] & 0x02) != 0) { + bit = 1; + } + break; + case 2: + if ((mask[(y * size) + x] & 0x04) != 0) { + bit = 1; + } + break; + case 3: + if ((mask[(y * size) + x] & 0x08) != 0) { + bit = 1; + } + break; + } + if (bit == 1) { + if ((grid[(y * size) + x] & 0x01) != 0) { + grid[(y * size) + x] = 0x00; + } else { + grid[(y * size) + x] = 0x01; + } + } + } + } + + return best_pattern; + } + + private int evaluateBitmask(int size, int pattern) { + int sum1, sum2, i, filter = 0, retval; + + switch (pattern) { + case 0: + filter = 0x01; + break; + case 1: + filter = 0x02; + break; + case 2: + filter = 0x04; + break; + case 3: + filter = 0x08; + break; + } + + sum1 = 0; + sum2 = 0; + for (i = 1; i < size; i++) { + if ((eval[(i * size) + size - 1] & filter) != 0) { + sum1++; + } + if ((eval[((size - 1) * size) + i] & filter) != 0) { + sum2++; + } + } + + if (sum1 <= sum2) { + retval = (sum1 * 16) + sum2; + } else { + retval = (sum2 * 16) + sum1; + } + + return retval; + } + + private enum qrMode { + NULL, KANJI, BINARY, ALPHANUM, NUMERIC + } + + public enum EccMode { + L, M, Q, H + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/MsiPlessey.java b/barcode/src/main/java/org/xbib/graphics/barcode/MsiPlessey.java new file mode 100755 index 0000000..45a6f8d --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/MsiPlessey.java @@ -0,0 +1,188 @@ +package org.xbib.graphics.barcode; + +/** + * Implements the MSI (Modified Plessey) bar code symbology. + * MSI Plessey can encode a string of numeric digits and has a range + * of check digit options. + */ +public class MsiPlessey extends Symbol { + + private final String[] MSI_PlessTable = { + "12121212", "12121221", "12122112", "12122121", "12211212", "12211221", + "12212112", "12212121", "21121212", "21121221" + }; + private CheckDigit checkOption; + + public MsiPlessey() { + checkOption = CheckDigit.NONE; + } + + /** + * Set the check digit scheme to use. Options are: None, Modulo-10, + * 2 x Modulo-10, Modulo-11 and Modulo-11 & 10. + * + * @param checkMode Type of check digit to add to symbol + */ + public void setCheckDigit(CheckDigit checkMode) { + checkOption = checkMode; + } + + @Override + public boolean encode() { + StringBuilder intermediate; + int length = content.length(); + int i; + StringBuilder evenString; + StringBuilder oddString; + String addupString; + int spacer; + int addup; + int weight; + int checkDigit1; + int checkDigit2; + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + intermediate = new StringBuilder("21"); // Start + for (i = 0; i < length; i++) { + intermediate.append(MSI_PlessTable[Character.getNumericValue(content.charAt(i))]); + } + + readable = new StringBuilder(content); + + if ((checkOption == CheckDigit.MOD10) || (checkOption == CheckDigit.MOD10_MOD10)) { + /* Add Modulo-10 check digit */ + evenString = new StringBuilder(); + oddString = new StringBuilder(); + + spacer = content.length() & 1; + + for (i = content.length() - 1; i >= 0; i--) { + if (spacer == 1) { + if ((i & 1) != 0) { + evenString.insert(0, content.charAt(i)); + } else { + oddString.insert(0, content.charAt(i)); + } + } else { + if ((i & 1) != 0) { + oddString.insert(0, content.charAt(i)); + } else { + evenString.insert(0, content.charAt(i)); + } + } + } + + if (oddString.length() == 0) { + addupString = "0"; + } else { + addupString = Integer.toString(Integer.parseInt(oddString.toString()) * 2); + } + + addupString += evenString; + + addup = 0; + for (i = 0; i < addupString.length(); i++) { + addup += addupString.charAt(i) - '0'; + } + + checkDigit1 = 10 - (addup % 10); + if (checkDigit1 == 10) { + checkDigit1 = 0; + } + + intermediate.append(MSI_PlessTable[checkDigit1]); + readable.append(checkDigit1); + } + + if ((checkOption == CheckDigit.MOD11) || (checkOption == CheckDigit.MOD11_MOD10)) { + /* Add a Modulo-11 check digit */ + weight = 2; + addup = 0; + for (i = content.length() - 1; i >= 0; i--) { + addup += (content.charAt(i) - '0') * weight; + weight++; + + if (weight == 8) { + weight = 2; + } + } + + checkDigit1 = 11 - (addup % 11); + + if (checkDigit1 == 11) { + checkDigit1 = 0; + } + + readable.append(checkDigit1); + if (checkDigit1 == 10) { + intermediate.append(MSI_PlessTable[1]); + intermediate.append(MSI_PlessTable[0]); + } else { + intermediate.append(MSI_PlessTable[checkDigit1]); + } + } + + if ((checkOption == CheckDigit.MOD10_MOD10) || (checkOption == CheckDigit.MOD11_MOD10)) { + /* Add a second Modulo-10 check digit */ + evenString = new StringBuilder(); + oddString = new StringBuilder(); + + spacer = readable.length() & 1; + + for (i = readable.length() - 1; i >= 0; i--) { + if (spacer == 1) { + if ((i & 1) != 0) { + evenString.insert(0, readable.charAt(i)); + } else { + oddString.insert(0, readable.charAt(i)); + } + } else { + if ((i & 1) != 0) { + oddString.insert(0, readable.charAt(i)); + } else { + evenString.insert(0, readable.charAt(i)); + } + } + } + + if (oddString.length() == 0) { + addupString = "0"; + } else { + addupString = Integer.toString(Integer.parseInt(oddString.toString()) * 2); + } + + addupString += evenString; + + addup = 0; + for (i = 0; i < addupString.length(); i++) { + addup += addupString.charAt(i) - '0'; + } + + checkDigit2 = 10 - (addup % 10); + if (checkDigit2 == 10) { + checkDigit2 = 0; + } + + intermediate.append(MSI_PlessTable[checkDigit2]); + readable.append(checkDigit2); + } + + intermediate.append("121"); // Stop + + pattern = new String[1]; + pattern[0] = intermediate.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + public enum CheckDigit { + NONE, MOD10, MOD10_MOD10, MOD11, MOD11_MOD10 + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Nve18.java b/barcode/src/main/java/org/xbib/graphics/barcode/Nve18.java new file mode 100755 index 0000000..021f732 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Nve18.java @@ -0,0 +1,72 @@ +package org.xbib.graphics.barcode; + +import java.util.stream.IntStream; + +/** + * Calculate NVE-18 (Nummer der Versandeinheit) + * Also SSCC-18 (Serial Shipping Container Code) + * Encodes a 17 digit number, adding a Modulo-10 check digit. + */ +public class Nve18 extends Symbol { + + @Override + public boolean encode() { + StringBuilder gs1Equivalent = new StringBuilder(); + int zeroes; + int count = 0; + int c, cdigit; + int p = 0; + Code128 code128 = new Code128(); + + if (content.length() > 17) { + errorMsg.append("Input data too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + // Add leading zeroes + zeroes = 17 - content.length(); + IntStream.range(0, zeroes).mapToObj(i -> "0").forEach(gs1Equivalent::append); + gs1Equivalent.append(content); + // Add Modulus-10 check digit + for (int i = gs1Equivalent.length() - 1; i >= 0; i--) { + c = Character.getNumericValue(gs1Equivalent.charAt(i)); + if ((p % 2) == 0) { + c = c * 3; + } + count += c; + p++; + } + cdigit = 10 - (count % 10); + if (cdigit == 10) { + cdigit = 0; + } + + encodeInfo.append("NVE Check Digit: ").append(cdigit).append("\n"); + + content = "[00]" + gs1Equivalent + cdigit; + + // Defer to Code 128 + code128.setDataType(DataType.GS1); + code128.setHumanReadableLocation(getHumanReadableLocation()); + + try { + code128.setContent(content); + } catch (Exception e) { + errorMsg.append(e.getMessage()); + return false; + } + getRectangles().clear(); + getRectangles().addAll(code128.getRectangles()); + getTexts().clear(); + getTexts().addAll(code128.getTexts()); + symbolHeight = code128.symbolHeight; + symbolWidth = code128.symbolWidth; + encodeInfo.append(code128.encodeInfo); + return true; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Pdf417.java b/barcode/src/main/java/org/xbib/graphics/barcode/Pdf417.java new file mode 100755 index 0000000..32922ef --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Pdf417.java @@ -0,0 +1,1704 @@ +package org.xbib.graphics.barcode; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * Implements PDF417 bar code symbology and MicroPDF417 bar code symbology + * according to ISO/IEC 15438:2006 and ISO/IEC 24728:2006 respectively. + * PDF417 supports encoding up to the ISO standard maximum symbol size of 925 + * codewords which (at error correction level 0) allows a maximum data size + * of 1850 text characters, or 2710 digits. The maximum size MicroPDF417 symbol + * can hold 250 alphanumeric characters or 366 digits. + */ +public class Pdf417 extends Symbol { + + private static final int MAX_NUMERIC_COMPACTION_BLOCK_SIZE = 44; + + private static final int[] COEFRS = { + /* k = 2 */ + 27, 917, + + /* k = 4 */ + 522, 568, 723, 809, + + /* k = 8 */ + 237, 308, 436, 284, 646, 653, 428, 379, + + /* k = 16 */ + 274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65, + + /* k = 32 */ + 361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, + 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410, + + /* k = 64 */ + 539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733, 877, 381, 612, + 723, 476, 462, 172, 430, 609, 858, 822, 543, 376, 511, 400, 672, 762, 283, 184, + 440, 35, 519, 31, 460, 594, 225, 535, 517, 352, 605, 158, 651, 201, 488, 502, + 648, 733, 717, 83, 404, 97, 280, 771, 840, 629, 4, 381, 843, 623, 264, 543, + + /* k = 128 */ + 521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400, 925, 749, 415, + 822, 93, 217, 208, 928, 244, 583, 620, 246, 148, 447, 631, 292, 908, 490, 704, + 516, 258, 457, 907, 594, 723, 674, 292, 272, 96, 684, 432, 686, 606, 860, 569, + 193, 219, 129, 186, 236, 287, 192, 775, 278, 173, 40, 379, 712, 463, 646, 776, + 171, 491, 297, 763, 156, 732, 95, 270, 447, 90, 507, 48, 228, 821, 808, 898, + 784, 663, 627, 378, 382, 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616, + 157, 374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814, 587, 804, 34, + 211, 330, 539, 297, 827, 865, 37, 517, 834, 315, 550, 86, 801, 4, 108, 539, + + /* k = 256 */ + 524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905, 786, 138, 720, + 858, 194, 311, 913, 275, 190, 375, 850, 438, 733, 194, 280, 201, 280, 828, 757, + 710, 814, 919, 89, 68, 569, 11, 204, 796, 605, 540, 913, 801, 700, 799, 137, + 439, 418, 592, 668, 353, 859, 370, 694, 325, 240, 216, 257, 284, 549, 209, 884, + 315, 70, 329, 793, 490, 274, 877, 162, 749, 812, 684, 461, 334, 376, 849, 521, + 307, 291, 803, 712, 19, 358, 399, 908, 103, 511, 51, 8, 517, 225, 289, 470, + 637, 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136, 538, 906, 90, + 2, 290, 743, 199, 655, 903, 329, 49, 802, 580, 355, 588, 188, 462, 10, 134, + 628, 320, 479, 130, 739, 71, 263, 318, 374, 601, 192, 605, 142, 673, 687, 234, + 722, 384, 177, 752, 607, 640, 455, 193, 689, 707, 805, 641, 48, 60, 732, 621, + 895, 544, 261, 852, 655, 309, 697, 755, 756, 60, 231, 773, 434, 421, 726, 528, + 503, 118, 49, 795, 32, 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550, + 73, 914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180, 791, 893, 754, + 605, 383, 228, 749, 760, 213, 54, 297, 134, 54, 834, 299, 922, 191, 910, 532, + 609, 829, 189, 20, 167, 29, 872, 449, 83, 402, 41, 656, 505, 579, 481, 173, + 404, 251, 688, 95, 497, 555, 642, 543, 307, 159, 924, 558, 648, 55, 497, 10, + + /* k = 512 */ + 352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285, 380, 350, 492, + 197, 265, 920, 155, 914, 299, 229, 643, 294, 871, 306, 88, 87, 193, 352, 781, + 846, 75, 327, 520, 435, 543, 203, 666, 249, 346, 781, 621, 640, 268, 794, 534, + 539, 781, 408, 390, 644, 102, 476, 499, 290, 632, 545, 37, 858, 916, 552, 41, + 542, 289, 122, 272, 383, 800, 485, 98, 752, 472, 761, 107, 784, 860, 658, 741, + 290, 204, 681, 407, 855, 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142, + 808, 684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513, 192, 516, 258, + 240, 518, 794, 395, 768, 848, 51, 610, 384, 168, 190, 826, 328, 596, 786, 303, + 570, 381, 415, 641, 156, 237, 151, 429, 531, 207, 676, 710, 89, 168, 304, 402, + 40, 708, 575, 162, 864, 229, 65, 861, 841, 512, 164, 477, 221, 92, 358, 785, + 288, 357, 850, 836, 827, 736, 707, 94, 8, 494, 114, 521, 2, 499, 851, 543, + 152, 729, 771, 95, 248, 361, 578, 323, 856, 797, 289, 51, 684, 466, 533, 820, + 669, 45, 902, 452, 167, 342, 244, 173, 35, 463, 651, 51, 699, 591, 452, 578, + 37, 124, 298, 332, 552, 43, 427, 119, 662, 777, 475, 850, 764, 364, 578, 911, + 283, 711, 472, 420, 245, 288, 594, 394, 511, 327, 589, 777, 699, 688, 43, 408, + 842, 383, 721, 521, 560, 644, 714, 559, 62, 145, 873, 663, 713, 159, 672, 729, + 624, 59, 193, 417, 158, 209, 563, 564, 343, 693, 109, 608, 563, 365, 181, 772, + 677, 310, 248, 353, 708, 410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777, + 618, 586, 424, 833, 77, 597, 346, 269, 757, 632, 695, 751, 331, 247, 184, 45, + 787, 680, 18, 66, 407, 369, 54, 492, 228, 613, 830, 922, 437, 519, 644, 905, + 789, 420, 305, 441, 207, 300, 892, 827, 141, 537, 381, 662, 513, 56, 252, 341, + 242, 797, 838, 837, 720, 224, 307, 631, 61, 87, 560, 310, 756, 665, 397, 808, + 851, 309, 473, 795, 378, 31, 647, 915, 459, 806, 590, 731, 425, 216, 548, 249, + 321, 881, 699, 535, 673, 782, 210, 815, 905, 303, 843, 922, 281, 73, 469, 791, + 660, 162, 498, 308, 155, 422, 907, 817, 187, 62, 16, 425, 535, 336, 286, 437, + 375, 273, 610, 296, 183, 923, 116, 667, 751, 353, 62, 366, 691, 379, 687, 842, + 37, 357, 720, 742, 330, 5, 39, 923, 311, 424, 242, 749, 321, 54, 669, 316, + 342, 299, 534, 105, 667, 488, 640, 672, 576, 540, 316, 486, 721, 610, 46, 656, + 447, 171, 616, 464, 190, 531, 297, 321, 762, 752, 533, 175, 134, 14, 381, 433, + 717, 45, 111, 20, 596, 284, 736, 138, 646, 411, 877, 669, 141, 919, 45, 780, + 407, 164, 332, 899, 165, 726, 600, 325, 498, 655, 357, 752, 768, 223, 849, 647, + 63, 310, 863, 251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263 + }; + + ; + private static final String[] CODAGEMC = { + "urA", "xfs", "ypy", "unk", "xdw", "yoz", "pDA", "uls", "pBk", "eBA", + "pAs", "eAk", "prA", "uvs", "xhy", "pnk", "utw", "xgz", "fDA", "pls", "fBk", "frA", "pvs", + "uxy", "fnk", "ptw", "uwz", "fls", "psy", "fvs", "pxy", "ftw", "pwz", "fxy", "yrx", "ufk", + "xFw", "ymz", "onA", "uds", "xEy", "olk", "ucw", "dBA", "oks", "uci", "dAk", "okg", "dAc", + "ovk", "uhw", "xaz", "dnA", "ots", "ugy", "dlk", "osw", "ugj", "dks", "osi", "dvk", "oxw", + "uiz", "dts", "owy", "dsw", "owj", "dxw", "oyz", "dwy", "dwj", "ofA", "uFs", "xCy", "odk", + "uEw", "xCj", "clA", "ocs", "uEi", "ckk", "ocg", "ckc", "ckE", "cvA", "ohs", "uay", "ctk", + "ogw", "uaj", "css", "ogi", "csg", "csa", "cxs", "oiy", "cww", "oij", "cwi", "cyy", "oFk", + "uCw", "xBj", "cdA", "oEs", "uCi", "cck", "oEg", "uCb", "ccc", "oEa", "ccE", "oED", "chk", + "oaw", "uDj", "cgs", "oai", "cgg", "oab", "cga", "cgD", "obj", "cib", "cFA", "oCs", "uBi", + "cEk", "oCg", "uBb", "cEc", "oCa", "cEE", "oCD", "cEC", "cas", "cag", "caa", "cCk", "uAr", + "oBa", "oBD", "cCB", "tfk", "wpw", "yez", "mnA", "tds", "woy", "mlk", "tcw", "woj", "FBA", + "mks", "FAk", "mvk", "thw", "wqz", "FnA", "mts", "tgy", "Flk", "msw", "Fks", "Fkg", "Fvk", + "mxw", "tiz", "Fts", "mwy", "Fsw", "Fsi", "Fxw", "myz", "Fwy", "Fyz", "vfA", "xps", "yuy", + "vdk", "xow", "yuj", "qlA", "vcs", "xoi", "qkk", "vcg", "xob", "qkc", "vca", "mfA", "tFs", + "wmy", "qvA", "mdk", "tEw", "wmj", "qtk", "vgw", "xqj", "hlA", "Ekk", "mcg", "tEb", "hkk", + "qsg", "hkc", "EvA", "mhs", "tay", "hvA", "Etk", "mgw", "taj", "htk", "qww", "vij", "hss", + "Esg", "hsg", "Exs", "miy", "hxs", "Eww", "mij", "hww", "qyj", "hwi", "Eyy", "hyy", "Eyj", + "hyj", "vFk", "xmw", "ytj", "qdA", "vEs", "xmi", "qck", "vEg", "xmb", "qcc", "vEa", "qcE", + "qcC", "mFk", "tCw", "wlj", "qhk", "mEs", "tCi", "gtA", "Eck", "vai", "tCb", "gsk", "Ecc", + "mEa", "gsc", "qga", "mED", "EcC", "Ehk", "maw", "tDj", "gxk", "Egs", "mai", "gws", "qii", + "mab", "gwg", "Ega", "EgD", "Eiw", "mbj", "gyw", "Eii", "gyi", "Eib", "gyb", "gzj", "qFA", + "vCs", "xli", "qEk", "vCg", "xlb", "qEc", "vCa", "qEE", "vCD", "qEC", "qEB", "EFA", "mCs", + "tBi", "ghA", "EEk", "mCg", "tBb", "ggk", "qag", "vDb", "ggc", "EEE", "mCD", "ggE", "qaD", + "ggC", "Eas", "mDi", "gis", "Eag", "mDb", "gig", "qbb", "gia", "EaD", "giD", "gji", "gjb", + "qCk", "vBg", "xkr", "qCc", "vBa", "qCE", "vBD", "qCC", "qCB", "ECk", "mBg", "tAr", "gak", + "ECc", "mBa", "gac", "qDa", "mBD", "gaE", "ECC", "gaC", "ECB", "EDg", "gbg", "gba", "gbD", + "vAq", "vAn", "qBB", "mAq", "EBE", "gDE", "gDC", "gDB", "lfA", "sps", "wey", "ldk", "sow", + "ClA", "lcs", "soi", "Ckk", "lcg", "Ckc", "CkE", "CvA", "lhs", "sqy", "Ctk", "lgw", "sqj", + "Css", "lgi", "Csg", "Csa", "Cxs", "liy", "Cww", "lij", "Cwi", "Cyy", "Cyj", "tpk", "wuw", + "yhj", "ndA", "tos", "wui", "nck", "tog", "wub", "ncc", "toa", "ncE", "toD", "lFk", "smw", + "wdj", "nhk", "lEs", "smi", "atA", "Cck", "tqi", "smb", "ask", "ngg", "lEa", "asc", "CcE", + "asE", "Chk", "law", "snj", "axk", "Cgs", "trj", "aws", "nii", "lab", "awg", "Cga", "awa", + "Ciw", "lbj", "ayw", "Cii", "ayi", "Cib", "Cjj", "azj", "vpA", "xus", "yxi", "vok", "xug", + "yxb", "voc", "xua", "voE", "xuD", "voC", "nFA", "tms", "wti", "rhA", "nEk", "xvi", "wtb", + "rgk", "vqg", "xvb", "rgc", "nEE", "tmD", "rgE", "vqD", "nEB", "CFA", "lCs", "sli", "ahA", + "CEk", "lCg", "slb", "ixA", "agk", "nag", "tnb", "iwk", "rig", "vrb", "lCD", "iwc", "agE", + "naD", "iwE", "CEB", "Cas", "lDi", "ais", "Cag", "lDb", "iys", "aig", "nbb", "iyg", "rjb", + "CaD", "aiD", "Cbi", "aji", "Cbb", "izi", "ajb", "vmk", "xtg", "ywr", "vmc", "xta", "vmE", + "xtD", "vmC", "vmB", "nCk", "tlg", "wsr", "rak", "nCc", "xtr", "rac", "vna", "tlD", "raE", + "nCC", "raC", "nCB", "raB", "CCk", "lBg", "skr", "aak", "CCc", "lBa", "iik", "aac", "nDa", + "lBD", "iic", "rba", "CCC", "iiE", "aaC", "CCB", "aaB", "CDg", "lBr", "abg", "CDa", "ijg", + "aba", "CDD", "ija", "abD", "CDr", "ijr", "vlc", "xsq", "vlE", "xsn", "vlC", "vlB", "nBc", + "tkq", "rDc", "nBE", "tkn", "rDE", "vln", "rDC", "nBB", "rDB", "CBc", "lAq", "aDc", "CBE", + "lAn", "ibc", "aDE", "nBn", "ibE", "rDn", "CBB", "ibC", "aDB", "ibB", "aDq", "ibq", "ibn", + "xsf", "vkl", "tkf", "nAm", "nAl", "CAo", "aBo", "iDo", "CAl", "aBl", "kpk", "BdA", "kos", + "Bck", "kog", "seb", "Bcc", "koa", "BcE", "koD", "Bhk", "kqw", "sfj", "Bgs", "kqi", "Bgg", + "kqb", "Bga", "BgD", "Biw", "krj", "Bii", "Bib", "Bjj", "lpA", "sus", "whi", "lok", "sug", + "loc", "sua", "loE", "suD", "loC", "BFA", "kms", "sdi", "DhA", "BEk", "svi", "sdb", "Dgk", + "lqg", "svb", "Dgc", "BEE", "kmD", "DgE", "lqD", "BEB", "Bas", "kni", "Dis", "Bag", "knb", + "Dig", "lrb", "Dia", "BaD", "Bbi", "Dji", "Bbb", "Djb", "tuk", "wxg", "yir", "tuc", "wxa", + "tuE", "wxD", "tuC", "tuB", "lmk", "stg", "nqk", "lmc", "sta", "nqc", "tva", "stD", "nqE", + "lmC", "nqC", "lmB", "nqB", "BCk", "klg", "Dak", "BCc", "str", "bik", "Dac", "lna", "klD", + "bic", "nra", "BCC", "biE", "DaC", "BCB", "DaB", "BDg", "klr", "Dbg", "BDa", "bjg", "Dba", + "BDD", "bja", "DbD", "BDr", "Dbr", "bjr", "xxc", "yyq", "xxE", "yyn", "xxC", "xxB", "ttc", + "wwq", "vvc", "xxq", "wwn", "vvE", "xxn", "vvC", "ttB", "vvB", "llc", "ssq", "nnc", "llE", + "ssn", "rrc", "nnE", "ttn", "rrE", "vvn", "llB", "rrC", "nnB", "rrB", "BBc", "kkq", "DDc", + "BBE", "kkn", "bbc", "DDE", "lln", "jjc", "bbE", "nnn", "BBB", "jjE", "rrn", "DDB", "jjC", + "BBq", "DDq", "BBn", "bbq", "DDn", "jjq", "bbn", "jjn", "xwo", "yyf", "xwm", "xwl", "tso", + "wwf", "vto", "xwv", "vtm", "tsl", "vtl", "lko", "ssf", "nlo", "lkm", "rno", "nlm", "lkl", + "rnm", "nll", "rnl", "BAo", "kkf", "DBo", "lkv", "bDo", "DBm", "BAl", "jbo", "bDm", "DBl", + "jbm", "bDl", "jbl", "DBv", "jbv", "xwd", "vsu", "vst", "nku", "rlu", "rlt", "DAu", "bBu", + "jDu", "jDt", "ApA", "Aok", "keg", "Aoc", "AoE", "AoC", "Aqs", "Aqg", "Aqa", "AqD", "Ari", + "Arb", "kuk", "kuc", "sha", "kuE", "shD", "kuC", "kuB", "Amk", "kdg", "Bqk", "kvg", "kda", + "Bqc", "kva", "BqE", "kvD", "BqC", "AmB", "BqB", "Ang", "kdr", "Brg", "kvr", "Bra", "AnD", + "BrD", "Anr", "Brr", "sxc", "sxE", "sxC", "sxB", "ktc", "lvc", "sxq", "sgn", "lvE", "sxn", + "lvC", "ktB", "lvB", "Alc", "Bnc", "AlE", "kcn", "Drc", "BnE", "AlC", "DrE", "BnC", "AlB", + "DrC", "BnB", "Alq", "Bnq", "Aln", "Drq", "Bnn", "Drn", "wyo", "wym", "wyl", "swo", "txo", + "wyv", "txm", "swl", "txl", "kso", "sgf", "lto", "swv", "nvo", "ltm", "ksl", "nvm", "ltl", + "nvl", "Ako", "kcf", "Blo", "ksv", "Dno", "Blm", "Akl", "bro", "Dnm", "Bll", "brm", "Dnl", + "Akv", "Blv", "Dnv", "brv", "yze", "yzd", "wye", "xyu", "wyd", "xyt", "swe", "twu", "swd", + "vxu", "twt", "vxt", "kse", "lsu", "ksd", "ntu", "lst", "rvu", "ypk", "zew", "xdA", "yos", + "zei", "xck", "yog", "zeb", "xcc", "yoa", "xcE", "yoD", "xcC", "xhk", "yqw", "zfj", "utA", + "xgs", "yqi", "usk", "xgg", "yqb", "usc", "xga", "usE", "xgD", "usC", "uxk", "xiw", "yrj", + "ptA", "uws", "xii", "psk", "uwg", "xib", "psc", "uwa", "psE", "uwD", "psC", "pxk", "uyw", + "xjj", "ftA", "pws", "uyi", "fsk", "pwg", "uyb", "fsc", "pwa", "fsE", "pwD", "fxk", "pyw", + "uzj", "fws", "pyi", "fwg", "pyb", "fwa", "fyw", "pzj", "fyi", "fyb", "xFA", "yms", "zdi", + "xEk", "ymg", "zdb", "xEc", "yma", "xEE", "ymD", "xEC", "xEB", "uhA", "xas", "yni", "ugk", + "xag", "ynb", "ugc", "xaa", "ugE", "xaD", "ugC", "ugB", "oxA", "uis", "xbi", "owk", "uig", + "xbb", "owc", "uia", "owE", "uiD", "owC", "owB", "dxA", "oys", "uji", "dwk", "oyg", "ujb", + "dwc", "oya", "dwE", "oyD", "dwC", "dys", "ozi", "dyg", "ozb", "dya", "dyD", "dzi", "dzb", + "xCk", "ylg", "zcr", "xCc", "yla", "xCE", "ylD", "xCC", "xCB", "uak", "xDg", "ylr", "uac", + "xDa", "uaE", "xDD", "uaC", "uaB", "oik", "ubg", "xDr", "oic", "uba", "oiE", "ubD", "oiC", + "oiB", "cyk", "ojg", "ubr", "cyc", "oja", "cyE", "ojD", "cyC", "cyB", "czg", "ojr", "cza", + "czD", "czr", "xBc", "ykq", "xBE", "ykn", "xBC", "xBB", "uDc", "xBq", "uDE", "xBn", "uDC", + "uDB", "obc", "uDq", "obE", "uDn", "obC", "obB", "cjc", "obq", "cjE", "obn", "cjC", "cjB", + "cjq", "cjn", "xAo", "ykf", "xAm", "xAl", "uBo", "xAv", "uBm", "uBl", "oDo", "uBv", "oDm", + "oDl", "cbo", "oDv", "cbm", "cbl", "xAe", "xAd", "uAu", "uAt", "oBu", "oBt", "wpA", "yes", + "zFi", "wok", "yeg", "zFb", "woc", "yea", "woE", "yeD", "woC", "woB", "thA", "wqs", "yfi", + "tgk", "wqg", "yfb", "tgc", "wqa", "tgE", "wqD", "tgC", "tgB", "mxA", "tis", "wri", "mwk", + "tig", "wrb", "mwc", "tia", "mwE", "tiD", "mwC", "mwB", "FxA", "mys", "tji", "Fwk", "myg", + "tjb", "Fwc", "mya", "FwE", "myD", "FwC", "Fys", "mzi", "Fyg", "mzb", "Fya", "FyD", "Fzi", + "Fzb", "yuk", "zhg", "hjs", "yuc", "zha", "hbw", "yuE", "zhD", "hDy", "yuC", "yuB", "wmk", + "ydg", "zEr", "xqk", "wmc", "zhr", "xqc", "yva", "ydD", "xqE", "wmC", "xqC", "wmB", "xqB", + "tak", "wng", "ydr", "vik", "tac", "wna", "vic", "xra", "wnD", "viE", "taC", "viC", "taB", + "viB", "mik", "tbg", "wnr", "qyk", "mic", "tba", "qyc", "vja", "tbD", "qyE", "miC", "qyC", + "miB", "qyB", "Eyk", "mjg", "tbr", "hyk", "Eyc", "mja", "hyc", "qza", "mjD", "hyE", "EyC", + "hyC", "EyB", "Ezg", "mjr", "hzg", "Eza", "hza", "EzD", "hzD", "Ezr", "ytc", "zgq", "grw", + "ytE", "zgn", "gny", "ytC", "glz", "ytB", "wlc", "ycq", "xnc", "wlE", "ycn", "xnE", "ytn", + "xnC", "wlB", "xnB", "tDc", "wlq", "vbc", "tDE", "wln", "vbE", "xnn", "vbC", "tDB", "vbB", + "mbc", "tDq", "qjc", "mbE", "tDn", "qjE", "vbn", "qjC", "mbB", "qjB", "Ejc", "mbq", "gzc", + "EjE", "mbn", "gzE", "qjn", "gzC", "EjB", "gzB", "Ejq", "gzq", "Ejn", "gzn", "yso", "zgf", + "gfy", "ysm", "gdz", "ysl", "wko", "ycf", "xlo", "ysv", "xlm", "wkl", "xll", "tBo", "wkv", + "vDo", "tBm", "vDm", "tBl", "vDl", "mDo", "tBv", "qbo", "vDv", "qbm", "mDl", "qbl", "Ebo", + "mDv", "gjo", "Ebm", "gjm", "Ebl", "gjl", "Ebv", "gjv", "yse", "gFz", "ysd", "wke", "xku", + "wkd", "xkt", "tAu", "vBu", "tAt", "vBt", "mBu", "qDu", "mBt", "qDt", "EDu", "gbu", "EDt", + "gbt", "ysF", "wkF", "xkh", "tAh", "vAx", "mAx", "qBx", "wek", "yFg", "zCr", "wec", "yFa", + "weE", "yFD", "weC", "weB", "sqk", "wfg", "yFr", "sqc", "wfa", "sqE", "wfD", "sqC", "sqB", + "lik", "srg", "wfr", "lic", "sra", "liE", "srD", "liC", "liB", "Cyk", "ljg", "srr", "Cyc", + "lja", "CyE", "ljD", "CyC", "CyB", "Czg", "ljr", "Cza", "CzD", "Czr", "yhc", "zaq", "arw", + "yhE", "zan", "any", "yhC", "alz", "yhB", "wdc", "yEq", "wvc", "wdE", "yEn", "wvE", "yhn", + "wvC", "wdB", "wvB", "snc", "wdq", "trc", "snE", "wdn", "trE", "wvn", "trC", "snB", "trB", + "lbc", "snq", "njc", "lbE", "snn", "njE", "trn", "njC", "lbB", "njB", "Cjc", "lbq", "azc", + "CjE", "lbn", "azE", "njn", "azC", "CjB", "azB", "Cjq", "azq", "Cjn", "azn", "zio", "irs", + "rfy", "zim", "inw", "rdz", "zil", "ily", "ikz", "ygo", "zaf", "afy", "yxo", "ziv", "ivy", + "adz", "yxm", "ygl", "itz", "yxl", "wco", "yEf", "wto", "wcm", "xvo", "yxv", "wcl", "xvm", + "wtl", "xvl", "slo", "wcv", "tno", "slm", "vro", "tnm", "sll", "vrm", "tnl", "vrl", "lDo", + "slv", "nbo", "lDm", "rjo", "nbm", "lDl", "rjm", "nbl", "rjl", "Cbo", "lDv", "ajo", "Cbm", + "izo", "ajm", "Cbl", "izm", "ajl", "izl", "Cbv", "ajv", "zie", "ifw", "rFz", "zid", "idy", + "icz", "yge", "aFz", "ywu", "ygd", "ihz", "ywt", "wce", "wsu", "wcd", "xtu", "wst", "xtt", + "sku", "tlu", "skt", "vnu", "tlt", "vnt", "lBu", "nDu", "lBt", "rbu", "nDt", "rbt", "CDu", + "abu", "CDt", "iju", "abt", "ijt", "ziF", "iFy", "iEz", "ygF", "ywh", "wcF", "wsh", "xsx", + "skh", "tkx", "vlx", "lAx", "nBx", "rDx", "CBx", "aDx", "ibx", "iCz", "wFc", "yCq", "wFE", + "yCn", "wFC", "wFB", "sfc", "wFq", "sfE", "wFn", "sfC", "sfB", "krc", "sfq", "krE", "sfn", + "krC", "krB", "Bjc", "krq", "BjE", "krn", "BjC", "BjB", "Bjq", "Bjn", "yao", "zDf", "Dfy", + "yam", "Ddz", "yal", "wEo", "yCf", "who", "wEm", "whm", "wEl", "whl", "sdo", "wEv", "svo", + "sdm", "svm", "sdl", "svl", "kno", "sdv", "lro", "knm", "lrm", "knl", "lrl", "Bbo", "knv", + "Djo", "Bbm", "Djm", "Bbl", "Djl", "Bbv", "Djv", "zbe", "bfw", "npz", "zbd", "bdy", "bcz", + "yae", "DFz", "yiu", "yad", "bhz", "yit", "wEe", "wgu", "wEd", "wxu", "wgt", "wxt", "scu", + "stu", "sct", "tvu", "stt", "tvt", "klu", "lnu", "klt", "nru", "lnt", "nrt", "BDu", "Dbu", + "BDt", "bju", "Dbt", "bjt", "jfs", "rpy", "jdw", "roz", "jcy", "jcj", "zbF", "bFy", "zjh", + "jhy", "bEz", "jgz", "yaF", "yih", "yyx", "wEF", "wgh", "wwx", "xxx", "sch", "ssx", "ttx", + "vvx", "kkx", "llx", "nnx", "rrx", "BBx", "DDx", "bbx", "jFw", "rmz", "jEy", "jEj", "bCz", + "jaz", "jCy", "jCj", "jBj", "wCo", "wCm", "wCl", "sFo", "wCv", "sFm", "sFl", "kfo", "sFv", + "kfm", "kfl", "Aro", "kfv", "Arm", "Arl", "Arv", "yDe", "Bpz", "yDd", "wCe", "wau", "wCd", + "wat", "sEu", "shu", "sEt", "sht", "kdu", "kvu", "kdt", "kvt", "Anu", "Bru", "Ant", "Brt", + "zDp", "Dpy", "Doz", "yDF", "ybh", "wCF", "wah", "wix", "sEh", "sgx", "sxx", "kcx", "ktx", + "lvx", "Alx", "Bnx", "Drx", "bpw", "nuz", "boy", "boj", "Dmz", "bqz", "jps", "ruy", "jow", + "ruj", "joi", "job", "bmy", "jqy", "bmj", "jqj", "jmw", "rtj", "jmi", "jmb", "blj", "jnj", + "jli", "jlb", "jkr", "sCu", "sCt", "kFu", "kFt", "Afu", "Aft", "wDh", "sCh", "sax", "kEx", + "khx", "Adx", "Avx", "Buz", "Duy", "Duj", "buw", "nxj", "bui", "bub", "Dtj", "bvj", "jus", + "rxi", "jug", "rxb", "jua", "juD", "bti", "jvi", "btb", "jvb", "jtg", "rwr", "jta", "jtD", + "bsr", "jtr", "jsq", "jsn", "Bxj", "Dxi", "Dxb", "bxg", "nyr", "bxa", "bxD", "Dwr", "bxr", + "bwq", "bwn", "pjk", "urw", "ejA", "pbs", "uny", "ebk", "pDw", "ulz", "eDs", "pBy", "eBw", + "zfc", "fjk", "prw", "zfE", "fbs", "pny", "zfC", "fDw", "plz", "zfB", "fBy", "yrc", "zfq", + "frw", "yrE", "zfn", "fny", "yrC", "flz", "yrB", "xjc", "yrq", "xjE", "yrn", "xjC", "xjB", + "uzc", "xjq", "uzE", "xjn", "uzC", "uzB", "pzc", "uzq", "pzE", "uzn", "pzC", "djA", "ors", + "ufy", "dbk", "onw", "udz", "dDs", "oly", "dBw", "okz", "dAy", "zdo", "drs", "ovy", "zdm", + "dnw", "otz", "zdl", "dly", "dkz", "yno", "zdv", "dvy", "ynm", "dtz", "ynl", "xbo", "ynv", + "xbm", "xbl", "ujo", "xbv", "ujm", "ujl", "ozo", "ujv", "ozm", "ozl", "crk", "ofw", "uFz", + "cns", "ody", "clw", "ocz", "cky", "ckj", "zcu", "cvw", "ohz", "zct", "cty", "csz", "ylu", + "cxz", "ylt", "xDu", "xDt", "ubu", "ubt", "oju", "ojt", "cfs", "oFy", "cdw", "oEz", "ccy", + "ccj", "zch", "chy", "cgz", "ykx", "xBx", "uDx", "cFw", "oCz", "cEy", "cEj", "caz", "cCy", + "cCj", "FjA", "mrs", "tfy", "Fbk", "mnw", "tdz", "FDs", "mly", "FBw", "mkz", "FAy", "zFo", + "Frs", "mvy", "zFm", "Fnw", "mtz", "zFl", "Fly", "Fkz", "yfo", "zFv", "Fvy", "yfm", "Ftz", + "yfl", "wro", "yfv", "wrm", "wrl", "tjo", "wrv", "tjm", "tjl", "mzo", "tjv", "mzm", "mzl", + "qrk", "vfw", "xpz", "hbA", "qns", "vdy", "hDk", "qlw", "vcz", "hBs", "qky", "hAw", "qkj", + "hAi", "Erk", "mfw", "tFz", "hrk", "Ens", "mdy", "hns", "qty", "mcz", "hlw", "Eky", "hky", + "Ekj", "hkj", "zEu", "Evw", "mhz", "zhu", "zEt", "hvw", "Ety", "zht", "hty", "Esz", "hsz", + "ydu", "Exz", "yvu", "ydt", "hxz", "yvt", "wnu", "xru", "wnt", "xrt", "tbu", "vju", "tbt", + "vjt", "mju", "mjt", "grA", "qfs", "vFy", "gnk", "qdw", "vEz", "gls", "qcy", "gkw", "qcj", + "gki", "gkb", "Efs", "mFy", "gvs", "Edw", "mEz", "gtw", "qgz", "gsy", "Ecj", "gsj", "zEh", + "Ehy", "zgx", "gxy", "Egz", "gwz", "ycx", "ytx", "wlx", "xnx", "tDx", "vbx", "mbx", "gfk", + "qFw", "vCz", "gds", "qEy", "gcw", "qEj", "gci", "gcb", "EFw", "mCz", "ghw", "EEy", "ggy", + "EEj", "ggj", "Eaz", "giz", "gFs", "qCy", "gEw", "qCj", "gEi", "gEb", "ECy", "gay", "ECj", + "gaj", "gCw", "qBj", "gCi", "gCb", "EBj", "gDj", "gBi", "gBb", "Crk", "lfw", "spz", "Cns", + "ldy", "Clw", "lcz", "Cky", "Ckj", "zCu", "Cvw", "lhz", "zCt", "Cty", "Csz", "yFu", "Cxz", + "yFt", "wfu", "wft", "sru", "srt", "lju", "ljt", "arA", "nfs", "tpy", "ank", "ndw", "toz", + "als", "ncy", "akw", "ncj", "aki", "akb", "Cfs", "lFy", "avs", "Cdw", "lEz", "atw", "ngz", + "asy", "Ccj", "asj", "zCh", "Chy", "zax", "axy", "Cgz", "awz", "yEx", "yhx", "wdx", "wvx", + "snx", "trx", "lbx", "rfk", "vpw", "xuz", "inA", "rds", "voy", "ilk", "rcw", "voj", "iks", + "rci", "ikg", "rcb", "ika", "afk", "nFw", "tmz", "ivk", "ads", "nEy", "its", "rgy", "nEj", + "isw", "aci", "isi", "acb", "isb", "CFw", "lCz", "ahw", "CEy", "ixw", "agy", "CEj", "iwy", + "agj", "iwj", "Caz", "aiz", "iyz", "ifA", "rFs", "vmy", "idk", "rEw", "vmj", "ics", "rEi", + "icg", "rEb", "ica", "icD", "aFs", "nCy", "ihs", "aEw", "nCj", "igw", "raj", "igi", "aEb", + "igb", "CCy", "aay", "CCj", "iiy", "aaj", "iij", "iFk", "rCw", "vlj", "iEs", "rCi", "iEg", + "rCb", "iEa", "iED", "aCw", "nBj", "iaw", "aCi", "iai", "aCb", "iab", "CBj", "aDj", "ibj", + "iCs", "rBi", "iCg", "rBb", "iCa", "iCD", "aBi", "iDi", "aBb", "iDb", "iBg", "rAr", "iBa", + "iBD", "aAr", "iBr", "iAq", "iAn", "Bfs", "kpy", "Bdw", "koz", "Bcy", "Bcj", "Bhy", "Bgz", + "yCx", "wFx", "sfx", "krx", "Dfk", "lpw", "suz", "Dds", "loy", "Dcw", "loj", "Dci", "Dcb", + "BFw", "kmz", "Dhw", "BEy", "Dgy", "BEj", "Dgj", "Baz", "Diz", "bfA", "nps", "tuy", "bdk", + "now", "tuj", "bcs", "noi", "bcg", "nob", "bca", "bcD", "DFs", "lmy", "bhs", "DEw", "lmj", + "bgw", "DEi", "bgi", "DEb", "bgb", "BCy", "Day", "BCj", "biy", "Daj", "bij", "rpk", "vuw", + "xxj", "jdA", "ros", "vui", "jck", "rog", "vub", "jcc", "roa", "jcE", "roD", "jcC", "bFk", + "nmw", "ttj", "jhk", "bEs", "nmi", "jgs", "rqi", "nmb", "jgg", "bEa", "jga", "bED", "jgD", + "DCw", "llj", "baw", "DCi", "jiw", "bai", "DCb", "jii", "bab", "jib", "BBj", "DDj", "bbj", + "jjj", "jFA", "rms", "vti", "jEk", "rmg", "vtb", "jEc", "rma", "jEE", "rmD", "jEC", "jEB", + "bCs", "nli", "jas", "bCg", "nlb", "jag", "rnb", "jaa", "bCD", "jaD", "DBi", "bDi", "DBb", + "jbi", "bDb", "jbb", "jCk", "rlg", "vsr", "jCc", "rla", "jCE", "rlD", "jCC", "jCB", "bBg", + "nkr", "jDg", "bBa", "jDa", "bBD", "jDD", "DAr", "bBr", "jDr", "jBc", "rkq", "jBE", "rkn", + "jBC", "jBB", "bAq", "jBq", "bAn", "jBn", "jAo", "rkf", "jAm", "jAl", "bAf", "jAv", "Apw", + "kez", "Aoy", "Aoj", "Aqz", "Bps", "kuy", "Bow", "kuj", "Boi", "Bob", "Amy", "Bqy", "Amj", + "Bqj", "Dpk", "luw", "sxj", "Dos", "lui", "Dog", "lub", "Doa", "DoD", "Bmw", "ktj", "Dqw", + "Bmi", "Dqi", "Bmb", "Dqb", "Alj", "Bnj", "Drj", "bpA", "nus", "txi", "bok", "nug", "txb", + "boc", "nua", "boE", "nuD", "boC", "boB", "Dms", "lti", "bqs", "Dmg", "ltb", "bqg", "nvb", + "bqa", "DmD", "bqD", "Bli", "Dni", "Blb", "bri", "Dnb", "brb", "ruk", "vxg", "xyr", "ruc", + "vxa", "ruE", "vxD", "ruC", "ruB", "bmk", "ntg", "twr", "jqk", "bmc", "nta", "jqc", "rva", + "ntD", "jqE", "bmC", "jqC", "bmB", "jqB", "Dlg", "lsr", "bng", "Dla", "jrg", "bna", "DlD", + "jra", "bnD", "jrD", "Bkr", "Dlr", "bnr", "jrr", "rtc", "vwq", "rtE", "vwn", "rtC", "rtB", + "blc", "nsq", "jnc", "blE", "nsn", "jnE", "rtn", "jnC", "blB", "jnB", "Dkq", "blq", "Dkn", + "jnq", "bln", "jnn", "rso", "vwf", "rsm", "rsl", "bko", "nsf", "jlo", "bkm", "jlm", "bkl", + "jll", "Dkf", "bkv", "jlv", "rse", "rsd", "bke", "jku", "bkd", "jkt", "Aey", "Aej", "Auw", + "khj", "Aui", "Aub", "Adj", "Avj", "Bus", "kxi", "Bug", "kxb", "Bua", "BuD", "Ati", "Bvi", + "Atb", "Bvb", "Duk", "lxg", "syr", "Duc", "lxa", "DuE", "lxD", "DuC", "DuB", "Btg", "kwr", + "Dvg", "lxr", "Dva", "BtD", "DvD", "Asr", "Btr", "Dvr", "nxc", "tyq", "nxE", "tyn", "nxC", + "nxB", "Dtc", "lwq", "bvc", "nxq", "lwn", "bvE", "DtC", "bvC", "DtB", "bvB", "Bsq", "Dtq", + "Bsn", "bvq", "Dtn", "bvn", "vyo", "xzf", "vym", "vyl", "nwo", "tyf", "rxo", "nwm", "rxm", + "nwl", "rxl", "Dso", "lwf", "bto", "Dsm", "jvo", "btm", "Dsl", "jvm", "btl", "jvl", "Bsf", + "Dsv", "btv", "jvv", "vye", "vyd", "nwe", "rwu", "nwd", "rwt", "Dse", "bsu", "Dsd", "jtu", + "bst", "jtt", "vyF", "nwF", "rwh", "DsF", "bsh", "jsx", "Ahi", "Ahb", "Axg", "kir", "Axa", + "AxD", "Agr", "Axr", "Bxc", "kyq", "BxE", "kyn", "BxC", "BxB", "Awq", "Bxq", "Awn", "Bxn", + "lyo", "szf", "lym", "lyl", "Bwo", "kyf", "Dxo", "lyv", "Dxm", "Bwl", "Dxl", "Awf", "Bwv", + "Dxv", "tze", "tzd", "lye", "nyu", "lyd", "nyt", "Bwe", "Dwu", "Bwd", "bxu", "Dwt", "bxt", + "tzF", "lyF", "nyh", "BwF", "Dwh", "bwx", "Aiq", "Ain", "Ayo", "kjf", "Aym", "Ayl", "Aif", + "Ayv", "kze", "kzd", "Aye", "Byu", "Ayd", "Byt", "szp" + }; + private static final char[] BR_SET = { + 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '*', '+', '-' + }; + private static final String[] PDF_TTF = { + "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", + "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001", + "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001", "11010", + "11011", "11100", "11101", "11110", "11111", "01", "1111111101010100", "11111101000101001" + }; + private static final int[] ASCII_X = { + 7, 8, 8, 4, 12, 4, 4, 8, 8, 8, 12, 4, 12, 12, 12, 12, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 12, 8, 8, 4, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 8, 8, 8, 4, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 8, 8, 8, 8 + }; + private static final int[] ASCII_Y = { + 26, 10, 20, 15, 18, 21, 10, 28, 23, 24, 22, 20, 13, 16, 17, 19, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 14, 0, 1, 23, 2, 25, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 4, 5, 6, 24, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 21, 27, 9 + }; + private static final int[] MICRO_AUTOSIZE = { + 4, 6, 7, 8, 8, 10, 10, 12, 12, 13, 14, 16, 18, 18, 19, 20, 24, 24, 24, 29, 30, 33, 34, 37, 39, 46, 54, 58, 70, 72, 82, 90, 108, 126, // max codeword counts + 1, 14, 2, 7, 24, 3, 15, 25, 4, 8, 16, 5, 17, 26, 9, 6, 10, 18, 27, 11, 28, 12, 19, 13, 29, 20, 30, 21, 22, 31, 23, 32, 33, 34 // corresponding variant + }; + /* Rows, columns, error codewords, k-offset of valid MicroPDF417 sizes from ISO/IEC 24728:2006 */ + private static final int[] MICRO_VARIANTS = { + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // columns + 11, 14, 17, 20, 24, 28, 8, 11, 14, 17, 20, 23, 26, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, 4, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, // rows + 7, 7, 7, 8, 8, 8, 8, 9, 9, 10, 11, 13, 15, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, 8, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, // k (EC codewords) + 0, 0, 0, 7, 7, 7, 7, 15, 15, 24, 34, 57, 84, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294, 7, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294 // offset + }; + /* Following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24728:2006 tables 10, 11 and 12 */ + private static final int[] RAP_TABLE = { + 1, 8, 36, 19, 9, 25, 1, 1, 8, 36, 19, 9, 27, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, 47, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, // left RAP + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, 19, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, // centre RAP + 9, 8, 36, 19, 17, 33, 1, 9, 8, 36, 19, 17, 35, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, 43, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, // right RAP + 0, 3, 6, 0, 6, 0, 0, 0, 3, 6, 0, 6, 6, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 3, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0 // start cluster + }; + /* Left and Right Row Address Pattern from Table 2 */ + private static final String[] RAPLR = { + "", "221311", "311311", "312211", "222211", "213211", "214111", "223111", + "313111", "322111", "412111", "421111", "331111", "241111", "232111", "231211", "321211", + "411211", "411121", "411112", "321112", "312112", "311212", "311221", "311131", "311122", + "311113", "221113", "221122", "221131", "221221", "222121", "312121", "321121", "231121", + "231112", "222112", "213112", "212212", "212221", "212131", "212122", "212113", "211213", + "211123", "211132", "211141", "211231", "211222", "211312", "211321", "211411", "212311" + }; + /* Centre Row Address Pattern from Table 2 */ + private static final String[] RAPC = { + "", "112231", "121231", "122131", "131131", "131221", "132121", "141121", + "141211", "142111", "133111", "132211", "131311", "122311", "123211", "124111", "115111", + "114211", "114121", "123121", "123112", "122212", "122221", "121321", "121411", "112411", + "113311", "113221", "113212", "113122", "122122", "131122", "131113", "122113", "113113", + "112213", "112222", "112312", "112321", "111421", "111331", "111322", "111232", "111223", + "111133", "111124", "111214", "112114", "121114", "121123", "121132", "112132", "112141" + }; + /* MicroPDF417 coefficients from ISO/IEC 24728:2006 Annex F */ + private static final int[] MICRO_COEFFS = { + /* k = 7 */ + 76, 925, 537, 597, 784, 691, 437, + + /* k = 8 */ + 237, 308, 436, 284, 646, 653, 428, 379, + + /* k = 9 */ + 567, 527, 622, 257, 289, 362, 501, 441, 205, + + /* k = 10 */ + 377, 457, 64, 244, 826, 841, 818, 691, 266, 612, + + /* k = 11 */ + 462, 45, 565, 708, 825, 213, 15, 68, 327, 602, 904, + + /* k = 12 */ + 597, 864, 757, 201, 646, 684, 347, 127, 388, 7, 69, 851, + + /* k = 13 */ + 764, 713, 342, 384, 606, 583, 322, 592, 678, 204, 184, 394, 692, + + /* k = 14 */ + 669, 677, 154, 187, 241, 286, 274, 354, 478, 915, 691, 833, 105, 215, + + /* k = 15 */ + 460, 829, 476, 109, 904, 664, 230, 5, 80, 74, 550, 575, 147, 868, 642, + + /* k = 16 */ + 274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65, + + /* k = 18 */ + 279, 577, 315, 624, 37, 855, 275, 739, 120, 297, 312, 202, 560, 321, 233, 756, + 760, 573, + + /* k = 21 */ + 108, 519, 781, 534, 129, 425, 681, 553, 422, 716, 763, 693, 624, 610, 310, 691, + 347, 165, 193, 259, 568, + + /* k = 26 */ + 443, 284, 887, 544, 788, 93, 477, 760, 331, 608, 269, 121, 159, 830, 446, 893, + 699, 245, 441, 454, 325, 858, 131, 847, 764, 169, + + /* k = 32 */ + 361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, + 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410, + + /* k = 38 */ + 234, 228, 438, 848, 133, 703, 529, 721, 788, 322, 280, 159, 738, 586, 388, 684, + 445, 680, 245, 595, 614, 233, 812, 32, 284, 658, 745, 229, 95, 689, 920, 771, + 554, 289, 231, 125, 117, 518, + + /* k = 44 */ + 476, 36, 659, 848, 678, 64, 764, 840, 157, 915, 470, 876, 109, 25, 632, 405, + 417, 436, 714, 60, 376, 97, 413, 706, 446, 21, 3, 773, 569, 267, 272, 213, + 31, 560, 231, 758, 103, 271, 572, 436, 339, 730, 82, 285, + + /* k = 50 */ + 923, 797, 576, 875, 156, 706, 63, 81, 257, 874, 411, 416, 778, 50, 205, 303, + 188, 535, 909, 155, 637, 230, 534, 96, 575, 102, 264, 233, 919, 593, 865, 26, + 579, 623, 766, 146, 10, 739, 246, 127, 71, 244, 211, 477, 920, 876, 427, 820, + 718, 435 + }; + private int[] codeWords = new int[2700]; + private int codeWordCount; + private Mode symbolMode = Mode.NORMAL; + private int[] inputData; + private Integer columns; + private Integer rows; + private int preferredEccLevel = -1; + private int structuredAppendFileId = 0; + private int structuredAppendPosition = 1; + private int structuredAppendTotal = 1; + + /** + * Creates a new PDF417 symbol instance. + */ + public Pdf417() { + setBarHeight(3); + } + + private static EncodingMode chooseMode(int codeascii) { + if (codeascii >= '0' && codeascii <= '9') { + return EncodingMode.NUM; + } else if (codeascii == '\t' || codeascii == '\n' || codeascii == '\r' || (codeascii >= ' ' && codeascii <= '~')) { + return EncodingMode.TEX; + } else { + return EncodingMode.BYT; + } + } + + private static int getMicroPdf417Variant(int codeWordCount, Integer columns, Integer rows) { + for (int i = 0; i < 34; i++) { + int maxCodewordCount = MICRO_AUTOSIZE[i]; + if (codeWordCount <= maxCodewordCount) { + int variant = MICRO_AUTOSIZE[i + 34]; + int columnsForThisVariant = MICRO_VARIANTS[variant - 1]; + int rowsForThisVariant = MICRO_VARIANTS[variant - 1 + 34]; + if ((columns == null || columns == columnsForThisVariant) && (rows == null || rows == rowsForThisVariant)) { + return variant; + } + } + } + throw new IllegalStateException("Unable to determine MicroPDF417 variant for " + codeWordCount + " codewords"); + } + + /** + * Determines the encoding block groups for the specified data. + */ + private static List createBlocks(int[] data) { + + List blocks = new ArrayList<>(); + Block current = null; + + for (int aData : data) { + EncodingMode mode = chooseMode(aData); + if ((current != null && current.mode == mode) && + (mode != EncodingMode.NUM || current.length < MAX_NUMERIC_COMPACTION_BLOCK_SIZE)) { + current.length++; + } else { + current = new Block(mode); + blocks.add(current); + } + } + + smoothBlocks(blocks); + + return blocks; + } + + /** + * Combines adjacent blocks of different types in very specific scenarios. + */ + private static void smoothBlocks(List blocks) { + + for (int i = 0; i < blocks.size(); i++) { + Block block = blocks.get(i); + EncodingMode last = (i > 0 ? blocks.get(i - 1).mode : EncodingMode.FALSE); + EncodingMode next = (i < blocks.size() - 1 ? blocks.get(i + 1).mode : EncodingMode.FALSE); + if (block.mode == EncodingMode.NUM) { + if (i == 0) { /* first block */ + if (next == EncodingMode.TEX && block.length < 8) { + block.mode = EncodingMode.TEX; + } else if (next == EncodingMode.BYT && block.length == 1) { + block.mode = EncodingMode.BYT; + } + } else if (i == blocks.size() - 1) { /* last block */ + if (last == EncodingMode.TEX && block.length < 7) { + block.mode = EncodingMode.TEX; + } else if (last == EncodingMode.BYT && block.length == 1) { + block.mode = EncodingMode.BYT; + } + } else { /* not first or last block */ + if (last == EncodingMode.BYT && next == EncodingMode.BYT && block.length < 4) { + block.mode = EncodingMode.BYT; + } else if (last == EncodingMode.BYT && next == EncodingMode.TEX && block.length < 4) { + block.mode = EncodingMode.TEX; + } else if (last == EncodingMode.TEX && next == EncodingMode.BYT && block.length < 5) { + block.mode = EncodingMode.TEX; + } else if (last == EncodingMode.TEX && next == EncodingMode.TEX && block.length < 8) { + block.mode = EncodingMode.TEX; + } else if (last == EncodingMode.NUM && next == EncodingMode.TEX && block.length < 8) { + block.mode = EncodingMode.TEX; + } + } + } + } + + mergeBlocks(blocks); + + for (int i = 0; i < blocks.size(); i++) { + Block block = blocks.get(i); + EncodingMode last = (i > 0 ? blocks.get(i - 1).mode : EncodingMode.FALSE); + EncodingMode next = (i < blocks.size() - 1 ? blocks.get(i + 1).mode : EncodingMode.FALSE); + if (block.mode == EncodingMode.TEX && i > 0) { /* not the first */ + if (i == blocks.size() - 1) { /* the last one */ + if (last == EncodingMode.BYT && block.length == 1) { + block.mode = EncodingMode.BYT; + } + } else { /* not the last one */ + if (last == EncodingMode.BYT && next == EncodingMode.BYT && block.length < 5) { + block.mode = EncodingMode.BYT; + } + if (((last == EncodingMode.BYT && next != EncodingMode.BYT) || + (last != EncodingMode.BYT && next == EncodingMode.BYT)) && (block.length < 3)) { + block.mode = EncodingMode.BYT; + } + } + } + } + + mergeBlocks(blocks); + } + + /** + * Combines adjacent blocks of the same type. + */ + private static void mergeBlocks(List blocks) { + for (int i = 1; i < blocks.size(); i++) { + Block b1 = blocks.get(i - 1); + Block b2 = blocks.get(i); + if ((b1.mode == b2.mode) && + (b1.mode != EncodingMode.NUM || b1.length + b2.length <= MAX_NUMERIC_COMPACTION_BLOCK_SIZE)) { + b1.length += b2.length; + blocks.remove(i); + i--; + } + } + } + + /** + * Sets the default bar height (height of a single row) for this symbol (default value is 3). + * + * @param barHeight the default bar height for this symbol + */ + @Override + public void setBarHeight(int barHeight) { + super.setBarHeight(barHeight); + } + + /** + * Returns the number of data columns used by this symbol, or {@code null} + * if the number of data columns has not been set. + * + * @return the number of data columns used by this symbol + */ + public Integer getDataColumns() { + return columns; + } + + /** + * Sets the width of the symbol by specifying the number of columns + * of data codewords. Valid values are 1-30 for PDF417 and 1-4 + * for MicroPDF417. + * + * @param columns the number of data columns in the symbol + */ + public void setDataColumns(int columns) { + this.columns = columns; + } + + /** + * Returns the number of rows used by this symbol, or {@code null} if + * the number of rows has not been set. + * + * @return the number of rows used by this symbol + */ + public Integer getRows() { + return rows; + } + + /** + * Sets the height of the symbol by specifying the number of rows + * of data codewords. Valid values are 3-90 for PDF417 and 4-44 + * for MicroPDF417. + * + * @param rows the number of rows in the symbol + */ + public void setRows(int rows) { + this.rows = rows; + } + + /** + * Set the amount of the symbol which is dedicated to error correction + * codewords. The number of codewords of error correction data is + * determined by 2(eccLevel + 1). This attribute is ignored + * when using {@link Mode#MICRO micro} mode. + * + * @param eccLevel level of error correction (0-8) + */ + public void setPreferredEccLevel(int eccLevel) { + if (eccLevel < 0 || eccLevel > 8) { + throw new IllegalArgumentException("ECC level must be between 0 and 8."); + } + preferredEccLevel = eccLevel; + } + + /** + * Forces the use of the specified MicroPDF417 variant. Only valid + * when using {@link Mode#MICRO micro} mode. + * + * @param variant the MicroPDF417 variant to use + */ + public void setVariant(int variant) { + if (symbolMode != Mode.MICRO) { + throw new IllegalArgumentException("Can only set variant when using MICRO mode."); + } + if (variant < 1 || variant > 34) { + throw new IllegalArgumentException("Variant must be between 1 and 34."); + } + this.columns = MICRO_VARIANTS[variant - 1]; + this.rows = MICRO_VARIANTS[variant - 1 + 34]; + } + + /** + * Returns the position of this PDF417 symbol in a series of symbols using structured append + * (Macro PDF417). If this symbol is not part of such a series, this method will return 1. + * + * @return the position of this PDF417 symbol in a series of symbols using structured append + */ + public int getStructuredAppendPosition() { + return structuredAppendPosition; + } + + /** + * If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format + * (Macro PDF417), this method sets the position of this symbol in the series. Valid values are + * 1 through 99,999 inclusive. + * + * @param position the position of this PDF417 symbol in the structured append series + */ + public void setStructuredAppendPosition(int position) { + if (position < 1 || position > 99_999) { + throw new IllegalArgumentException("Invalid PDF417 structured append position: " + position); + } + this.structuredAppendPosition = position; + } + + /** + * Returns the size of the series of PDF417 symbols using structured append (Macro PDF417) that + * this symbol is part of. If this symbol is not part of a structured append series, this method + * will return 1. + * + * @return size of the series that this symbol is part of + */ + public int getStructuredAppendTotal() { + return structuredAppendTotal; + } + + /** + * If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format + * (Macro PDF417), this method sets the total number of symbols in the series. Valid values are + * 1 through 99,999 inclusive. A value of 1 indicates that this symbol is not part of a structured + * append series. + * + * @param total the total number of PDF417 symbols in the structured append series + */ + public void setStructuredAppendTotal(int total) { + if (total < 1 || total > 99_999) { + throw new IllegalArgumentException("Invalid PDF417 structured append total: " + total); + } + this.structuredAppendTotal = total; + } + + /** + * Returns the unique file ID of the series of PDF417 symbols using structured append (Macro PDF417) + * that this symbol is part of. If this symbol is not part of a structured append series, this method + * will return 0. + * + * @return the unique file ID for the series that this symbol is part of + */ + public int getStructuredAppendFileId() { + return structuredAppendFileId; + } + + /** + * If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format + * (Macro PDF417), this method sets the unique file ID for the series. Valid values are 0 through + * 899 inclusive. + * + * @param fileId the unique file ID for the series that this symbol is part of + */ + public void setStructuredAppendFileId(int fileId) { + if (fileId < 0 || fileId > 899) { + throw new IllegalArgumentException("Invalid PDF417 structured append file ID: " + fileId); + } + this.structuredAppendFileId = fileId; + } + + public Mode getMode() { + return symbolMode; + } + + public void setMode(Mode mode) { + symbolMode = mode; + } + + @Override + public boolean encode() { + + eciProcess(); + + int sourceLength = inputBytes.length; + inputData = new int[sourceLength]; + for (int i = 0; i < sourceLength; i++) { + inputData[i] = inputBytes[i] & 0xFF; + } + + boolean ok; + switch (symbolMode) { + case MICRO: + ok = processMicroPdf417(); + break; + case NORMAL: + case TRUNCATED: + default: + ok = processPdf417(); + break; + } + + if (ok) { + plotSymbol(); + } + + return ok; + } + + private boolean processPdf417() { + int j, loop, offset; + int[] mccorrection = new int[520]; + int total; + int c1, c2, c3; + int[] dummy = new int[35]; + StringBuilder codebarre; + int selectedECCLevel; + String bin; + + List blocks = createBlocks(inputData); + + /* now compress the data */ + codeWordCount = 0; + + if (readerInit) { + codeWords[codeWordCount] = 921; /* Reader Initialisation */ + codeWordCount++; + } + + if (eciMode != 3) { + /* Encoding ECI assignment number, from ISO/IEC 15438 Table 8 */ + if (eciMode <= 899) { + codeWords[codeWordCount] = 927; + codeWordCount++; + codeWords[codeWordCount] = eciMode; + codeWordCount++; + } + + if ((eciMode >= 900) && (eciMode <= 810899)) { + codeWords[codeWordCount] = 926; + codeWordCount++; + codeWords[codeWordCount] = (eciMode / 900) - 1; + codeWordCount++; + codeWords[codeWordCount] = eciMode % 900; + codeWordCount++; + } + + if ((eciMode >= 810900) && (eciMode <= 811799)) { + codeWords[codeWordCount] = 925; + codeWordCount++; + codeWords[codeWordCount] = eciMode - 810900; + codeWordCount++; + } + } + + int blockCount = 0; + for (int i = 0; i < blocks.size(); i++) { + Block block = blocks.get(i); + switch (block.mode) { + case TEX: + /* text mode */ + boolean firstBlock = (i == 0); + processText(blockCount, block.length, firstBlock); + break; + case BYT: + /* octet stream mode */ + EncodingMode lastMode = (i == 0 ? EncodingMode.TEX : blocks.get(i - 1).mode); + processBytes(blockCount, block.length, lastMode); + break; + case NUM: + /* numeric mode */ + processNumbers(inputData, blockCount, block.length, false); + break; + default: + throw new IllegalStateException("Unknown block type: " + block.mode); + } + blockCount += block.length; + } + + addMacroCodewords(); + + encodeInfo.append("Codewords: "); + for (int i = 0; i < codeWordCount; i++) { + encodeInfo.append(Integer.toString(codeWords[i])).append(" "); + } + encodeInfo.append("\n"); + + /* Now take care of the number of CWs per row */ + + // if we have to default the ECC level, do so per the + // recommendations in the specification (Table E.1) + selectedECCLevel = preferredEccLevel; + if (selectedECCLevel < 0) { + if (codeWordCount <= 40) { + selectedECCLevel = 2; + } else if (codeWordCount <= 160) { + selectedECCLevel = 3; + } else if (codeWordCount <= 320) { + selectedECCLevel = 4; + } else if (codeWordCount <= 863) { + selectedECCLevel = 5; + } else { + selectedECCLevel = 6; + } + } + + int k = 1 << (selectedECCLevel + 1); // error correction codeword count + int dataCodeWordCount = codeWordCount + k + 1; // not including padding + + if (validateRows(3, 90) || validateColumns(1, 30)) { + return false; + } + + if (columns != null) { + if (rows != null) { + // user specified both columns and rows; make sure the data fits + if (columns * rows < dataCodeWordCount) { + errorMsg.append("Too few rows (" + rows + ") and columns (" + columns + ") to hold codewords (" + dataCodeWordCount + ")"); + return false; + } + } else { + // user only specified column count; figure out row count + rows = (int) Math.ceil(dataCodeWordCount / (double) columns); + } + } else { + if (rows != null) { + // user only specified row count; figure out column count + columns = (int) Math.ceil(dataCodeWordCount / (double) rows); + } else { + // user didn't specify columns or rows; figure both out + columns = (int) (0.5 + Math.sqrt((dataCodeWordCount - 1) / 3.0)); + rows = (int) Math.ceil(dataCodeWordCount / (double) columns); + } + } + + if (validateRows(3, 90) || validateColumns(1, 30)) { + return false; + } + + /* add the padding */ + int paddingCount = (columns * rows) - codeWordCount - k - 1; + while (paddingCount > 0) { + codeWords[codeWordCount] = 900; + codeWordCount++; + paddingCount--; + } + + /* add the length descriptor */ + System.arraycopy(codeWords, 0, codeWords, 1, codeWordCount); + codeWordCount++; + codeWords[0] = codeWordCount; + + /* 796 - we now take care of the Reed Solomon codes */ + switch (selectedECCLevel) { + case 1: + offset = 2; + break; + case 2: + offset = 6; + break; + case 3: + offset = 14; + break; + case 4: + offset = 30; + break; + case 5: + offset = 62; + break; + case 6: + offset = 126; + break; + case 7: + offset = 254; + break; + case 8: + offset = 510; + break; + default: + offset = 0; + break; + } + + for (loop = 0; loop < 520; loop++) { + mccorrection[loop] = 0; + } + + for (int i = 0; i < codeWordCount; i++) { + total = (codeWords[i] + mccorrection[k - 1]) % 929; + for (j = k - 1; j > 0; j--) { + mccorrection[j] = (mccorrection[j - 1] + 929 - (total * COEFRS[offset + j]) % 929) % 929; + } + mccorrection[0] = (929 - (total * COEFRS[offset + j]) % 929) % 929; + } + + encodeInfo.append("Data Codewords: ").append(codeWordCount).append("\n"); + encodeInfo.append("ECC Codewords: ").append(k).append("\n"); + + /* we add these codes to the string */ + for (int i = k - 1; i >= 0; i--) { + codeWords[codeWordCount++] = mccorrection[i] != 0 ? 929 - mccorrection[i] : 0; + } + + /* make sure total codeword count isn't too high */ + if (codeWordCount > 929) { + errorMsg.append("Too many codewords required (" + codeWordCount + ", but max is 929)"); + return false; + } + /* 818 - The CW string is finished */ + c1 = (rows - 1) / 3; + c2 = (selectedECCLevel * 3) + (rows - 1) % 3; + c3 = columns - 1; + + readable = new StringBuilder(); + rowCount = rows; + pattern = new String[rows]; + rowHeight = new int[rows]; + encodeInfo.append("Grid Size: ").append(columns).append(" X ").append(rows).append("\n"); + + /* we now encode each row */ + for (int i = 0; i < rows; i++) { + for (j = 0; j < columns; j++) { + dummy[j + 1] = codeWords[i * columns + j]; + } + k = (i / 3) * 30; + switch (i % 3) { + case 0: + offset = 0; // cluster 0 + dummy[0] = k + c1; // left row indicator + dummy[columns + 1] = k + c3; // right row indicator + break; + case 1: + offset = 929; // cluster 3 + dummy[0] = k + c2; // left row indicator + dummy[columns + 1] = k + c1; // right row indicator + break; + case 2: + offset = 1858; // cluster 6 + dummy[0] = k + c3; // left row indicator + dummy[columns + 1] = k + c2; // right row indicator + break; + } + codebarre = new StringBuilder("+*"); + for (j = 0; j <= columns + 1; j++) { + if (!(symbolMode == Mode.TRUNCATED && j > columns)) { + codebarre.append(CODAGEMC[offset + dummy[j]]); + codebarre.append("*"); + } + } + if (symbolMode != Mode.TRUNCATED) { + codebarre.append("-"); + } + bin = ""; + for (j = 0; j < codebarre.length(); j++) { + bin += PDF_TTF[positionOf(codebarre.charAt(j), BR_SET)]; + } + pattern[i] = bin2pat(bin); + rowHeight[i] = defaultHeight; + } + return true; + } + + private boolean processMicroPdf417() { /* like PDF417 only much smaller! */ + + int k, j, longueur, offset; + int total; + int LeftRAPStart, CentreRAPStart, RightRAPStart, StartCluster; + int LeftRAP, CentreRAP, RightRAP, Cluster, flip, loop; + String codebarre; + int[] dummy = new int[5]; + int[] mccorrection = new int[50]; + StringBuilder bin; + + /* Encoding starts out the same as PDF417, so use the same code */ + + List blocks = createBlocks(inputData); + + /* 541 - now compress the data */ + codeWordCount = 0; + if (readerInit) { + codeWords[codeWordCount] = 921; /* Reader Initialisation */ + codeWordCount++; + } + + if (eciMode != 3) { + /* Encoding ECI assignment number, from ISO/IEC 15438 Table 8 */ + if (eciMode <= 899) { + codeWords[codeWordCount] = 927; + codeWordCount++; + codeWords[codeWordCount] = eciMode; + codeWordCount++; + } + + if ((eciMode >= 900) && (eciMode <= 810899)) { + codeWords[codeWordCount] = 926; + codeWordCount++; + codeWords[codeWordCount] = (eciMode / 900) - 1; + codeWordCount++; + codeWords[codeWordCount] = eciMode % 900; + codeWordCount++; + } + + if ((eciMode >= 810900) && (eciMode <= 811799)) { + codeWords[codeWordCount] = 925; + codeWordCount++; + codeWords[codeWordCount] = eciMode - 810900; + codeWordCount++; + } + } + + int blockCount = 0; + for (int i = 0; i < blocks.size(); i++) { + Block block = blocks.get(i); + switch (block.mode) { + case TEX: + /* text mode */ + processText(blockCount, block.length, false); // TODO: this shouldn't always be false? + break; + case BYT: + /* octet stream mode */ + EncodingMode lastMode = (i == 0 ? EncodingMode.TEX : blocks.get(i - 1).mode); + processBytes(blockCount, block.length, lastMode); + break; + case NUM: + /* numeric mode */ + processNumbers(inputData, blockCount, block.length, false); + break; + default: + throw new IllegalStateException("Unknown block type: " + block.mode); + } + blockCount += block.length; + } + + addMacroCodewords(); + + encodeInfo.append("Codewords: "); + for (int i = 0; i < codeWordCount; i++) { + encodeInfo.append(Integer.toString(codeWords[i])).append(" "); + } + encodeInfo.append("\n"); + + /* This is where it all changes! */ + + if (validateRows(4, 44) || validateColumns(1, 4)) { + return false; + } + + if (columns != null) { + int max; + switch (columns) { + case 1: + max = 20; + break; + case 2: + max = 37; + break; + case 3: + max = 82; + break; + case 4: + max = 126; + break; + default: + throw new IllegalStateException("Invalid column count: " + columns); + } + if (codeWordCount > max) { + errorMsg.append("Too few columns (" + columns + ") to hold data codewords (" + codeWordCount + ")"); + return false; + } + } + + /* Now figure out which variant of the symbol to use and load values accordingly */ + + int variant = getMicroPdf417Variant(codeWordCount, columns, rows); + + /* Now we have the variant we can load the data */ + + variant--; + columns = MICRO_VARIANTS[variant]; /* columns */ + rows = MICRO_VARIANTS[variant + 34]; /* rows */ + k = MICRO_VARIANTS[variant + 68]; /* number of EC CWs */ + longueur = (columns * rows) - k; /* number of non-EC CWs */ + int padding = longueur - codeWordCount; /* amount of padding required */ + offset = MICRO_VARIANTS[variant + 102]; /* coefficient offset */ + + encodeInfo.append("Data Codewords: ").append(longueur).append("\n"); + encodeInfo.append("ECC Codewords: ").append(k).append("\n"); + + /* We add the padding */ + while (padding > 0) { + codeWords[codeWordCount] = 900; + codeWordCount++; + padding--; + } + + /* Reed-Solomon error correction */ + longueur = codeWordCount; + for (loop = 0; loop < 50; loop++) { + mccorrection[loop] = 0; + } + + for (int i = 0; i < longueur; i++) { + total = (codeWords[i] + mccorrection[k - 1]) % 929; + for (j = k - 1; j >= 0; j--) { + if (j == 0) { + mccorrection[j] = (929 - (total * MICRO_COEFFS[offset + j]) % 929) % 929; + } else { + mccorrection[j] = (mccorrection[j - 1] + 929 - (total * MICRO_COEFFS[offset + j]) % 929) % 929; + } + } + } + + for (j = 0; j < k; j++) { + if (mccorrection[j] != 0) { + mccorrection[j] = 929 - mccorrection[j]; + } + } + /* we add these codes to the string */ + for (int i = k - 1; i >= 0; i--) { + codeWords[codeWordCount] = mccorrection[i]; + codeWordCount++; + } + + /* Now get the RAP (Row Address Pattern) start values */ + LeftRAPStart = RAP_TABLE[variant]; + CentreRAPStart = RAP_TABLE[variant + 34]; + RightRAPStart = RAP_TABLE[variant + 68]; + StartCluster = RAP_TABLE[variant + 102] / 3; + + /* That's all values loaded, get on with the encoding */ + + LeftRAP = LeftRAPStart; + CentreRAP = CentreRAPStart; + RightRAP = RightRAPStart; + Cluster = StartCluster; /* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and Cluster(6) */ + + readable = new StringBuilder(); + pattern = new String[rows]; + rowCount = rows; + rowHeight = new int[rows]; + + encodeInfo.append("Grid Size: ").append(columns).append(" X ").append(rowCount).append("\n"); + + for (int i = 0; i < rows; i++) { + codebarre = ""; + offset = 929 * Cluster; + for (j = 0; j < 5; j++) { + dummy[j] = 0; + } + for (j = 0; j < columns; j++) { + dummy[j + 1] = codeWords[i * columns + j]; + } + + /* Copy the data into codebarre */ + codebarre += RAPLR[LeftRAP]; + codebarre += "1"; + codebarre += CODAGEMC[offset + dummy[1]]; + codebarre += "1"; + if (columns == 3) { + codebarre += RAPC[CentreRAP]; + } + if (columns >= 2) { + codebarre += "1"; + codebarre += CODAGEMC[offset + dummy[2]]; + codebarre += "1"; + } + if (columns == 4) { + codebarre += RAPC[CentreRAP]; + } + if (columns >= 3) { + codebarre += "1"; + codebarre += CODAGEMC[offset + dummy[3]]; + codebarre += "1"; + } + if (columns == 4) { + codebarre += "1"; + codebarre += CODAGEMC[offset + dummy[4]]; + codebarre += "1"; + } + codebarre += RAPLR[RightRAP]; + codebarre += "1"; /* stop */ + + /* Now codebarre is a mixture of letters and numbers */ + + flip = 1; + bin = new StringBuilder(); + for (loop = 0; loop < codebarre.length(); loop++) { + if ((codebarre.charAt(loop) >= '0') && (codebarre.charAt(loop) <= '9')) { + for (k = 0; k < Character.getNumericValue(codebarre.charAt(loop)); k++) { + if (flip == 0) { + bin.append('0'); + } else { + bin.append('1'); + } + } + if (flip == 0) { + flip = 1; + } else { + flip = 0; + } + } else { + bin.append(PDF_TTF[positionOf(codebarre.charAt(loop), BR_SET)]); + } + } + + /* so now pattern[] holds the string of '1's and '0's. - copy this to the symbol */ + pattern[i] = bin2pat(bin.toString()); + rowHeight[i] = defaultHeight; + + /* Set up RAPs and Cluster for next row */ + LeftRAP++; + CentreRAP++; + RightRAP++; + Cluster++; + + if (LeftRAP == 53) { + LeftRAP = 1; + } + if (CentreRAP == 53) { + CentreRAP = 1; + } + if (RightRAP == 53) { + RightRAP = 1; + } + if (Cluster == 3) { + Cluster = 0; + } + } + return true; + } + + private boolean validateRows(int min, int max) { + if (rows != null) { + if (rows < min) { + errorMsg.append("Too few rows (").append(rows).append(")"); + return true; + } else if (rows > max) { + errorMsg.append("Too many rows (").append(rows).append(")"); + return true; + } + } + return false; + } + + private boolean validateColumns(int min, int max) { + if (columns != null) { + if (columns < min) { + errorMsg.append("Too few columns (").append(columns).append(")"); + return true; + } else if (columns > max) { + errorMsg.append("Too many columns (").append(columns).append(")"); + return true; + } + } + return false; + } + + private void processText(int start, int length, boolean skipLatch) { + int j, blockIndext, curtable, wnet; + int codeascii; + int[] listet0 = new int[5000]; + int[] listet1 = new int[5000]; + int[] chainet = new int[5000]; + + wnet = 0; + + for (j = 0; j < 1000; j++) { + listet0[j] = 0; + } + /* listet will contain the table numbers and the value of each characters */ + for (blockIndext = 0; blockIndext < length; blockIndext++) { + codeascii = inputData[start + blockIndext]; + switch (codeascii) { + case '\t': + listet0[blockIndext] = 12; + listet1[blockIndext] = 12; + break; + case '\n': + listet0[blockIndext] = 8; + listet1[blockIndext] = 15; + break; + case 13: + listet0[blockIndext] = 12; + listet1[blockIndext] = 11; + break; + default: + listet0[blockIndext] = ASCII_X[codeascii - 32]; + listet1[blockIndext] = ASCII_Y[codeascii - 32]; + break; + } + } + + curtable = 1; /* default table */ + for (j = 0; j < length; j++) { + if ((listet0[j] & curtable) != 0) { /* The character is in the current table */ + chainet[wnet] = listet1[j]; + wnet++; + } else { /* Obliged to change table */ + boolean flag = false; /* True if we change table for only one character */ + if (j == (length - 1)) { + flag = true; + } else { + if ((listet0[j] & listet0[j + 1]) == 0) { + flag = true; + } + } + + if (flag) { /* we change only one character - look for temporary switch */ + if (((listet0[j] & 1) != 0) && (curtable == 2)) { /* T_UPP */ + chainet[wnet] = 27; + chainet[wnet + 1] = listet1[j]; + wnet += 2; + } + if ((listet0[j] & 8) != 0) { /* T_PUN */ + chainet[wnet] = 29; + chainet[wnet + 1] = listet1[j]; + wnet += 2; + } + if (!((((listet0[j] & 1) != 0) && (curtable == 2)) || ((listet0[j] & 8) != 0))) { + /* No temporary switch available */ + flag = false; + } + } + + if (!(flag)) { + int newtable; + + if (j == (length - 1)) { + newtable = listet0[j]; + } else { + if ((listet0[j] & listet0[j + 1]) == 0) { + newtable = listet0[j]; + } else { + newtable = listet0[j] & listet0[j + 1]; + } + } + + /* Maintain the first if several tables are possible */ + switch (newtable) { + case 3: + case 5: + case 7: + case 9: + case 11: + case 13: + case 15: + newtable = 1; + break; + case 6: + case 10: + case 14: + newtable = 2; + break; + case 12: + newtable = 4; + break; + } + + /* select the switch */ + switch (curtable) { + case 1: + switch (newtable) { + case 2: + chainet[wnet] = 27; + wnet++; + break; + case 4: + chainet[wnet] = 28; + wnet++; + break; + case 8: + chainet[wnet] = 28; + wnet++; + chainet[wnet] = 25; + wnet++; + break; + } + break; + case 2: + switch (newtable) { + case 1: + chainet[wnet] = 28; + wnet++; + chainet[wnet] = 28; + wnet++; + break; + case 4: + chainet[wnet] = 28; + wnet++; + break; + case 8: + chainet[wnet] = 28; + wnet++; + chainet[wnet] = 25; + wnet++; + break; + } + break; + case 4: + switch (newtable) { + case 1: + chainet[wnet] = 28; + wnet++; + break; + case 2: + chainet[wnet] = 27; + wnet++; + break; + case 8: + chainet[wnet] = 25; + wnet++; + break; + } + break; + case 8: + switch (newtable) { + case 1: + chainet[wnet] = 29; + wnet++; + break; + case 2: + chainet[wnet] = 29; + wnet++; + chainet[wnet] = 27; + wnet++; + break; + case 4: + chainet[wnet] = 29; + wnet++; + chainet[wnet] = 28; + wnet++; + break; + } + break; + } + curtable = newtable; + /* at last we add the character */ + chainet[wnet] = listet1[j]; + wnet++; + } + } + } + + if ((wnet & 1) != 0) { + chainet[wnet] = 29; + wnet++; + } + + /* Now translate the string chainet into codewords */ + + if (!skipLatch) { + // text compaction mode is the default mode for PDF417, + // so no need for an explicit latch if this is the first block + codeWords[codeWordCount] = 900; + codeWordCount++; + } + + for (j = 0; j < wnet; j += 2) { + int cw_number; + + cw_number = (30 * chainet[j]) + chainet[j + 1]; + codeWords[codeWordCount] = cw_number; + codeWordCount++; + } + } + + private void processBytes(int start, int length, EncodingMode lastMode) { + int len = 0; + int chunkLen = 0; + BigInteger mantisa; + BigInteger total; + BigInteger word; + + mantisa = new BigInteger("0"); + total = new BigInteger("0"); + + if (length == 1 && lastMode == EncodingMode.TEX) { + codeWords[codeWordCount++] = 913; + codeWords[codeWordCount++] = inputData[start]; + } else { + /* select the switch for multiple of 6 bytes */ + if (length % 6 == 0) { + codeWords[codeWordCount++] = 924; + } else { + codeWords[codeWordCount++] = 901; + } + + while (len < length) { + chunkLen = length - len; + if (6 <= chunkLen) /* Take groups of 6 */ { + chunkLen = 6; + len += chunkLen; + total = BigInteger.valueOf(0); + + while ((chunkLen--) != 0) { + mantisa = BigInteger.valueOf(inputData[start++]); + total = total.or(mantisa.shiftLeft(chunkLen * 8)); + } + + chunkLen = 5; + + while ((chunkLen--) != 0) { + + word = total.mod(BigInteger.valueOf(900)); + codeWords[codeWordCount + chunkLen] = word.intValue(); + total = total.divide(BigInteger.valueOf(900)); + } + codeWordCount += 5; + } else /* If it remain a group of less than 6 bytes */ { + len += chunkLen; + while ((chunkLen--) != 0) { + codeWords[codeWordCount++] = inputData[start++]; + } + } + } + } + } + + private void processNumbers(int[] data, int start, int length, boolean skipLatch) { + + BigInteger tVal, dVal; + int[] d = new int[16]; + int cw_count; + + if (!skipLatch) { + // we don't need to latch to numeric mode in some cases, e.g. + // during numeric compaction of the Macro PDF417 segment index + codeWords[codeWordCount++] = 902; + } + + StringBuilder t = new StringBuilder(length + 1); + t.append('1'); + for (int i = 0; i < length; i++) { + t.append((char) data[start + i]); + } + + tVal = new BigInteger(t.toString()); + + cw_count = 0; + do { + dVal = tVal.mod(BigInteger.valueOf(900)); + d[cw_count] = dVal.intValue(); + tVal = tVal.divide(BigInteger.valueOf(900)); + cw_count++; + } while (tVal.compareTo(BigInteger.ZERO) == 1); + + for (int i = cw_count - 1; i >= 0; i--) { + codeWords[codeWordCount++] = d[i]; + } + } + + /** + * Adds the Macro PDF417 control block codewords (if any). + */ + private void addMacroCodewords() { + + // if the structured append series size is 1, this isn't + // actually part of a structured append series + if (structuredAppendTotal == 1) { + return; + } + + // add the Macro marker codeword + codeWords[codeWordCount++] = 928; + + // add the segment index, padded with leading zeros to five digits + // use numeric compaction, but no latch + int segmentIndex = structuredAppendPosition - 1; + int[] data = new int[5]; + for (int x = data.length - 1; x >= 0; x--) { + data[x] = '0' + (segmentIndex % 10); + segmentIndex /= 10; + } + processNumbers(data, 0, data.length, true); + + // add the file ID (base 900, which is easy since we limit + // file ID values to the range 0 to 899) + codeWords[codeWordCount++] = structuredAppendFileId; + + // NOTE: we could add the optional segment count field here, but + // it doesn't appear to be necessary... if we do eventually decide + // to add it, it will probably be [923, 001, count1, count2] + + // add the terminator to the last symbol of the series + boolean last = (structuredAppendPosition == structuredAppendTotal); + if (last) { + codeWords[codeWordCount++] = 922; + } + } + + public enum Mode { + /** + * Normal PDF417. + */ + NORMAL, + /** + * Truncated PDF417. + */ + TRUNCATED, + /** + * MicroPDF417. + */ + MICRO + } + + private enum EncodingMode { + FALSE, TEX, BYT, NUM + } + + private static class Block { + + public EncodingMode mode; + public int length; + + public Block(EncodingMode mode) { + this.mode = mode; + this.length = 1; + } + + @Override + public String toString() { + return mode + "x" + length; + } + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Pharmacode.java b/barcode/src/main/java/org/xbib/graphics/barcode/Pharmacode.java new file mode 100755 index 0000000..573490a --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Pharmacode.java @@ -0,0 +1,66 @@ +package org.xbib.graphics.barcode; + +/** + * Implements the Pharmacode + * bar code symbology. + * Pharmacode is used for the identification of pharmaceuticals. The symbology + * is able to encode whole numbers between 3 and 131070. + */ +public class Pharmacode extends Symbol { + + @Override + public boolean encode() { + int tester = 0; + int i; + + StringBuilder inter = new StringBuilder(); + StringBuilder dest = new StringBuilder(); + + if (content.length() > 6) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + + for (i = 0; i < content.length(); i++) { + tester *= 10; + tester += Character.getNumericValue(content.charAt(i)); + } + + if ((tester < 3) || (tester > 131070)) { + errorMsg.append("Data out of range"); + return false; + } + + do { + if ((tester & 1) == 0) { + inter.append("W"); + tester = (tester - 2) / 2; + } else { + inter.append("N"); + tester = (tester - 1) / 2; + } + } while (tester != 0); + + for (i = inter.length() - 1; i >= 0; i--) { + if (inter.charAt(i) == 'W') { + dest.append("32"); + } else { + dest.append("12"); + } + } + + readable = new StringBuilder(); + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Pharmacode2Track.java b/barcode/src/main/java/org/xbib/graphics/barcode/Pharmacode2Track.java new file mode 100755 index 0000000..6be57f6 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Pharmacode2Track.java @@ -0,0 +1,106 @@ +package org.xbib.graphics.barcode; + +import java.awt.geom.Rectangle2D; + +/** + * Implements the Two-Track Pharmacode bar code symbology. + * Pharmacode Two-Track is an alternative system to Pharmacode One-Track used + * for the identification of pharmaceuticals. The symbology is able to encode + * whole numbers between 4 and 64570080. + */ +public class Pharmacode2Track extends Symbol { + + @Override + public boolean encode() { + int i, tester = 0; + StringBuilder inter; + StringBuilder dest; + + if (content.length() > 8) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + + for (i = 0; i < content.length(); i++) { + tester *= 10; + tester += Character.getNumericValue(content.charAt(i)); + } + + if ((tester < 4) || (tester > 64570080)) { + errorMsg.append("Data out of range"); + return false; + } + + inter = new StringBuilder(); + do { + switch (tester % 3) { + case 0: + inter.append("F"); + tester = (tester - 3) / 3; + break; + case 1: + inter.append("D"); + tester = (tester - 1) / 3; + break; + case 2: + inter.append("A"); + tester = (tester - 2) / 3; + break; + } + } + while (tester != 0); + + dest = new StringBuilder(); + for (i = (inter.length() - 1); i >= 0; i--) { + dest.append(inter.charAt(i)); + } + + encodeInfo.append("Encoding: ").append(dest).append("\n"); + + readable = new StringBuilder(); + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + getRectangles().clear(); + x = 0; + w = 1; + y = 0; + h = 0; + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + switch (pattern[0].charAt(xBlock)) { + case 'A': + y = 0; + h = defaultHeight / 2; + break; + case 'D': + y = defaultHeight / 2; + h = defaultHeight / 2; + break; + case 'F': + y = 0; + h = defaultHeight; + break; + } + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + x += 2; + } + symbolWidth = pattern[0].length() * 2; + symbolHeight = defaultHeight; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Pharmazentralnummer.java b/barcode/src/main/java/org/xbib/graphics/barcode/Pharmazentralnummer.java new file mode 100755 index 0000000..6e3bcad --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Pharmazentralnummer.java @@ -0,0 +1,69 @@ +package org.xbib.graphics.barcode; + +/** + * PZN8 is a Code 39 based symbology used by the pharmaceutical industry in + * Germany. PZN8 encodes a 7 digit number and includes a modulo-10 check digit. + */ +public class Pharmazentralnummer extends Symbol { + + /* Pharmazentral Nummer is a Code 3 of 9 symbol with an extra + * check digit. Now generates PZN-8. + */ + @Override + public boolean encode() { + int l = content.length(); + StringBuilder localstr; + int zeroes, count = 0, check_digit; + Code3Of9 c = new Code3Of9(); + + if (l > 7) { + errorMsg.append("Input data too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + localstr = new StringBuilder("-"); + zeroes = 7 - l + 1; + for (int i = 1; i < zeroes; i++) + localstr.append('0'); + + localstr.append(content); + + for (int i = 1; i < 8; i++) { + count += i * Character.getNumericValue(localstr.charAt(i)); + } + + check_digit = count % 11; + if (check_digit == 11) { + check_digit = 0; + } + if (check_digit == 10) { + errorMsg.append("Not a valid PZN identifier"); + return false; + } + + encodeInfo.append("Check Digit: ").append(check_digit).append("\n"); + + localstr.append((char) (check_digit + '0')); + + try { + c.setContent(localstr.toString()); + } catch (Exception e) { + errorMsg.append(e.getMessage()); + return false; + } + + readable = new StringBuilder("PZN").append(localstr); + pattern = new String[1]; + pattern[0] = c.pattern[0]; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Postnet.java b/barcode/src/main/java/org/xbib/graphics/barcode/Postnet.java new file mode 100755 index 0000000..eade347 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Postnet.java @@ -0,0 +1,185 @@ +package org.xbib.graphics.barcode; + +import static org.xbib.graphics.barcode.HumanReadableLocation.NONE; +import static org.xbib.graphics.barcode.HumanReadableLocation.TOP; +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Rectangle2D; + +/** + * Implements POSTNET and + * PLANET + * bar code symbologies. + * POSTNET and PLANET both use numerical input data and include a modulo-10 + * check digit. + */ +public class Postnet extends Symbol { + + private static final String[] PN_TABLE = { + "LLSSS", "SSSLL", "SSLSL", "SSLLS", "SLSSL", "SLSLS", "SLLSS", "LSSSL", "LSSLS", "LSLSS" + }; + + ; + private static final String[] PL_TABLE = { + "SSLLL", "LLLSS", "LLSLS", "LLSSL", "LSLLS", "LSLSL", "LSSLL", "SLLLS", "SLLSL", "SLSLL" + }; + private Mode mode; + + public Postnet() { + this.mode = Mode.POSTNET; + this.defaultHeight = 12; + setHumanReadableLocation(HumanReadableLocation.NONE); + } + + /** + * Returns the barcode mode (PLANET or POSTNET). The default mode is POSTNET. + * + * @return the barcode mode (PLANET or POSTNET) + */ + public Mode getMode() { + return mode; + } + + /** + * Sets the barcode mode (PLANET or POSTNET). The default mode is POSTNET. + * + * @param mode the barcode mode (PLANET or POSTNET) + */ + public void setMode(Mode mode) { + this.mode = mode; + } + + @Override + public boolean encode() { + + boolean retval; + if (mode == Mode.POSTNET) { + retval = makePostnet(); + } else { + retval = makePlanet(); + } + + if (retval) { + plotSymbol(); + } + + return retval; + } + + private boolean makePostnet() { + int i, sum, check_digit; + StringBuilder dest; + + if (content.length() > 38) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + + sum = 0; + dest = new StringBuilder("L"); + + for (i = 0; i < content.length(); i++) { + dest.append(PN_TABLE[content.charAt(i) - '0']); + sum += content.charAt(i) - '0'; + } + + check_digit = (10 - (sum % 10)) % 10; + encodeInfo.append("Check Digit: ").append(check_digit).append("\n"); + + dest.append(PN_TABLE[check_digit]); + dest.append("L"); + + encodeInfo.append("Encoding: ").append(dest).append("\n"); + readable = new StringBuilder(content); + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + + return true; + } + + private boolean makePlanet() { + int i, sum, check_digit; + StringBuilder dest; + + if (content.length() > 38) { + errorMsg.append("Input too long"); + return false; + } + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + + sum = 0; + dest = new StringBuilder("L"); + + for (i = 0; i < content.length(); i++) { + dest.append(PL_TABLE[content.charAt(i) - '0']); + sum += content.charAt(i) - '0'; + } + + check_digit = (10 - (sum % 10)) % 10; + encodeInfo.append("Check Digit: ").append(check_digit).append("\n"); + + dest.append(PL_TABLE[check_digit]); + dest.append("L"); + + encodeInfo.append("Encoding: ").append(dest).append("\n"); + readable = new StringBuilder(content); + pattern = new String[]{dest.toString()}; + rowCount = 1; + rowHeight = new int[]{-1}; + + return true; + } + + @Override + protected void plotSymbol() { + int xBlock, shortHeight; + double x, y, w, h; + getRectangles().clear(); + getTexts().clear(); + int baseY; + if (getHumanReadableLocation() == TOP) { + baseY = getTheoreticalHumanReadableHeight(); + } else { + baseY = 0; + } + x = 0; + w = moduleWidth; + shortHeight = (int) (0.4 * defaultHeight); + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + if (pattern[0].charAt(xBlock) == 'L') { + y = baseY; + h = defaultHeight; + } else { + y = baseY + defaultHeight - shortHeight; + h = shortHeight; + } + getRectangles().add(new Rectangle2D.Double(x, y, w, h)); + x += (2.5 * w); + } + symbolWidth = (int) Math.ceil(((pattern[0].length() - 1) * 2.5 * w) + w); // final bar doesn't need extra whitespace + symbolHeight = defaultHeight; + if (getHumanReadableLocation() != NONE && readable.length() > 0) { + double baseline; + if (getHumanReadableLocation() == TOP) { + baseline = fontSize; + } else { + baseline = getHeight() + fontSize; + } + double centerX = getWidth() / 2.0; + getTexts().add(new TextBox(centerX, baseline, readable.toString())); + } + } + + public enum Mode { + PLANET, POSTNET + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/QrCode.java b/barcode/src/main/java/org/xbib/graphics/barcode/QrCode.java new file mode 100755 index 0000000..a528ddf --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/QrCode.java @@ -0,0 +1,2008 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.ReedSolomon; +import java.io.UnsupportedEncodingException; + +/** + * Implements QR Code bar code symbology According to ISO/IEC 18004:2015 + * The maximum capacity of a (version 40) QR Code symbol is 7089 numeric digits, + * 4296 alphanumeric characters or 2953 bytes of data. QR Code symbols can also + * be used to encode GS1 data. QR Code symbols can encode characters in the + * Latin-1 set and Kanji characters which are members of the Shift-JIS encoding + * scheme. + */ +public class QrCode extends Symbol { + + /* Table 5 - Encoding/Decoding table for Alphanumeric mode */ + private final char[] rhodium = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', + 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', '.', '/', ':' + }; + private final int[] qr_data_codewords_L = { + 19, 34, 55, 80, 108, 136, 156, 194, 232, 274, 324, 370, 428, 461, 523, 589, 647, + 721, 795, 861, 932, 1006, 1094, 1174, 1276, 1370, 1468, 1531, 1631, + 1735, 1843, 1955, 2071, 2191, 2306, 2434, 2566, 2702, 2812, 2956 + }; + private final int[] qr_data_codewords_M = { + 16, 28, 44, 64, 86, 108, 124, 154, 182, 216, 254, 290, 334, 365, 415, 453, 507, + 563, 627, 669, 714, 782, 860, 914, 1000, 1062, 1128, 1193, 1267, + 1373, 1455, 1541, 1631, 1725, 1812, 1914, 1992, 2102, 2216, 2334 + }; + private final int[] qr_data_codewords_Q = { + 13, 22, 34, 48, 62, 76, 88, 110, 132, 154, 180, 206, 244, 261, 295, 325, 367, + 397, 445, 485, 512, 568, 614, 664, 718, 754, 808, 871, 911, + 985, 1033, 1115, 1171, 1231, 1286, 1354, 1426, 1502, 1582, 1666 + }; + private final int[] qr_data_codewords_H = { + 9, 16, 26, 36, 46, 60, 66, 86, 100, 122, 140, 158, 180, 197, 223, 253, 283, + 313, 341, 385, 406, 442, 464, 514, 538, 596, 628, 661, 701, + 745, 793, 845, 901, 961, 986, 1054, 1096, 1142, 1222, 1276 + }; + private final int[] qr_blocks_L = { + 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, + 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 + }; + private final int[] qr_blocks_M = { + 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, + 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 + }; + private final int[] qr_blocks_Q = { + 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, + 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 + }; + private final int[] qr_blocks_H = { + 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, + 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 + }; + private final int[] qr_total_codewords = { + 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, + 901, 991, 1085, 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, + 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706 + }; + private final int[] qr_sizes = { + 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, + 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157, 161, 165, 169, 173, 177 + }; + private final int[] qr_align_loopsize = { + 0, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7 + }; + private final int[] qr_table_e1 = { + 6, 18, 0, 0, 0, 0, 0, + 6, 22, 0, 0, 0, 0, 0, + 6, 26, 0, 0, 0, 0, 0, + 6, 30, 0, 0, 0, 0, 0, + 6, 34, 0, 0, 0, 0, 0, + 6, 22, 38, 0, 0, 0, 0, + 6, 24, 42, 0, 0, 0, 0, + 6, 26, 46, 0, 0, 0, 0, + 6, 28, 50, 0, 0, 0, 0, + 6, 30, 54, 0, 0, 0, 0, + 6, 32, 58, 0, 0, 0, 0, + 6, 34, 62, 0, 0, 0, 0, + 6, 26, 46, 66, 0, 0, 0, + 6, 26, 48, 70, 0, 0, 0, + 6, 26, 50, 74, 0, 0, 0, + 6, 30, 54, 78, 0, 0, 0, + 6, 30, 56, 82, 0, 0, 0, + 6, 30, 58, 86, 0, 0, 0, + 6, 34, 62, 90, 0, 0, 0, + 6, 28, 50, 72, 94, 0, 0, + 6, 26, 50, 74, 98, 0, 0, + 6, 30, 54, 78, 102, 0, 0, + 6, 28, 54, 80, 106, 0, 0, + 6, 32, 58, 84, 110, 0, 0, + 6, 30, 58, 86, 114, 0, 0, + 6, 34, 62, 90, 118, 0, 0, + 6, 26, 50, 74, 98, 122, 0, + 6, 30, 54, 78, 102, 126, 0, + 6, 26, 52, 78, 104, 130, 0, + 6, 30, 56, 82, 108, 134, 0, + 6, 34, 60, 86, 112, 138, 0, + 6, 30, 58, 86, 114, 142, 0, + 6, 34, 62, 90, 118, 146, 0, + 6, 30, 54, 78, 102, 126, 150, + 6, 24, 50, 76, 102, 128, 154, + 6, 28, 54, 80, 106, 132, 158, + 6, 32, 58, 84, 110, 136, 162, + 6, 26, 54, 82, 110, 138, 166, + 6, 30, 58, 86, 114, 142, 170 + }; + private final int[] qr_annex_c = { + /* Format information bit sequences */ + 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, 0x77c4, 0x72f3, 0x7daa, 0x789d, + 0x662f, 0x6318, 0x6c41, 0x6976, 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b, + 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed + }; + private final long[] qr_annex_d = { + /* Version information bit sequences */ + 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, + 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab, + 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, + 0x2542e, 0x26a64, 0x27541, 0x28c69 + }; + private qrMode[] inputMode; + private StringBuilder binary; + private int[] datastream; + private int[] fullstream; + private int[] inputData; + private byte[] grid; + private byte[] eval; + private int preferredVersion = 0; + private int inputLength; + private EccMode preferredEccLevel = EccMode.L; + + /** + * Sets the preferred symbol size. This value may be ignored if the data + * string is too large to fit into the specified symbol. Input values + * correspond to symbol sizes as shown in the following table. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Available QR Code sizes

+ * Input

+ * Symbol Size

+ * Input

+ * Symbol Size

+ * 1

+ * 21 x 21

+ * 21

+ * 101 x 101

+ * 2

+ * 25 x 25

+ * 22

+ * 105 x 105

+ * 3

+ * 29 x 29

+ * 23

+ * 109 x 109

+ * 4

+ * 33 x 33

+ * 24

+ * 113 x 113

+ * 5

+ * 37 x 37

+ * 25

+ * 117 x 117

+ * 6

+ * 41 x 41

+ * 26

+ * 121 x 121

+ * 7

+ * 45 x 45

+ * 27

+ * 125 x 125

+ * 8

+ * 49 x 49

+ * 28

+ * 129 x 129

+ * 9

+ * 53 x 53

+ * 29

+ * 133 x 133

+ * 10

+ * 57 x 57

+ * 30

+ * 137 x 137

+ * 11

+ * 61 x 61

+ * 31

+ * 141 x 141

+ * 12

+ * 65 x 65

+ * 32

+ * 145 x 145

+ * 13

+ * 69 x 69

+ * 33

+ * 149 x 149

+ * 14

+ * 73 x 73

+ * 34

+ * 153 x 153

+ * 15

+ * 77 x 77

+ * 35

+ * 157 x 157

+ * 16

+ * 81 x 81

+ * 36

+ * 161 x 161

+ * 17

+ * 85 x 85

+ * 37

+ * 165 x 165

+ * 18

+ * 89 x 89

+ * 38

+ * 169 x 169

+ * 19

+ * 93 x 93

+ * 39

+ * 173 x 173

+ * 20

+ * 97 x 97

+ * 40

+ * 177 x 177

+ * + * @param version Symbol version number required + */ + public void setPreferredVersion(int version) { + preferredVersion = version; + } + + /** + * Set the amount of symbol space allocated to error correction. Levels are + * predefined according to the following table: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
QR Code error correction levels
ECC LevelError Correction CapacityRecovery Capacity
L (default)Approx 20% of symbolApprox 7%
MApprox 37% of symbolApprox 15%
QApprox 55% of symbolApprox 25%
HApprox 65% of symbolApprox 30%
+ * + * @param eccMode Error correction level + */ + public void setEccMode(EccMode eccMode) { + preferredEccLevel = eccMode; + } + + @Override + public boolean encode() { + int i, j; + int est_binlen; + EccMode ecc_level; + int max_cw; + int autosize; + int targetCwCount, version, blocks; + int size; + int bitmask; + StringBuilder bin; + boolean canShrink; + + /* This code uses modeFirstFix to make an estimate of the symbol size + needed to contain the data. It then optimises the encoding and + checks to see if the the data will fit in a smaller + symbol, recalculating the binary length if necessary. + */ + inputMode = new qrMode[content.length()]; + modeFirstFix(); + est_binlen = estimate_binary_length(); + + ecc_level = preferredEccLevel; + switch (preferredEccLevel) { + case L: + max_cw = 2956; + break; + case M: + max_cw = 2334; + break; + case Q: + max_cw = 1666; + break; + case H: + max_cw = 1276; + break; + default: + max_cw = 2956; + break; + } + + autosize = 40; + for (i = 39; i >= 0; i--) { + switch (ecc_level) { + case L: + if ((8 * qr_data_codewords_L[i]) >= est_binlen) { + autosize = i + 1; + } + break; + case M: + if ((8 * qr_data_codewords_M[i]) >= est_binlen) { + autosize = i + 1; + } + break; + case Q: + if ((8 * qr_data_codewords_Q[i]) >= est_binlen) { + autosize = i + 1; + } + break; + case H: + if ((8 * qr_data_codewords_H[i]) >= est_binlen) { + autosize = i + 1; + } + break; + } + } + + // The first guess of symbol size is in autosize. Use this to optimise. + est_binlen = getBinaryLength(autosize); + + if (est_binlen > (8 * max_cw)) { + errorMsg.append("Input too long for selected error correction level"); + return false; + } + + // Now see if the optimised binary will fit in a smaller symbol. + canShrink = true; + + do { + if (autosize == 1) { + canShrink = false; + } else { + if (tribus(autosize - 1, 1, 2, 3) != tribus(autosize, 1, 2, 3)) { + // Length of binary needed to encode the data in the smaller symbol is different, recalculate + est_binlen = getBinaryLength(autosize - 1); + } + + switch (ecc_level) { + case L: + if ((8 * qr_data_codewords_L[autosize - 2]) < est_binlen) { + canShrink = false; + } + break; + case M: + if ((8 * qr_data_codewords_M[autosize - 2]) < est_binlen) { + canShrink = false; + } + break; + case Q: + if ((8 * qr_data_codewords_Q[autosize - 2]) < est_binlen) { + canShrink = false; + } + break; + case H: + if ((8 * qr_data_codewords_H[autosize - 2]) < est_binlen) { + canShrink = false; + } + break; + } + + if (canShrink) { + // Optimisation worked - data will fit in a smaller symbol + autosize--; + } else { + // Data did not fit in the smaller symbol, revert to original size + if (tribus(autosize - 1, 1, 2, 3) != tribus(autosize, 1, 2, 3)) { + est_binlen = getBinaryLength(autosize); + } + } + } + } while (canShrink); + + version = autosize; + + if ((preferredVersion >= 1) && (preferredVersion <= 40)) { + /* If the user has selected a larger symbol than the smallest available, + then use the size the user has selected, and re-optimise for this + symbol size. + */ + if (preferredVersion > version) { + version = preferredVersion; + est_binlen = getBinaryLength(preferredVersion); + } + } + + /* Ensure maxium error correction capacity */ + if (est_binlen <= (qr_data_codewords_M[version - 1] * 8)) { + ecc_level = EccMode.M; + } + if (est_binlen <= (qr_data_codewords_Q[version - 1] * 8)) { + ecc_level = EccMode.Q; + } + if (est_binlen <= (qr_data_codewords_H[version - 1] * 8)) { + ecc_level = EccMode.H; + } + + targetCwCount = qr_data_codewords_L[version - 1]; + blocks = qr_blocks_L[version - 1]; + switch (ecc_level) { + case M: + targetCwCount = qr_data_codewords_M[version - 1]; + blocks = qr_blocks_M[version - 1]; + break; + case Q: + targetCwCount = qr_data_codewords_Q[version - 1]; + blocks = qr_blocks_Q[version - 1]; + break; + case H: + targetCwCount = qr_data_codewords_H[version - 1]; + blocks = qr_blocks_H[version - 1]; + break; + } + + datastream = new int[targetCwCount + 1]; + fullstream = new int[qr_total_codewords[version - 1] + 1]; + + if (!(qr_binary(version, targetCwCount))) { + /* Invalid characters used - stop encoding */ + return false; + } + + add_ecc(version, targetCwCount, blocks); + + size = qr_sizes[version - 1]; + + grid = new byte[size * size]; + + encodeInfo.append("Version: ").append(version).append("\n"); + encodeInfo.append("ECC Level: "); + switch (ecc_level) { + case L: + encodeInfo.append("L\n"); + break; + case M: + encodeInfo.append("M\n"); + break; + case Q: + encodeInfo.append("Q\n"); + break; + case H: + default: + encodeInfo.append("H\n"); + break; + } + + for (i = 0; i < size; i++) { + for (j = 0; j < size; j++) { + grid[(i * size) + j] = 0; + } + } + + setup_grid(size, version); + populate_grid(size, qr_total_codewords[version - 1]); + bitmask = apply_bitmask(size, ecc_level); + encodeInfo.append("Mask Pattern: ").append(Integer.toBinaryString(bitmask)).append("\n"); + add_format_info(size, ecc_level, bitmask); + if (version >= 7) { + add_version_info(size, version); + } + + readable = new StringBuilder(); + pattern = new String[size]; + rowCount = size; + rowHeight = new int[size]; + for (i = 0; i < size; i++) { + bin = new StringBuilder(); + for (j = 0; j < size; j++) { + if ((grid[(i * size) + j] & 0x01) != 0) { + bin.append("1"); + } else { + bin.append("0"); + } + } + pattern[i] = bin2pat(bin.toString()); + rowHeight[i] = 1; + } + + plotSymbol(); + return true; + } + + private void modeFirstFix() { + int i; + + eciProcess(); // Get ECI mode + + if (eciMode == 20) { + /* Shift-JIS encoding, use Kanji mode */ + inputLength = content.length(); + inputData = new int[inputLength]; + for (i = 0; i < inputLength; i++) { + inputData[i] = (int) content.charAt(i); + } + } else { + /* Any other encoding method */ + inputLength = inputBytes.length; + inputData = new int[inputLength]; + for (i = 0; i < inputLength; i++) { + inputData[i] = inputBytes[i] & 0xFF; + } + } + + inputMode = new qrMode[inputLength]; + + for (i = 0; i < inputLength; i++) { + if (inputData[i] > 0xff) { + inputMode[i] = qrMode.KANJI; + } else { + inputMode[i] = qrMode.BINARY; + if (isXAlpha((char) (inputData[i] & 0xFF))) { + inputMode[i] = qrMode.ALPHANUM; + } + if ((inputDataType == DataType.GS1) && (inputData[i] == '[')) { + inputMode[i] = qrMode.ALPHANUM; + } + if (isXNumeric((char) (inputData[i] & 0xff))) { + inputMode[i] = qrMode.NUMERIC; + } + } + } + } + + private int getBinaryLength(int version) { + /* Calculate the actial bitlength of the proposed binary string */ + qrMode currentMode; + int i, j; + int count = 0; + + applyOptimisation(version); + + currentMode = qrMode.NULL; + + if (eciMode != 3) { + count += 12; + } + + if (inputDataType == DataType.GS1) { + count += 4; + } + + for (i = 0; i < inputLength; i++) { + if (inputMode[i] != currentMode) { + count += 4; + switch (inputMode[i]) { + case KANJI: + count += tribus(version, 8, 10, 12); + count += (blockLength(i) * 13); + break; + case BINARY: + count += tribus(version, 8, 16, 16); + for (j = i; j < (i + blockLength(i)); j++) { + if (inputData[j] > 0xff) { + count += 16; + } else { + count += 8; + } + } + break; + case ALPHANUM: + count += tribus(version, 9, 11, 13); + switch (blockLength(i) % 2) { + case 0: + count += (blockLength(i) / 2) * 11; + break; + case 1: + count += ((blockLength(i) - 1) / 2) * 11; + count += 6; + break; + } + break; + case NUMERIC: + count += tribus(version, 10, 12, 14); + switch (blockLength(i) % 3) { + case 0: + count += (blockLength(i) / 3) * 10; + break; + case 1: + count += ((blockLength(i) - 1) / 3) * 10; + count += 4; + break; + case 2: + count += ((blockLength(i) - 2) / 3) * 10; + count += 7; + break; + } + break; + } + currentMode = inputMode[i]; + } + } + + return count; + } + + private void applyOptimisation(int version) { + /* Implements a custom optimisation algorithm, because implementation + of the algorithm shown in Annex J.2 created LONGER binary sequences. + */ + + int blockCount = 0; + int i, j; + qrMode currentMode = qrMode.NULL; + + for (i = 0; i < inputLength; i++) { + if (inputMode[i] != currentMode) { + currentMode = inputMode[i]; + blockCount++; + } + } + + int[] blockLength = new int[blockCount]; + qrMode[] blockMode = new qrMode[blockCount]; + + j = -1; + currentMode = qrMode.NULL; + for (i = 0; i < inputLength; i++) { + if (inputMode[i] != currentMode) { + j++; + blockLength[j] = 1; + blockMode[j] = inputMode[i]; + currentMode = inputMode[i]; + } else { + blockLength[j]++; + } + } + + if (blockCount > 1) { + // Search forward + for (i = 0; i <= (blockCount - 2); i++) { + if (blockMode[i] == qrMode.BINARY) { + switch (blockMode[i + 1]) { + case KANJI: + if (blockLength[i + 1] < tribus(version, 4, 5, 6)) { + blockMode[i + 1] = qrMode.BINARY; + } + break; + case ALPHANUM: + if (blockLength[i + 1] < tribus(version, 7, 8, 9)) { + blockMode[i + 1] = qrMode.BINARY; + } + break; + case NUMERIC: + if (blockLength[i + 1] < tribus(version, 3, 4, 5)) { + blockMode[i + 1] = qrMode.BINARY; + } + break; + } + } + + if ((blockMode[i] == qrMode.ALPHANUM) + && (blockMode[i + 1] == qrMode.NUMERIC)) { + if (blockLength[i + 1] < tribus(version, 6, 8, 10)) { + blockMode[i + 1] = qrMode.ALPHANUM; + } + } + } + + // Search backward + for (i = blockCount - 1; i > 0; i--) { + if (blockMode[i] == qrMode.BINARY) { + switch (blockMode[i - 1]) { + case KANJI: + if (blockLength[i - 1] < tribus(version, 4, 5, 6)) { + blockMode[i - 1] = qrMode.BINARY; + } + break; + case ALPHANUM: + if (blockLength[i - 1] < tribus(version, 7, 8, 9)) { + blockMode[i - 1] = qrMode.BINARY; + } + break; + case NUMERIC: + if (blockLength[i - 1] < tribus(version, 3, 4, 5)) { + blockMode[i - 1] = qrMode.BINARY; + } + break; + } + } + + if ((blockMode[i] == qrMode.ALPHANUM) + && (blockMode[i - 1] == qrMode.NUMERIC)) { + if (blockLength[i - 1] < tribus(version, 6, 8, 10)) { + blockMode[i - 1] = qrMode.ALPHANUM; + } + } + } + } + + j = 0; + for (int block = 0; block < blockCount; block++) { + currentMode = blockMode[block]; + for (i = 0; i < blockLength[block]; i++) { + inputMode[j] = currentMode; + j++; + } + } + } + + private int blockLength(int start) { + /* Find the length of the block starting from 'start' */ + int i, count; + qrMode mode = inputMode[start]; + + count = 0; + i = start; + + do { + count++; + } while (((i + count) < inputLength) && (inputMode[i + count] == mode)); + + return count; + } + + private int tribus(int version, int a, int b, int c) { + /* Choose from three numbers based on version */ + int RetVal; + + RetVal = c; + + if (version < 10) { + RetVal = a; + } + + if ((version >= 10) && (version <= 26)) { + RetVal = b; + } + + return RetVal; + } + + private boolean isXAlpha(char cglyph) { + /* Returns true if input is in exclusive Alphanumeric set (Table J.1) */ + boolean retval = false; + + if ((cglyph >= 'A') && (cglyph <= 'Z')) { + retval = true; + } + switch (cglyph) { + case ' ': + case '$': + case '%': + case '*': + case '+': + case '-': + case '.': + case '/': + case ':': + retval = true; + break; + } + + return retval; + } + + private boolean isXNumeric(char cglyph) { + /* Returns true if input is in exclusive Numeric set (Table J.1) */ + boolean retval; + + if ((cglyph >= '0') && (cglyph <= '9')) { + retval = true; + } else { + retval = false; + } + + return retval; + } + + private int estimate_binary_length() { + /* Make an estimate (worst case scenario) of how long the binary string will be */ + int i, count = 0; + qrMode current = qrMode.NULL; + int a_count = 0; + int n_count = 0; + + if (eciMode != 3) { + count += 12; + } + + if (inputDataType == DataType.GS1) { + count += 4; + } + + for (i = 0; i < inputLength; i++) { + if (inputMode[i] != current) { + switch (inputMode[i]) { + case KANJI: + count += 12 + 4; + current = qrMode.KANJI; + break; + case BINARY: + count += 16 + 4; + current = qrMode.BINARY; + break; + case ALPHANUM: + count += 13 + 4; + current = qrMode.ALPHANUM; + a_count = 0; + break; + case NUMERIC: + count += 14 + 4; + current = qrMode.NUMERIC; + n_count = 0; + break; + } + } + + switch (inputMode[i]) { + case KANJI: + count += 13; + break; + case BINARY: + count += 8; + break; + case ALPHANUM: + a_count++; + if ((a_count & 1) == 0) { + count += 5; // 11 in total + a_count = 0; + } else { + count += 6; + } + break; + case NUMERIC: + n_count++; + if ((n_count % 3) == 0) { + count += 3; // 10 in total + n_count = 0; + } else if ((n_count & 1) == 0) { + count += 3; // 7 in total + } else { + count += 4; + } + break; + } + } + + return count; + } + + private boolean qr_binary(int version, int target_binlen) { + /* Convert input data to a binary stream and add padding */ + int position = 0; + int short_data_block_length, i, scheme = 1; + int padbits; + int current_binlen, current_bytes; + int toggle; + boolean alphanumPercent; + String oneChar; + qrMode data_block; + int jis; + byte[] jisBytes; + int msb, lsb, prod; + int count, first, second, third; + int weight; + + binary = new StringBuilder(); + + /* Note: Shift-JIS characters can be encoded in either Kanji + mode or Byte mode. If no ECI code is given, a sequence in Byte + mode could be interpreted in two ways - for example 0xE4 0x6E + could refer to U+E4 and U+6E (än) or U+8205 (舅). To avoid + Mojibake, if using ECI 20 (i.e. if only valid Shift-JIS + characters are found in the input data), this code will insert + an explicit ECI 20 instruction. Symbols without an ECI can then + be assumed to be ECI 3 (ISO 8859-1). + */ + if (eciMode != 3) { + binary.append("0111"); /* ECI */ + + if ((eciMode >= 0) && (eciMode <= 127)) { + binary.append("0"); + qr_bscan(eciMode, 0x40); + } + + if ((eciMode >= 128) && (eciMode <= 16383)) { + binary.append("10"); + qr_bscan(eciMode, 0x1000); + } + + if ((eciMode >= 16384) && (eciMode <= 999999)) { + binary.append("110"); + qr_bscan(eciMode, 0x100000); + } + } + + if (inputDataType == DataType.GS1) { + binary.append("0101"); /* FNC1 */ + + } + + if (version <= 9) { + scheme = 1; + } else if (version <= 26) { + scheme = 2; + } else { + scheme = 3; + } + + encodeInfo.append("Encoding: "); + + alphanumPercent = false; + + do { + data_block = inputMode[position]; + short_data_block_length = 0; + do { + short_data_block_length++; + } while (((short_data_block_length + position) < inputLength) + && (inputMode[position + short_data_block_length] == data_block)); + + switch (data_block) { + case KANJI: + /* Kanji mode */ + /* Mode indicator */ + binary.append("1000"); + + /* Character count indicator */ + qr_bscan(short_data_block_length, 0x20 << (scheme * 2)); /* scheme = 1..3 */ + + encodeInfo.append("KNJI "); + + /* Character representation */ + for (i = 0; i < short_data_block_length; i++) { + oneChar = ""; + oneChar += (char) inputData[position + i]; + + /* Convert Unicode input to Shift-JIS */ + try { + jisBytes = oneChar.getBytes("SJIS"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Shift-JIS character conversion error"); + return false; + } + + jis = ((jisBytes[0] & 0xFF) << 8) + (jisBytes[1] & 0xFF); + + if (jis > 0x9fff) { + jis -= 0xc140; + } else { + jis -= 0x8140; + } + msb = (jis & 0xff00) >> 8; + lsb = (jis & 0xff); + prod = (msb * 0xc0) + lsb; + + qr_bscan(prod, 0x1000); + + encodeInfo.append(Integer.toString(prod)).append(" "); + } + break; + case BINARY: + /* Byte mode */ + /* Mode indicator */ + binary.append("0100"); + int kanjiModifiedLength = short_data_block_length; + + for (i = 0; i < short_data_block_length; i++) { + if (inputData[position + i] > 0xff) { + kanjiModifiedLength++; + } + } + + /* Character count indicator */ + qr_bscan(kanjiModifiedLength, scheme > 1 ? 0x8000 : 0x80); /* scheme = 1..3 */ + + encodeInfo.append("BYTE "); + + /* Character representation */ + for (i = 0; i < short_data_block_length; i++) { + if (inputData[position + i] > 0xff) { + // Convert Kanji character to Shift-JIS + oneChar = ""; + oneChar += (char) inputData[position + i]; + + /* Convert Unicode input to Shift-JIS */ + try { + jisBytes = oneChar.getBytes("SJIS"); + } catch (UnsupportedEncodingException e) { + errorMsg.append("Shift-JIS character conversion error"); + return false; + } + + qr_bscan((int) jisBytes[0] & 0xff, 0x80); + qr_bscan((int) jisBytes[1] & 0xff, 0x80); + + encodeInfo.append("(").append(Integer.toString((int) jisBytes[0] & 0xff)).append(" "); + encodeInfo.append(Integer.toString((int) jisBytes[1] & 0xff)).append(") "); + } else { + // Process 8-bit byte + int lbyte = (inputData[position + i] & 0xFF); + + if ((inputDataType == DataType.GS1) && (lbyte == '[')) { + lbyte = 0x1d; /* FNC1 */ + + } + + qr_bscan(lbyte, 0x80); + + encodeInfo.append(Integer.toString(lbyte)).append(" "); + } + } + + break; + case ALPHANUM: + /* Alphanumeric mode */ + /* Mode indicator */ + binary.append("0010"); + + /* Character count indicator */ + qr_bscan(short_data_block_length, 0x40 << (2 * scheme)); /* scheme = 1..3 */ + + encodeInfo.append("ALPH "); + + /* Character representation */ + i = 0; + while (i < short_data_block_length) { + + if (!alphanumPercent) { + if ((inputDataType == DataType.GS1) && (inputData[position + i] == '%')) { + first = positionOf('%', rhodium); + second = positionOf('%', rhodium); + count = 2; + prod = (first * 45) + second; + i++; + } else { + if ((inputDataType == DataType.GS1) && (inputData[position + i] == '[')) { + first = positionOf('%', rhodium); /* FNC1 */ + + } else { + first = positionOf((char) (inputData[position + i] & 0xFF), rhodium); + } + count = 1; + i++; + prod = first; + + if (i < short_data_block_length) { + if (inputMode[position + i] == qrMode.ALPHANUM) { + if ((inputDataType == DataType.GS1) && (inputData[position + i] == '%')) { + second = positionOf('%', rhodium); + count = 2; + prod = (first * 45) + second; + alphanumPercent = true; + } else { + if ((inputDataType == DataType.GS1) && (inputData[position + i] == '[')) { + second = positionOf('%', rhodium); /* FNC1 */ + + } else { + second = positionOf((char) (inputData[position + i] & 0xFF), rhodium); + } + count = 2; + i++; + prod = (first * 45) + second; + } + } + } + } + } else { + first = positionOf('%', rhodium); + count = 1; + i++; + prod = first; + alphanumPercent = false; + + if (i < short_data_block_length) { + if (inputMode[position + i] == qrMode.ALPHANUM) { + if ((inputDataType == DataType.GS1) && (inputData[position + i] == '%')) { + second = positionOf('%', rhodium); + count = 2; + prod = (first * 45) + second; + alphanumPercent = true; + } else { + if ((inputDataType == DataType.GS1) && (inputData[position + i] == '[')) { + second = positionOf('%', rhodium); /* FNC1 */ + + } else { + second = positionOf((char) (inputData[position + i] & 0xFF), rhodium); + } + count = 2; + i++; + prod = (first * 45) + second; + } + } + } + } + + qr_bscan(prod, count == 2 ? 0x400 : 0x20); /* count = 1..2 */ + + encodeInfo.append(Integer.toString(prod)).append(" "); + } + ; + break; + case NUMERIC: + /* Numeric mode */ + /* Mode indicator */ + binary.append("0001"); + + /* Character count indicator */ + qr_bscan(short_data_block_length, 0x80 << (2 * scheme)); /* scheme = 1..3 */ + + encodeInfo.append("NUMB "); + + /* Character representation */ + i = 0; + while (i < short_data_block_length) { + + first = Character.getNumericValue(inputData[position + i]); + count = 1; + prod = first; + + if ((i + 1) < short_data_block_length) { + second = Character.getNumericValue(inputData[position + i + 1]); + count = 2; + prod = (prod * 10) + second; + } + + if ((i + 2) < short_data_block_length) { + third = Character.getNumericValue(inputData[position + i + 2]); + count = 3; + prod = (prod * 10) + third; + } + + qr_bscan(prod, 1 << (3 * count)); /* count = 1..3 */ + + encodeInfo.append(Integer.toString(prod)).append(" "); + + i += count; + } + ; + break; + } + position += short_data_block_length; + } while (position < inputLength); + + encodeInfo.append("\n"); + + /* Terminator */ + binary.append("0000"); + + current_binlen = binary.length(); + padbits = 8 - (current_binlen % 8); + if (padbits == 8) { + padbits = 0; + } + current_bytes = (current_binlen + padbits) / 8; + + /* Padding bits */ + for (i = 0; i < padbits; i++) { + binary.append("0"); + } + + /* Put data into 8-bit codewords */ + for (i = 0; i < current_bytes; i++) { + datastream[i] = 0x00; + for (weight = 0; weight < 8; weight++) { + if (binary.charAt((i * 8) + weight) == '1') { + datastream[i] += (0x80 >> weight); + } + } + } + + /* Add pad codewords */ + toggle = 0; + for (i = current_bytes; i < target_binlen; i++) { + if (toggle == 0) { + datastream[i] = 0xec; + toggle = 1; + } else { + datastream[i] = 0x11; + toggle = 0; + } + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < target_binlen; i++) { + encodeInfo.append(Integer.toString(datastream[i])).append(" "); + } + encodeInfo.append("\n"); + + return true; + } + + private void qr_bscan(int data, int h) { + + for (; + (h != 0); h >>= 1) { + if ((data & h) != 0) { + binary.append("1"); + } else { + binary.append("0"); + } + } + + } + + private void add_ecc(int version, int data_cw, int blocks) { + /* Split data into blocks, add error correction and then interleave the blocks and error correction data */ + int ecc_cw = qr_total_codewords[version - 1] - data_cw; + int short_data_block_length = data_cw / blocks; + int qty_long_blocks = data_cw % blocks; + int qty_short_blocks = blocks - qty_long_blocks; + int ecc_block_length = ecc_cw / blocks; + int i, j, k, length_this_block, posn; + + int[] data_block = new int[short_data_block_length + 2]; + int[] ecc_block = new int[ecc_block_length + 2]; + int[] interleaved_data = new int[data_cw + 2]; + int[] interleaved_ecc = new int[ecc_cw + 2]; + + posn = 0; + + for (i = 0; i < blocks; i++) { + ReedSolomon rs = new ReedSolomon(); + if (i < qty_short_blocks) { + length_this_block = short_data_block_length; + } else { + length_this_block = short_data_block_length + 1; + } + + for (j = 0; j < ecc_block_length; j++) { + ecc_block[j] = 0; + } + + for (j = 0; j < length_this_block; j++) { + data_block[j] = datastream[posn + j]; + } + + rs.init_gf(0x11d); + rs.init_code(ecc_block_length, 0); + rs.encode(length_this_block, data_block); + for (k = 0; k < ecc_block_length; k++) { + ecc_block[k] = rs.getResult(k); + } + for (j = 0; j < short_data_block_length; j++) { + interleaved_data[(j * blocks) + i] = data_block[j]; + } + + if (i >= qty_short_blocks) { + interleaved_data[(short_data_block_length * blocks) + (i - qty_short_blocks)] = data_block[short_data_block_length]; + } + + for (j = 0; j < ecc_block_length; j++) { + interleaved_ecc[(j * blocks) + i] = ecc_block[ecc_block_length - j - 1]; + } + + posn += length_this_block; + } + + for (j = 0; j < data_cw; j++) { + fullstream[j] = interleaved_data[j]; + } + for (j = 0; j < ecc_cw; j++) { + fullstream[j + data_cw] = interleaved_ecc[j]; + } + } + + private void setup_grid(int size, int version) { + int i; + int loopsize, x, y, xcoord, ycoord; + boolean toggle = true; + + /* Add timing patterns */ + for (i = 0; i < size; i++) { + if (toggle) { + grid[(6 * size) + i] = 0x21; + grid[(i * size) + 6] = 0x21; + toggle = false; + } else { + grid[(6 * size) + i] = 0x20; + grid[(i * size) + 6] = 0x20; + toggle = true; + } + } + + /* Add finder patterns */ + place_finder(size, 0, 0); + place_finder(size, 0, size - 7); + place_finder(size, size - 7, 0); + + /* Add separators */ + for (i = 0; i < 7; i++) { + grid[(7 * size) + i] = 0x10; + grid[(i * size) + 7] = 0x10; + grid[(7 * size) + (size - 1 - i)] = 0x10; + grid[(i * size) + (size - 8)] = 0x10; + grid[((size - 8) * size) + i] = 0x10; + grid[((size - 1 - i) * size) + 7] = 0x10; + } + grid[(7 * size) + 7] = 0x10; + grid[(7 * size) + (size - 8)] = 0x10; + grid[((size - 8) * size) + 7] = 0x10; + + /* Add alignment patterns */ + if (version != 1) { + /* Version 1 does not have alignment patterns */ + + loopsize = qr_align_loopsize[version - 1]; + for (x = 0; x < loopsize; x++) { + for (y = 0; y < loopsize; y++) { + xcoord = qr_table_e1[((version - 2) * 7) + x]; + ycoord = qr_table_e1[((version - 2) * 7) + y]; + + if ((grid[(ycoord * size) + xcoord] & 0x10) == 0) { + place_align(size, xcoord, ycoord); + } + } + } + } + + /* Reserve space for format information */ + for (i = 0; i < 8; i++) { + grid[(8 * size) + i] += 0x20; + grid[(i * size) + 8] += 0x20; + grid[(8 * size) + (size - 1 - i)] = 0x20; + grid[((size - 1 - i) * size) + 8] = 0x20; + } + grid[(8 * size) + 8] += 0x20; + grid[((size - 1 - 7) * size) + 8] = 0x21; /* Dark Module from Figure 25 */ + + /* Reserve space for version information */ + if (version >= 7) { + for (i = 0; i < 6; i++) { + grid[((size - 9) * size) + i] = 0x20; + grid[((size - 10) * size) + i] = 0x20; + grid[((size - 11) * size) + i] = 0x20; + grid[(i * size) + (size - 9)] = 0x20; + grid[(i * size) + (size - 10)] = 0x20; + grid[(i * size) + (size - 11)] = 0x20; + } + } + } + + private void place_finder(int size, int x, int y) { + int xp, yp; + + int[] finder = { + 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 0, 1, + 1, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1 + }; + + for (xp = 0; xp < 7; xp++) { + for (yp = 0; yp < 7; yp++) { + if (finder[xp + (7 * yp)] == 1) { + grid[((yp + y) * size) + (xp + x)] = 0x11; + } else { + grid[((yp + y) * size) + (xp + x)] = 0x10; + } + } + } + } + + private void place_align(int size, int x, int y) { + int xp, yp; + + int[] alignment = { + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1 + }; + + x -= 2; + y -= 2; /* Input values represent centre of pattern */ + + for (xp = 0; xp < 5; xp++) { + for (yp = 0; yp < 5; yp++) { + if (alignment[xp + (5 * yp)] == 1) { + grid[((yp + y) * size) + (xp + x)] = 0x11; + } else { + grid[((yp + y) * size) + (xp + x)] = 0x10; + } + } + } + } + + private void populate_grid(int size, int cw) { + boolean goingUp = true; + int row = 0; /* right hand side */ + + int i, n, x, y; + + n = cw * 8; + y = size - 1; + i = 0; + do { + x = (size - 2) - (row * 2); + if (x < 6) { + x--; /* skip over vertical timing pattern */ + + } + + if ((grid[(y * size) + (x + 1)] & 0xf0) == 0) { + if (cwbit(i)) { + grid[(y * size) + (x + 1)] = 0x01; + } else { + grid[(y * size) + (x + 1)] = 0x00; + } + i++; + } + + if (i < n) { + if ((grid[(y * size) + x] & 0xf0) == 0) { + if (cwbit(i)) { + grid[(y * size) + x] = 0x01; + } else { + grid[(y * size) + x] = 0x00; + } + i++; + } + } + + if (goingUp) { + y--; + } else { + y++; + } + if (y == -1) { + /* reached the top */ + row++; + y = 0; + goingUp = false; + } + if (y == size) { + /* reached the bottom */ + row++; + y = size - 1; + goingUp = true; + } + } while (i < n); + } + + private boolean cwbit(int i) { + boolean resultant = false; + + if ((fullstream[i / 8] & (0x80 >> (i % 8))) != 0) { + resultant = true; + } + + return resultant; + } + + private int apply_bitmask(int size, EccMode ecc_level) { + int x, y; + char p; + int local_pattern; + int best_val, best_pattern; + int[] penalty = new int[8]; + byte[] mask = new byte[size * size]; + eval = new byte[size * size]; + + + /* Perform data masking */ + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + mask[(y * size) + x] = 0x00; + + if ((grid[(y * size) + x] & 0xf0) == 0) { + if (((y + x) & 1) == 0) { + mask[(y * size) + x] += 0x01; + } + if ((y & 1) == 0) { + mask[(y * size) + x] += 0x02; + } + if ((x % 3) == 0) { + mask[(y * size) + x] += 0x04; + } + if (((y + x) % 3) == 0) { + mask[(y * size) + x] += 0x08; + } + if ((((y / 2) + (x / 3)) & 1) == 0) { + mask[(y * size) + x] += 0x10; + } + if ((((y * x) & 1) + ((y * x) % 3)) == 0) { + mask[(y * size) + x] += 0x20; + } + if (((((y * x) & 1) + ((y * x) % 3)) & 1) == 0) { + mask[(y * size) + x] += 0x40; + } + if (((((y + x) & 1) + ((y * x) % 3)) & 1) == 0) { + mask[(y * size) + x] += 0x80; + } + } + } + } + + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + if ((grid[(y * size) + x] & 0x01) != 0) { + p = 0xff; + } else { + p = 0x00; + } + + eval[(y * size) + x] = (byte) (mask[(y * size) + x] ^ p); + } + } + + + /* Evaluate result */ + for (local_pattern = 0; local_pattern < 8; local_pattern++) { + add_format_info_eval(size, ecc_level, local_pattern); + penalty[local_pattern] = evaluate(size, local_pattern); + } + + best_pattern = 0; + best_val = penalty[0]; + for (local_pattern = 1; local_pattern < 8; local_pattern++) { + if (penalty[local_pattern] < best_val) { + best_pattern = local_pattern; + best_val = penalty[local_pattern]; + } + } + + /* Apply mask */ + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + if ((mask[(y * size) + x] & (0x01 << best_pattern)) != 0) { + if ((grid[(y * size) + x] & 0x01) != 0) { + grid[(y * size) + x] = 0x00; + } else { + grid[(y * size) + x] = 0x01; + } + } + } + } + + return best_pattern; + } + + private void add_format_info_eval(int size, EccMode ecc_level, int pattern) { + /* Add format information to grid */ + + int format = pattern; + int seq; + int i; + + switch (ecc_level) { + case L: + format += 0x08; + break; + case Q: + format += 0x18; + break; + case H: + format += 0x10; + break; + } + + seq = qr_annex_c[format]; + + for (i = 0; i < 6; i++) { + if (((seq >> i) & 0x01) != 0) { + eval[(i * size) + 8] = (byte) (0x01 >> pattern); + } else { + eval[(i * size) + 8] = 0; + } + } + + for (i = 0; i < 8; i++) { + if (((seq >> i) & 0x01) != 0) { + eval[(8 * size) + (size - i - 1)] = (byte) (0x01 >> pattern); + } else { + eval[(8 * size) + (size - i - 1)] = 0; + } + } + + for (i = 0; i < 6; i++) { + if (((seq >> (i + 9)) & 0x01) != 0) { + eval[(8 * size) + (5 - i)] = (byte) (0x01 >> pattern); + } else { + eval[(8 * size) + (5 - i)] = 0; + } + } + + for (i = 0; i < 7; i++) { + if (((seq >> (i + 8)) & 0x01) != 0) { + eval[(((size - 7) + i) * size) + 8] = (byte) (0x01 >> pattern); + } else { + eval[(((size - 7) + i) * size) + 8] = 0; + } + } + + if (((seq >> 6) & 0x01) != 0) { + eval[(7 * size) + 8] = (byte) (0x01 >> pattern); + } else { + eval[(7 * size) + 8] = 0; + } + + if (((seq >> 7) & 0x01) != 0) { + eval[(8 * size) + 8] = (byte) (0x01 >> pattern); + } else { + eval[(8 * size) + 8] = 0; + } + + if (((seq >> 8) & 0x01) != 0) { + eval[(8 * size) + 7] = (byte) (0x01 >> pattern); + } else { + eval[(8 * size) + 7] = 0; + } + } + + private int evaluate(int size, int pattern) { + int x, y, block; + int result = 0; + int state; + int p; + int weight; + int dark_mods; + int percentage, k; + int a, b, afterCount, beforeCount; + byte[] local = new byte[size * size]; + + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + if ((eval[(y * size) + x] & (0x01 << pattern)) != 0) { + local[(y * size) + x] = '1'; + } else { + local[(y * size) + x] = '0'; + } + } + } + + /* Test 1: Adjacent modules in row/column in same colour */ + /* Vertical */ + for (x = 0; x < size; x++) { + state = local[x]; + block = 0; + for (y = 0; y < size; y++) { + if (local[(y * size) + x] == state) { + block++; + } else { + if (block > 5) { + result += (3 + (block - 5)); + } + block = 0; + state = local[(y * size) + x]; + } + } + if (block > 5) { + result += (3 + (block - 5)); + } + } + + /* Horizontal */ + for (y = 0; y < size; y++) { + state = local[y * size]; + block = 0; + for (x = 0; x < size; x++) { + if (local[(y * size) + x] == state) { + block++; + } else { + if (block > 5) { + result += (3 + (block - 5)); + } + block = 0; + state = local[(y * size) + x]; + } + } + if (block > 5) { + result += (3 + (block - 5)); + } + } + + /* Test 2: Block of modules in same color */ + for (x = 0; x < size - 1; x++) { + for (y = 0; y < size - 1; y++) { + if (((local[(y * size) + x] == local[((y + 1) * size) + x]) + && (local[(y * size) + x] == local[(y * size) + (x + 1)])) + && (local[(y * size) + x] == local[((y + 1) * size) + (x + 1)])) { + result += 3; + } + } + } + + /* Test 3: 1:1:3:1:1 ratio pattern in row/column */ + /* Vertical */ + for (x = 0; x < size; x++) { + for (y = 0; y < (size - 7); y++) { + p = 0; + for (weight = 0; weight < 7; weight++) { + if (local[((y + weight) * size) + x] == '1') { + p += (0x40 >> weight); + } + } + if (p == 0x5d) { + /* Pattern found, check before and after */ + beforeCount = 0; + for (b = (y - 4); b < y; b++) { + if (b < 0) { + beforeCount++; + } else { + if (local[(b * size) + x] == '0') { + beforeCount++; + } else { + beforeCount = 0; + } + } + } + + afterCount = 0; + for (a = (y + 7); a <= (y + 10); a++) { + if (a >= size) { + afterCount++; + } else { + if (local[(a * size) + x] == '0') { + afterCount++; + } else { + afterCount = 0; + } + } + } + + if ((beforeCount == 4) || (afterCount == 4)) { + /* Pattern is preceeded or followed by light area + 4 modules wide */ + result += 40; + } + } + } + } + + /* Horizontal */ + for (y = 0; y < size; y++) { + for (x = 0; x < (size - 7); x++) { + p = 0; + for (weight = 0; weight < 7; weight++) { + if (local[(y * size) + x + weight] == '1') { + p += (0x40 >> weight); + } + } + if (p == 0x5d) { + /* Pattern found, check before and after */ + beforeCount = 0; + for (b = (x - 4); b < x; b++) { + if (b < 0) { + beforeCount++; + } else { + if (local[(y * size) + b] == '0') { + beforeCount++; + } else { + beforeCount = 0; + } + } + } + + afterCount = 0; + for (a = (x + 7); a <= (x + 10); a++) { + if (a >= size) { + afterCount++; + } else { + if (local[(y * size) + a] == '0') { + afterCount++; + } else { + afterCount = 0; + } + } + } + + if ((beforeCount == 4) || (afterCount == 4)) { + /* Pattern is preceeded or followed by light area + 4 modules wide */ + result += 40; + } + } + } + } + + /* Test 4: Proportion of dark modules in entire symbol */ + dark_mods = 0; + for (x = 0; x < size; x++) { + for (y = 0; y < size; y++) { + if (local[(y * size) + x] == '1') { + dark_mods++; + } + } + } + percentage = 100 * (dark_mods / (size * size)); + if (percentage <= 50) { + k = ((100 - percentage) - 50) / 5; + } else { + k = (percentage - 50) / 5; + } + + result += 10 * k; + + return result; + } + + private void add_format_info(int size, EccMode ecc_level, int pattern) { + /* Add format information to grid */ + + int format = pattern; + int seq; + int i; + + switch (ecc_level) { + case L: + format += 0x08; + break; + case Q: + format += 0x18; + break; + case H: + format += 0x10; + break; + } + + seq = qr_annex_c[format]; + + for (i = 0; i < 6; i++) { + grid[(i * size) + 8] += (seq >> i) & 0x01; + } + + for (i = 0; i < 8; i++) { + grid[(8 * size) + (size - i - 1)] += (seq >> i) & 0x01; + } + + for (i = 0; i < 6; i++) { + grid[(8 * size) + (5 - i)] += (seq >> (i + 9)) & 0x01; + } + + for (i = 0; i < 7; i++) { + grid[(((size - 7) + i) * size) + 8] += (seq >> (i + 8)) & 0x01; + } + + grid[(7 * size) + 8] += (seq >> 6) & 0x01; + grid[(8 * size) + 8] += (seq >> 7) & 0x01; + grid[(8 * size) + 7] += (seq >> 8) & 0x01; + } + + private void add_version_info(int size, int version) { + /* Add version information */ + int i; + + long version_data = qr_annex_d[version - 7]; + for (i = 0; i < 6; i++) { + grid[((size - 11) * size) + i] += (version_data >> (i * 3)) & 0x01; + grid[((size - 10) * size) + i] += (version_data >> ((i * 3) + 1)) & 0x01; + grid[((size - 9) * size) + i] += (version_data >> ((i * 3) + 2)) & 0x01; + grid[(i * size) + (size - 11)] += (version_data >> (i * 3)) & 0x01; + grid[(i * size) + (size - 10)] += (version_data >> ((i * 3) + 1)) & 0x01; + grid[(i * size) + (size - 9)] += (version_data >> ((i * 3) + 2)) & 0x01; + } + } + + private enum qrMode { + + NULL, KANJI, BINARY, ALPHANUM, NUMERIC + } + + public enum EccMode { + + L, M, Q, H + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/RoyalMail4State.java b/barcode/src/main/java/org/xbib/graphics/barcode/RoyalMail4State.java new file mode 100755 index 0000000..9fddf90 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/RoyalMail4State.java @@ -0,0 +1,112 @@ +package org.xbib.graphics.barcode; + +import java.awt.geom.Rectangle2D; +import java.util.Locale; + +/** + * Encodes data according to the Royal Mail 4-State Country Code + * Data input can consist of numbers 0-9 and letters A-Z and usually includes + * delivery postcode followed by house number. A check digit is calculated + * and added. + */ +public class RoyalMail4State extends Symbol { + /* Handles the 4 State barcodes used in the UK by Royal Mail */ + + private String[] RoyalTable = { + "TTFF", "TDAF", "TDFA", "DTAF", "DTFA", "DDAA", "TADF", "TFTF", "TFDA", + "DATF", "DADA", "DFTA", "TAFD", "TFAD", "TFFT", "DAAD", "DAFT", "DFAT", + "ATDF", "ADTF", "ADDA", "FTTF", "FTDA", "FDTA", "ATFD", "ADAD", "ADFT", + "FTAD", "FTFT", "FDAT", "AADD", "AFTD", "AFDT", "FATD", "FADT", "FFTT" + }; + + private char[] krSet = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + }; + + @Override + public boolean encode() { + StringBuilder dest; + int i, top = 0, bottom = 0; + int row, column; + int index; + + content = content.toUpperCase(Locale.ENGLISH); + if (!(content.matches("[0-9A-Z]+"))) { + errorMsg.append("Invalid characters in data"); + return false; + } + dest = new StringBuilder("A"); + + for (i = 0; i < content.length(); i++) { + index = positionOf(content.charAt(i), krSet); + dest.append(RoyalTable[index]); + top += (index + 1) % 6; + bottom += ((index / 6) + 1) % 6; + } + + /* calculate check digit */ + row = (top % 6) - 1; + column = (bottom % 6) - 1; + if (row == -1) { + row = 5; + } + if (column == -1) { + column = 5; + } + + dest.append(RoyalTable[(6 * row) + column]); + + encodeInfo.append("Check Digit: ").append((6 * row) + column).append("\n"); + + /* Stop character */ + dest.append("F"); + + encodeInfo.append("Encoding: ").append(dest).append("\n"); + readable = new StringBuilder(); + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + getRectangles().clear(); + x = 0; + w = 1; + y = 0; + h = 0; + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + switch (pattern[0].charAt(xBlock)) { + case 'A': + y = 0; + h = 5; + break; + case 'D': + y = 3; + h = 5; + break; + case 'F': + y = 0; + h = 8; + break; + case 'T': + y = 3; + h = 2; + break; + } + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + x += 2; + } + symbolWidth = pattern[0].length() * 3; + symbolHeight = 8; + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Symbol.java b/barcode/src/main/java/org/xbib/graphics/barcode/Symbol.java new file mode 100755 index 0000000..77b321b --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Symbol.java @@ -0,0 +1,1058 @@ +package org.xbib.graphics.barcode; + +import static org.xbib.graphics.barcode.HumanReadableLocation.BOTTOM; +import static org.xbib.graphics.barcode.HumanReadableLocation.NONE; +import static org.xbib.graphics.barcode.HumanReadableLocation.TOP; +import org.xbib.graphics.barcode.util.Hexagon; +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +/** + * Generic barcode symbology class. + * TODO: Setting attributes like module width, font size, etc should probably throw + * an exception if set *after* encoding has already been completed. + */ +public abstract class Symbol { + + private final List rectangles = new ArrayList<>(); + + private final List texts = new ArrayList<>(); + + private final List hexagons = new ArrayList<>(); + + private final List target = new ArrayList<>(); + + protected String content; + + protected StringBuilder readable; + + protected String[] pattern; + + protected int rowCount = 0; + + protected int[] rowHeight; + + protected StringBuilder errorMsg = new StringBuilder(); + + protected int symbolHeight = 0; + + protected int symbolWidth = 0; + + protected int defaultHeight = 40; + + private HumanReadableLocation humanReadableLocation = BOTTOM; + + protected StringBuilder encodeInfo = new StringBuilder(); + + protected byte[] inputBytes; + + protected DataType inputDataType = DataType.ECI; + + int moduleWidth = 1; + + double fontSize = 8; + + boolean readerInit; + + int eciMode = 3; + + private int quietZoneHorizontal = 0; + + private int quietZoneVertical = 0; + + private String fontName = "Helvetica"; + + public Symbol() { + unsetReaderInit(); + } + + public List getRectangles() { + return rectangles; + } + + public List getTexts() { + return texts; + } + + public List getHexagons() { + return hexagons; + } + + public List getTarget() { + return target; + } + + /** + * Inserts the specified array into the specified original array at the specified index. + * + * @param original the original array into which we want to insert another array + * @param index the index at which we want to insert the array + * @param inserted the array that we want to insert + * @return the combined array + */ + static int[] insert(int[] original, int index, int[] inserted) { + int[] modified = new int[original.length + inserted.length]; + System.arraycopy(original, 0, modified, 0, index); + System.arraycopy(inserted, 0, modified, index, inserted.length); + System.arraycopy(original, index, modified, index + inserted.length, modified.length - index - inserted.length); + return modified; + } + + /** + * Returns true if the specified array contains the specified value. + * + * @param values the array to check in + * @param value the value to check for + * @return true if the specified array contains the specified value + */ + static boolean contains(int[] values, int value) { + for (int value1 : values) { + if (value1 == value) { + return true; + } + } + return false; + } + + private static boolean roughlyEqual(double d1, double d2) { + return Math.abs(d1 - d2) < 0.0001; + } + + /** + * Sets the type of input data. This setting influences what + * pre-processing is done on data before encoding in the symbol. + * For example: for GS1 mode the AI data will be used to + * calculate the position of 'FNC1' characters. + * Valid values are: + *

+ * + * @param dataType A DataType value which specifies the type of data + */ + public void setDataType(DataType dataType) { + inputDataType = dataType; + } + + /** + * Prefixes symbol data with a "Reader Initialisation" or "Reader + * Programming" instruction. + */ + public final void setReaderInit() { + readerInit = true; + } + + /** + * Removes "Reader Initialisation" or "Reader Programming" instruction + * from symbol data. + */ + private void unsetReaderInit() { + readerInit = false; + } + + /** + * Returns the default bar height for this symbol. + * + * @return the default bar height for this symbol + */ + public int getBarHeight() { + return defaultHeight; + } + + /** + * Sets the default bar height for this symbol (default value is 40). + * + * @param barHeight the default bar height for this symbol + */ + public void setBarHeight(int barHeight) { + this.defaultHeight = barHeight; + } + + /** + * Returns the module width for this symbol. + * + * @return the module width for this symbol + */ + public int getModuleWidth() { + return moduleWidth; + } + + /** + * Sets the module width for this symbol (default value is 1). + * + * @param moduleWidth the module width for this symbol + */ + public void setModuleWidth(int moduleWidth) { + this.moduleWidth = moduleWidth; + } + + /** + * Returns the horizontal quiet zone (white space) added to the left and to the right of this symbol. + * + * @return the horizontal quiet zone (white space) added to the left and to the right of this symbol + */ + public int getQuietZoneHorizontal() { + return quietZoneHorizontal; + } + + /** + * Sets the horizontal quiet zone (white space) added to the left and to the right of this symbol. + * + * @param quietZoneHorizontal the horizontal quiet zone (white space) added to the left and to the right of this symbol + */ + public void setQuietZoneHorizontal(int quietZoneHorizontal) { + this.quietZoneHorizontal = quietZoneHorizontal; + } + + /** + * Returns the vertical quiet zone (white space) added above and below this symbol. + * + * @return the vertical quiet zone (white space) added above and below this symbol + */ + public int getQuietZoneVertical() { + return quietZoneVertical; + } + + /** + * Sets the vertical quiet zone (white space) added above and below this symbol. + * + * @param quietZoneVertical the vertical quiet zone (white space) added above and below this symbol + */ + public void setQuietZoneVertical(int quietZoneVertical) { + this.quietZoneVertical = quietZoneVertical; + } + + /** + * Returns the name of the font to use to render the human-readable text. + * + * @return the name of the font to use to render the human-readable text + */ + public String getFontName() { + return fontName; + } + + /** + * Sets the name of the font to use to render the human-readable text (default value is Helvetica). + * + * @param fontName the name of the font to use to render the human-readable text + */ + public void setFontName(String fontName) { + this.fontName = fontName; + } + + /** + * Returns the size of the font to use to render the human-readable text. + * + * @return the size of the font to use to render the human-readable text + */ + public double getFontSize() { + return fontSize; + } + + /** + * Sets the size of the font to use to render the human-readable text (default value is 8). + * + * @param fontSize the size of the font to use to render the human-readable text + */ + public void setFontSize(double fontSize) { + this.fontSize = fontSize; + } + + /** + * Gets the width of the encoded symbol, including the horizontal quiet zone. + * + * @return the width of the encoded symbol + */ + public int getWidth() { + return symbolWidth + (2 * quietZoneHorizontal); + } + + /** + * Returns the height of the symbol, including the human-readable text, if any, as well as the vertical + * quiet zone. This height is an approximation, since it is calculated without access to a font engine. + * + * @return the height of the symbol, including the human-readable text, if any, as well as the vertical + * quiet zone + */ + public int getHeight() { + return symbolHeight + getHumanReadableHeight() + (2 * quietZoneVertical); + } + + /** + * Returns the height of the human-readable text, including the space between the text and other symbols. + * This height is an approximation, since it is calculated without access to a font engine. + * + * @return the height of the human-readable text + */ + public int getHumanReadableHeight() { + if (texts.isEmpty()) { + return 0; + } else { + return getTheoreticalHumanReadableHeight(); + } + } + + /** + * Returns the height of the human-readable text, assuming this symbol had human-readable text. + * + * @return the height of the human-readable text, assuming this symbol had human-readable text + */ + int getTheoreticalHumanReadableHeight() { + return (int) Math.ceil(fontSize * 1.2); // 0.2 space between bars and text + } + + /** + * Returns a human readable summary of the decisions made by the encoder when creating a symbol. + * + * @return a human readable summary of the decisions made by the encoder when creating a symbol + */ + public String getEncodeInfo() { + return encodeInfo.toString(); + } + + /** + * Returns the location of the human-readable text. + * + * @return the location of the human-readable text + */ + public HumanReadableLocation getHumanReadableLocation() { + return humanReadableLocation; + } + + /** + * Sets the location of the human-readable text (default value is {@link HumanReadableLocation#BOTTOM}). + * + * @param humanReadableLocation the location of the human-readable text + */ + public void setHumanReadableLocation(HumanReadableLocation humanReadableLocation) { + this.humanReadableLocation = humanReadableLocation; + } + + protected int positionOf(char thischar, char[] LookUp) { + int i, outval = 0; + + for (i = 0; i < LookUp.length; i++) { + if (thischar == LookUp[i]) { + outval = i; + } + } + return outval; + } + + protected String bin2pat(String bin) { + boolean black; + int i, l; + StringBuilder pat = new StringBuilder(); + + black = true; + l = 0; + for (i = 0; i < bin.length(); i++) { + if (black) { + if (bin.charAt(i) == '1') { + l++; + } else { + black = false; + pat.append((char) (l + '0')); + l = 1; + } + } else { + if (bin.charAt(i) == '0') { + l++; + } else { + black = true; + pat.append((char) (l + '0')); + l = 1; + } + } + } + pat.append((char) (l + '0')); + + return pat.toString(); + } + + public String getContent() { + return content; + } + + /** + * Set the data to be encoded. Input data will be assumed to be of + * the type set by setDataType. + * + * @param inputData A String containing the data to encode + */ + public void setContent(String inputData) { + int i; + content = inputData; + if (inputDataType == DataType.GS1) { + content = gs1SanityCheck(inputData); + } + if (inputDataType == DataType.GS1) { + readable = new StringBuilder(); + for (i = 0; i < inputData.length(); i++) { + switch (inputData.charAt(i)) { + case '[': + readable.append('('); + break; + case ']': + readable.append(')'); + break; + default: + readable.append(inputData.charAt(i)); + break; + } + } + } + if (inputDataType == DataType.HIBC) { + content = hibcProcess(inputData); + } + if (!content.isEmpty()) { + if (!encode()) { + throw new IllegalStateException(errorMsg.toString()); + } + } else { + throw new IllegalStateException("No input data"); + } + } + + void eciProcess() { + int qmarksBefore, qmarksAfter; + int i; + + qmarksBefore = 0; + for (i = 0; i < content.length(); i++) { + if (content.charAt(i) == '?') { + qmarksBefore++; + } + } + + qmarksAfter = eciEncode("ISO8859_1"); + if (qmarksAfter == qmarksBefore) { + eciMode = 3; + encodeInfo.append("Encoding in ISO 8859-1 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_2"); + if (qmarksAfter == qmarksBefore) { + eciMode = 4; + encodeInfo.append("Encoding in ISO 8859-2 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_3"); + if (qmarksAfter == qmarksBefore) { + eciMode = 5; + encodeInfo.append("Encoding in ISO 8859-3 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_4"); + if (qmarksAfter == qmarksBefore) { + eciMode = 6; + encodeInfo.append("Encoding in ISO 8859-4 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_5"); + if (qmarksAfter == qmarksBefore) { + eciMode = 7; + encodeInfo.append("Encoding in ISO 8859-5 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_6"); + if (qmarksAfter == qmarksBefore) { + eciMode = 8; + encodeInfo.append("Encoding in ISO 8859-6 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_7"); + if (qmarksAfter == qmarksBefore) { + eciMode = 9; + encodeInfo.append("Encoding in ISO 8859-7 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_8"); + if (qmarksAfter == qmarksBefore) { + eciMode = 10; + encodeInfo.append("Encoding in ISO 8859-8 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_9"); + if (qmarksAfter == qmarksBefore) { + eciMode = 11; + encodeInfo.append("Encoding in ISO 8859-9 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_10"); + if (qmarksAfter == qmarksBefore) { + eciMode = 12; + encodeInfo.append("Encoding in ISO 8859-10 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_11"); + if (qmarksAfter == qmarksBefore) { + eciMode = 13; + encodeInfo.append("Encoding in ISO 8859-11 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_13"); + if (qmarksAfter == qmarksBefore) { + eciMode = 15; + encodeInfo.append("Encoding in ISO 8859-13 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_14"); + if (qmarksAfter == qmarksBefore) { + eciMode = 16; + encodeInfo.append("Encoding in ISO 8859-14 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_15"); + if (qmarksAfter == qmarksBefore) { + eciMode = 17; + encodeInfo.append("Encoding in ISO 8859-15 character set\n"); + return; + } + + qmarksAfter = eciEncode("ISO8859_16"); + if (qmarksAfter == qmarksBefore) { + eciMode = 18; + encodeInfo.append("Encoding in ISO 8859-16 character set\n"); + return; + } + + qmarksAfter = eciEncode("Windows_1250"); + if (qmarksAfter == qmarksBefore) { + eciMode = 21; + encodeInfo.append("Encoding in Windows-1250 character set\n"); + return; + } + + qmarksAfter = eciEncode("Windows_1251"); + if (qmarksAfter == qmarksBefore) { + eciMode = 22; + encodeInfo.append("Encoding in Windows-1251 character set\n"); + return; + } + + qmarksAfter = eciEncode("Windows_1252"); + if (qmarksAfter == qmarksBefore) { + eciMode = 23; + encodeInfo.append("Encoding in Windows-1252 character set\n"); + return; + } + + qmarksAfter = eciEncode("Windows_1256"); + if (qmarksAfter == qmarksBefore) { + eciMode = 24; + encodeInfo.append("Encoding in Windows-1256 character set\n"); + return; + } + + qmarksAfter = eciEncode("SJIS"); + if (qmarksAfter == qmarksBefore) { + eciMode = 20; + encodeInfo.append("Encoding in Shift-JIS character set\n"); + return; + } + + /* default */ + qmarksAfter = eciEncode("UTF8"); + eciMode = 26; + encodeInfo.append("Encoding in UTF-8 character set\n"); + } + + private int eciEncode(String charset) { + /* getBytes replaces unconverted characters to '?', so count + the number of question marks to find if conversion was sucessful. + */ + int i, qmarksAfter; + + try { + inputBytes = content.getBytes(charset); + } catch (UnsupportedEncodingException e) { + return -1; + } + + qmarksAfter = 0; + for (i = 0; i < inputBytes.length; i++) { + if (inputBytes[i] == '?') { + qmarksAfter++; + } + } + + return qmarksAfter; + } + + abstract boolean encode(); + + protected void plotSymbol() { + int xBlock, yBlock; + double x, y, w, h; + boolean black; + rectangles.clear(); + texts.clear(); + int baseY; + if (humanReadableLocation == TOP) { + baseY = getTheoreticalHumanReadableHeight(); + } else { + baseY = 0; + } + h = 0; + y = baseY; + for (yBlock = 0; yBlock < rowCount; yBlock++) { + black = true; + x = 0; + for (xBlock = 0; xBlock < pattern[yBlock].length(); xBlock++) { + char c = pattern[yBlock].charAt(xBlock); + w = getModuleWidth(c - '0') * moduleWidth; + if (black) { + if (rowHeight[yBlock] == -1) { + h = defaultHeight; + } else { + h = rowHeight[yBlock]; + } + if (w != 0 && h != 0) { + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + rectangles.add(rect); + } + if (x + w > symbolWidth) { + symbolWidth = (int) Math.ceil(x + w); + } + } + black = !black; + x += w; + } + if ((y - baseY + h) > symbolHeight) { + symbolHeight = (int) Math.ceil(y - baseY + h); + } + y += h; + } + mergeVerticalBlocks(); + if (humanReadableLocation != NONE && readable.length() > 0) { + double baseline; + if (humanReadableLocation == TOP) { + baseline = fontSize; + } else { + baseline = getHeight() + fontSize; + } + double centerX = getWidth() / 2.0; + texts.add(new TextBox(centerX, baseline, readable.toString())); + } + } + + /** + * Returns the module width to use for the specified original module width, taking into account any module width ratio + * customizations. Intended to be overridden by subclasses that support such module width ratio customization. + * + * @param originalWidth the original module width + * @return the module width to use for the specified original module width + */ + protected double getModuleWidth(int originalWidth) { + return originalWidth; + } + + /** + * Search for rectangles which have the same width and x position, and + * which join together vertically and merge them together to reduce the + * number of rectangles needed to describe a symbol. + */ + void mergeVerticalBlocks() { + for (int i = 0; i < rectangles.size() - 1; i++) { + for (int j = i + 1; j < rectangles.size(); j++) { + Rectangle2D.Double firstRect = rectangles.get(i); + Rectangle2D.Double secondRect = rectangles.get(j); + if (roughlyEqual(firstRect.x, secondRect.x) && roughlyEqual(firstRect.width, secondRect.width)) { + if (roughlyEqual(firstRect.y + firstRect.height, secondRect.y)) { + firstRect.height += secondRect.height; + rectangles.set(i, firstRect); + rectangles.remove(j); + } + } + } + } + } + + private String gs1SanityCheck(String source) { + // Enforce compliance with GS1 General Specification + // http://www.gs1.org/docs/gsmp/barcodes/GS1_General_Specifications.pdf + + StringBuilder reduced = new StringBuilder(); + + int i, j, lastAi; + boolean aiLatch; + int bracketLevel, maxBracketLevel, aiLength, maxAiLength, minAiLength; + int[] aiValue = new int[100]; + int[] aiLocation = new int[100]; + int aiCount; + int[] dataLocation = new int[100]; + int[] dataLength = new int[100]; + int srcLen = source.length(); + int errorLatch; + + /* Detect extended ASCII characters */ + for (i = 0; i < srcLen; i++) { + if (source.charAt(i) >= 128) { + errorMsg.append("Extended ASCII characters are not supported by GS1"); + return ""; + } + if (source.charAt(i) < 32) { + errorMsg.append("Control characters are not supported by GS1"); + return ""; + } + } + + if (source.charAt(0) != '[') { + errorMsg.append("Data does not start with an AI"); + return ""; + } + + /* Check the position of the brackets */ + bracketLevel = 0; + maxBracketLevel = 0; + aiLength = 0; + maxAiLength = 0; + minAiLength = 5; + j = 0; + aiLatch = false; + for (i = 0; i < srcLen; i++) { + aiLength += j; + if (((j == 1) && (source.charAt(i) != ']')) + && ((source.charAt(i) < '0') || (source.charAt(i) > '9'))) { + aiLatch = true; + } + if (source.charAt(i) == '[') { + bracketLevel++; + j = 1; + } + if (source.charAt(i) == ']') { + bracketLevel--; + if (aiLength < minAiLength) { + minAiLength = aiLength; + } + j = 0; + aiLength = 0; + } + if (bracketLevel > maxBracketLevel) { + maxBracketLevel = bracketLevel; + } + if (aiLength > maxAiLength) { + maxAiLength = aiLength; + } + } + minAiLength--; + + if (bracketLevel != 0) { + /* Not all brackets are closed */ + errorMsg.append("Malformed AI in input data (brackets don't match)"); + return ""; + } + + if (maxBracketLevel > 1) { + /* Nested brackets */ + errorMsg.append("Found nested brackets in input data"); + return ""; + } + + if (maxAiLength > 4) { + /* AI is too long */ + errorMsg.append("Invalid AI in input data (AI too long)"); + return ""; + } + + if (minAiLength <= 1) { + /* AI is too short */ + errorMsg.append("Invalid AI in input data (AI too short)"); + return ""; + } + + if (aiLatch) { + /* Non-numeric data in AI */ + errorMsg.append("Invalid AI in input data (non-numeric characters in AI)"); + return ""; + } + + aiCount = 0; + for (i = 1; i < srcLen; i++) { + if (source.charAt(i - 1) == '[') { + aiLocation[aiCount] = i; + aiValue[aiCount] = 0; + for (j = 0; source.charAt(i + j) != ']'; j++) { + aiValue[aiCount] *= 10; + aiValue[aiCount] += Character.getNumericValue(source.charAt(i + j)); + } + aiCount++; + } + } + + for (i = 0; i < aiCount; i++) { + dataLocation[i] = aiLocation[i] + 3; + if (aiValue[i] >= 100) { + dataLocation[i]++; + } + if (aiValue[i] >= 1000) { + dataLocation[i]++; + } + dataLength[i] = source.length() - dataLocation[i]; + for (j = source.length() - 1; j >= dataLocation[i]; j--) { + if (source.charAt(j) == '[') { + dataLength[i] = j - dataLocation[i]; + } + } + } + + for (i = 0; i < aiCount; i++) { + if (dataLength[i] == 0) { + /* No data for given AI */ + errorMsg.append("Empty data field in input data"); + return ""; + } + } + + errorLatch = 0; + for (i = 0; i < aiCount; i++) { + switch (aiValue[i]) { + case 0: + if (dataLength[i] != 18) { + errorLatch = 1; + } + break; + case 1: + case 2: + case 3: + if (dataLength[i] != 14) { + errorLatch = 1; + } + break; + case 4: + if (dataLength[i] != 16) { + errorLatch = 1; + } + break; + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + if (dataLength[i] != 6) { + errorLatch = 1; + } + break; + case 20: + if (dataLength[i] != 2) { + errorLatch = 1; + } + break; + case 23: + case 24: + case 25: + case 39: + case 40: + case 41: + case 42: + case 70: + case 80: + case 81: + errorLatch = 2; + break; + } + if ( + ((aiValue[i] >= 100) && (aiValue[i] <= 179)) + || ((aiValue[i] >= 1000) && (aiValue[i] <= 1799)) + || ((aiValue[i] >= 200) && (aiValue[i] <= 229)) + || ((aiValue[i] >= 2000) && (aiValue[i] <= 2299)) + || ((aiValue[i] >= 300) && (aiValue[i] <= 309)) + || ((aiValue[i] >= 3000) && (aiValue[i] <= 3099)) + || ((aiValue[i] >= 31) && (aiValue[i] <= 36)) + || ((aiValue[i] >= 310) && (aiValue[i] <= 369))) { + errorLatch = 2; + } + if ((aiValue[i] >= 3100) && (aiValue[i] <= 3699) && dataLength[i] != 6) { + errorLatch = 1; + } + if ( + ((aiValue[i] >= 370) && (aiValue[i] <= 379)) + || ((aiValue[i] >= 3700) && (aiValue[i] <= 3799))) { + errorLatch = 2; + } + if ((aiValue[i] >= 410) && (aiValue[i] <= 415)) { + if (dataLength[i] != 13) { + errorLatch = 1; + } + } + if ( + ((aiValue[i] >= 4100) && (aiValue[i] <= 4199)) + || ((aiValue[i] >= 700) && (aiValue[i] <= 703)) + || ((aiValue[i] >= 800) && (aiValue[i] <= 810)) + || ((aiValue[i] >= 900) && (aiValue[i] <= 999)) + || ((aiValue[i] >= 9000) && (aiValue[i] <= 9999))) { + errorLatch = 2; + } + + if (errorLatch == 1) { + errorMsg = new StringBuilder("Invalid data length for AI"); + return ""; + } + + if (errorLatch == 2) { + errorMsg = new StringBuilder("Invalid AI value"); + return ""; + } + } + + /* Resolve AI data - put resulting string in 'reduced' */ + aiLatch = false; + for (i = 0; i < srcLen; i++) { + if ((source.charAt(i) != '[') && (source.charAt(i) != ']')) { + reduced.append(source.charAt(i)); + } + if (source.charAt(i) == '[') { + /* Start of an AI string */ + if (aiLatch) { + reduced.append('['); + } + lastAi = (10 * Character.getNumericValue(source.charAt(i + 1))) + + Character.getNumericValue(source.charAt(i + 2)); + aiLatch = ((lastAi < 0) || (lastAi > 4)) + && ((lastAi < 11) || (lastAi > 20)) + && (lastAi != 23) /* legacy support - see 5.3.8.2.2 */ + && ((lastAi < 31) || (lastAi > 36)) + && (lastAi != 41); + } + /* The ']' character is simply dropped from the input */ + } + + /* the character '[' in the reduced string refers to the FNC1 character */ + return reduced.toString(); + } + + private String hibcProcess(String source) { + char[] hibcCharTable = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', + '/', '+', '%'}; + + int counter, i; + String toProcess; + char checkDigit; + + if (source.length() > 36) { + errorMsg = new StringBuilder("Data too long for HIBC LIC"); + return ""; + } + source = source.toUpperCase(); + if (!(source.matches("[A-Z0-9-\\. \\$/+\\%]+?"))) { + errorMsg = new StringBuilder("Invalid characters in input"); + return ""; + } + + counter = 41; + for (i = 0; i < source.length(); i++) { + counter += positionOf(source.charAt(i), hibcCharTable); + } + counter = counter % 43; + + if (counter < 10) { + checkDigit = (char) (counter + '0'); + } else { + if (counter < 36) { + checkDigit = (char) ((counter - 10) + 'A'); + } else { + switch (counter) { + case 36: + checkDigit = '-'; + break; + case 37: + checkDigit = '.'; + break; + case 38: + checkDigit = ' '; + break; + case 39: + checkDigit = '$'; + break; + case 40: + checkDigit = '/'; + break; + case 41: + checkDigit = '+'; + break; + case 42: + checkDigit = '%'; + break; + default: + checkDigit = ' '; + break; /* Keep compiler happy */ + } + } + } + + encodeInfo.append("HIBC Check Digit: ").append(counter).append(" (").append(checkDigit).append(")\n"); + + toProcess = "+" + source + checkDigit; + return toProcess; + } + + /** + * Returns the intermediate coding of this bar code. Symbol types that use the test + * infrastructure should override this method. + * + * @return the intermediate coding of this bar code + */ + protected int[] getCodewords() { + throw new UnsupportedOperationException(); + } + + /** + * Returns this bar code's pattern, converted into a set of corresponding codewords. + * Useful for bar codes that encode their content as a pattern. + * + * @param size the number of digits in each codeword + * @return this bar code's pattern, converted into a set of corresponding codewords + */ + int[] getPatternAsCodewords(int size) { + if (pattern == null || pattern.length == 0) { + return new int[0]; + } else { + int count = (int) Math.ceil(pattern[0].length() / (double) size); + int[] codewords = new int[pattern.length * count]; + for (int i = 0; i < pattern.length; i++) { + String row = pattern[i]; + for (int j = 0; j < count; j++) { + int substringStart = j * size; + int substringEnd = Math.min((j + 1) * size, row.length()); + codewords[(i * count) + j] = Integer.parseInt(row.substring(substringStart, substringEnd)); + } + } + return codewords; + } + } + + public enum DataType { + UTF8, LATIN1, BINARY, GS1, HIBC, ECI + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Telepen.java b/barcode/src/main/java/org/xbib/graphics/barcode/Telepen.java new file mode 100755 index 0000000..39f11a1 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Telepen.java @@ -0,0 +1,177 @@ +package org.xbib.graphics.barcode; + +/** + * Telepen (also known as Telepen Alpha) can encode ASCII text input and + * includes a modulo-127 check digit. Telepen Numeric allows compression of + * numeric data into a Telepen symbol. Data can consist of pairs of numbers + * or pairs consisting of a numerical digit followed an X character. + * Telepen Numeric also includes a modulo-127 check digit. + */ +public class Telepen extends Symbol { + + public tp_mode mode; + private String[] TeleTable = { + "1111111111111111", "1131313111", "33313111", "1111313131", + "3111313111", "11333131", "13133131", "111111313111", "31333111", + "1131113131", "33113131", "1111333111", "3111113131", "1113133111", + "1311133111", "111111113131", "3131113111", "11313331", "333331", + "111131113111", "31113331", "1133113111", "1313113111", "1111113331", + "31131331", "113111113111", "3311113111", "1111131331", "311111113111", + "1113111331", "1311111331", "11111111113111", "31313311", "1131311131", + "33311131", "1111313311", "3111311131", "11333311", "13133311", + "111111311131", "31331131", "1131113311", "33113311", "1111331131", + "3111113311", "1113131131", "1311131131", "111111113311", "3131111131", + "1131131311", "33131311", "111131111131", "3111131311", "1133111131", + "1313111131", "111111131311", "3113111311", "113111111131", + "3311111131", "111113111311", "311111111131", "111311111311", + "131111111311", "11111111111131", "3131311111", "11313133", "333133", + "111131311111", "31113133", "1133311111", "1313311111", "1111113133", + "313333", "113111311111", "3311311111", "11113333", "311111311111", + "11131333", "13111333", "11111111311111", "31311133", "1131331111", + "33331111", "1111311133", "3111331111", "11331133", "13131133", + "111111331111", "3113131111", "1131111133", "33111133", "111113131111", + "3111111133", "111311131111", "131111131111", "111111111133", + "31311313", "113131111111", "3331111111", "1111311313", "311131111111", + "11331313", "13131313", "11111131111111", "3133111111", "1131111313", + "33111313", "111133111111", "3111111313", "111313111111", + "131113111111", "111111111313", "313111111111", "1131131113", + "33131113", "11113111111111", "3111131113", "113311111111", + "131311111111", "111111131113", "3113111113", "11311111111111", + "331111111111", "111113111113", "31111111111111", "111311111113", + "131111111113" + }; + + public Telepen() { + mode = tp_mode.NORMAL; + } + + public void setNormalMode() { + mode = tp_mode.NORMAL; + } + + public void setNumericMode() { + mode = tp_mode.NUMERIC; + } + + @Override + public boolean encode() { + if (mode == tp_mode.NORMAL) { + return normal_mode(); + } else { + return numeric_mode(); + } + } + + private boolean normal_mode() { + int count = 0, asciicode, check_digit; + StringBuilder p = new StringBuilder(); + String dest; + + int l = content.length(); + + if (!content.matches("[\u0000-\u007F]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + dest = TeleTable['_']; // Start + for (int i = 0; i < l; i++) { + asciicode = content.charAt(i); + p.append(TeleTable[asciicode]); + count += asciicode; + } + + check_digit = 127 - (count % 127); + if (check_digit == 127) { + check_digit = 0; + } + + p.append(TeleTable[check_digit]); + + encodeInfo.append("Check Digit: ").append(check_digit).append("\n"); + + dest += p; + dest += TeleTable['z']; // Stop + + readable = new StringBuilder(content); + pattern = new String[1]; + pattern[0] = dest; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + public boolean numeric_mode() { + int count = 0, check_digit; + StringBuilder p = new StringBuilder(); + String t; + String dest; + int l = content.length(); + int tl, glyph; + char c1, c2; + + //FIXME: Ensure no extended ASCII or Unicode charcters are entered + if (!(content.matches("[0-9X]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + /* If input is an odd length, add a leading zero */ + if ((l & 1) == 1) { + t = "0" + content; + tl = l + 1; + } else { + t = content; + tl = l; + } + + dest = TeleTable['_']; // Start + for (int i = 0; i < tl; i += 2) { + + c1 = t.charAt(i); + c2 = t.charAt(i + 1); + + /* Input nX is allowed, but Xn is not */ + if (c1 == 'X') { + errorMsg.append("Invalid position of X in data"); + return false; + } + + if (c2 == 'X') { + glyph = (c1 - '0') + 17; + count += glyph; + } else { + glyph = ((10 * (c1 - '0')) + (c2 - '0')) + 27; + count += glyph; + } + + p.append(TeleTable[glyph]); + } + + check_digit = 127 - (count % 127); + if (check_digit == 127) { + check_digit = 0; + } + + p.append(TeleTable[check_digit]); + + encodeInfo.append("Check Digit: ").append(check_digit).append("\n"); + + dest += p; + dest += TeleTable['z']; // Stop + readable = new StringBuilder(content); + pattern = new String[1]; + pattern[0] = dest; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + return true; + } + + public enum tp_mode { + NORMAL, NUMERIC + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/Upc.java b/barcode/src/main/java/org/xbib/graphics/barcode/Upc.java new file mode 100755 index 0000000..43ca5dc --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/Upc.java @@ -0,0 +1,445 @@ +package org.xbib.graphics.barcode; + +import static org.xbib.graphics.barcode.HumanReadableLocation.NONE; +import static org.xbib.graphics.barcode.HumanReadableLocation.TOP; +import org.xbib.graphics.barcode.util.AddOn; +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Rectangle2D; + +/** + * Implements UPC bar code symbology. + * According to BS EN 797:1996 + * UPC-A requires an 11 digit article number. The check digit is calculated. + * UPC-E is a zero-compressed version of UPC-A developed for smaller packages. + * The code requires a 6 digit article number (digits 0-9). The check digit + * is calculated. Also supports Number System 1 encoding by entering a 7-digit + * article number stating with the digit 1. In addition EAN-2 and EAN-5 add-on + * symbols can be added using the + character followed by the add-on data. + * + * @author Robert Elliott + */ +public class Upc extends Symbol { + + private boolean useAddOn; + + ; + private String addOnContent; + private Mode mode; + private boolean linkageFlag; + private String[] setAC = { + "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", + "1213", "3112" + }; + private String[] setB = { + "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", + "3121", "2113" + }; + private String[] UPCParity0 = { + "BBBAAA", "BBABAA", "BBAABA", "BBAAAB", "BABBAA", "BAABBA", "BAAABB", + "BABABA", "BABAAB", "BAABAB" + }; /* Number set for UPC-E symbol (EN Table 4) */ + private String[] UPCParity1 = { + "AAABBB", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA", + "ABABAB", "ABABBA", "ABBABA" + }; /* Not covered by BS EN 797 */ + + public Upc() { + mode = Mode.UPCA; + linkageFlag = false; + } + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + public void setLinkageFlag() { + linkageFlag = true; + } + + public void unsetLinkageFlag() { + linkageFlag = false; + } + + @Override + public void setHumanReadableLocation(HumanReadableLocation humanReadableLocation) { + if (humanReadableLocation == TOP) { + throw new IllegalArgumentException("Cannot display human-readable text above UPC bar codes."); + } else { + super.setHumanReadableLocation(humanReadableLocation); + } + } + + @Override + public boolean encode() { + boolean retval; + AddOn addOn = new AddOn(); + String addOnData = ""; + + separateContent(); + if (content.length() == 0) { + errorMsg.append("Missing UPC data"); + retval = false; + } else { + if (mode == Mode.UPCA) { + retval = upca(); + } else { + retval = upce(); + } + } + + if (useAddOn) { + addOnData = addOn.calcAddOn(addOnContent); + if (addOnData.length() == 0) { + errorMsg.append("Invalid Add-On data"); + retval = false; + } else { + pattern[0] = pattern[0] + "9" + addOnData; + + //add leading zeroes to add-on text + if (addOnContent.length() == 1) { + addOnContent = "0" + addOnContent; + } + if (addOnContent.length() == 3) { + addOnContent = "0" + addOnContent; + } + if (addOnContent.length() == 4) { + addOnContent = "0" + addOnContent; + } + } + } + + if (retval) { + plotSymbol(); + } + return retval; + } + + private void separateContent() { + int splitPoint; + + splitPoint = content.indexOf('+'); + if (splitPoint != -1) { + // There is a '+' in the input data, use an add-on EAN2 or EAN5 + useAddOn = true; + addOnContent = content.substring(splitPoint + 1); + content = content.substring(0, splitPoint); + } + } + + private boolean upca() { + StringBuilder accumulator; + StringBuilder dest; + int i; + char check; + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (content.length() > 11) { + errorMsg.append("Input data too long"); + return false; + } + + accumulator = new StringBuilder(); + for (i = content.length(); i < 11; i++) { + accumulator.append("0"); + } + accumulator.append(content); + check = calcDigit(accumulator.toString()); + accumulator.append(check); + dest = new StringBuilder("111"); + for (i = 0; i < 12; i++) { + if (i == 6) { + dest.append("11111"); + } + dest.append(setAC[Character.getNumericValue(accumulator.charAt(i))]); + } + dest.append("111"); + + encodeInfo.append("Check Digit: ").append(check).append("\n"); + + readable = accumulator; + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + return true; + } + + private boolean upce() { + int i, num_system; + char emode, check; + StringBuilder source; + String parity; + StringBuilder dest; + char[] equivalent = new char[12]; + StringBuilder equiv = new StringBuilder(); + + if (!(content.matches("[0-9]+"))) { + errorMsg.append("Invalid characters in input"); + return false; + } + + if (content.length() > 7) { + errorMsg.append("Input data too long"); + return false; + } + + source = new StringBuilder(); + for (i = content.length(); i < 7; i++) { + source.append("0"); + } + source.append(content); + + /* Two number systems can be used - system 0 and system 1 */ + switch (source.charAt(0)) { + case '0': + num_system = 0; + break; + case '1': + num_system = 1; + break; + default: + errorMsg.append("Invalid input data"); + return false; + } + + /* Expand the zero-compressed UPCE code to make a UPCA equivalent (EN Table 5) */ + emode = source.charAt(6); + for (i = 0; i < 11; i++) { + equivalent[i] = '0'; + } + equivalent[0] = source.charAt(0); + equivalent[1] = source.charAt(1); + equivalent[2] = source.charAt(2); + + switch (emode) { + case '0': + case '1': + case '2': + equivalent[3] = emode; + equivalent[8] = source.charAt(3); + equivalent[9] = source.charAt(4); + equivalent[10] = source.charAt(5); + break; + case '3': + equivalent[3] = source.charAt(3); + equivalent[9] = source.charAt(4); + equivalent[10] = source.charAt(5); + if (((source.charAt(3) == '0') || (source.charAt(3) == '1')) + || (source.charAt(3) == '2')) { + /* Note 1 - "X3 shall not be equal to 0, 1 or 2" */ + errorMsg.append("Invalid UPC-E data"); + return false; + } + break; + case '4': + equivalent[3] = source.charAt(3); + equivalent[4] = source.charAt(4); + equivalent[10] = source.charAt(5); + if (source.charAt(4) == '0') { + /* Note 2 - "X4 shall not be equal to 0" */ + errorMsg.append("Invalid UPC-E data"); + return false; + } + break; + case '5': + case '6': + case '7': + case '8': + case '9': + equivalent[3] = source.charAt(3); + equivalent[4] = source.charAt(4); + equivalent[5] = source.charAt(5); + equivalent[10] = emode; + if (source.charAt(5) == '0') { + /* Note 3 - "X5 shall not be equal to 0" */ + errorMsg.append("Invalid UPC-E data"); + return false; + } + break; + } + + for (i = 0; i < 11; i++) { + equiv.append(equivalent[i]); + } + + /* Get the check digit from the expanded UPCA code */ + check = calcDigit(equiv.toString()); + + encodeInfo.append("Check Digit: ").append(check).append("\n"); + + /* Use the number system and check digit information to choose a parity scheme */ + if (num_system == 1) { + parity = UPCParity1[check - '0']; + } else { + parity = UPCParity0[check - '0']; + } + + /* Take all this information and make the barcode pattern */ + + /* start character */ + dest = new StringBuilder("111"); + + for (i = 0; i <= 5; i++) { + switch (parity.charAt(i)) { + case 'A': + dest.append(setAC[source.charAt(i + 1) - '0']); + break; + case 'B': + dest.append(setB[source.charAt(i + 1) - '0']); + break; + } + } + + /* stop character */ + dest.append("111111"); + + readable = new StringBuilder(source.toString()).append(check); + pattern = new String[1]; + pattern[0] = dest.toString(); + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + return true; + } + + private char calcDigit(String x) { + int count = 0; + int c, cdigit; + for (int i = 0; i < 11; i++) { + c = Character.getNumericValue(x.charAt(i)); + if ((i % 2) == 0) { + c = c * 3; + } + count = count + c; + } + cdigit = 10 - (count % 10); + if (cdigit == 10) { + cdigit = 0; + } + + return (char) (cdigit + '0'); + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + boolean black; + int compositeOffset = 0; + int shortLongDiff = 5; + getRectangles().clear(); + getTexts().clear(); + black = true; + x = 0; + if (linkageFlag) { + compositeOffset = 6; + } + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + if (black) { + y = 0; + black = false; + w = pattern[0].charAt(xBlock) - '0'; + h = defaultHeight; + /* Add extension to guide bars */ + if (mode == Mode.UPCA) { + if ((x < 10) || (x > 84)) { + h += shortLongDiff; + } + if ((x > 45) && (x < 49)) { + h += shortLongDiff; + } + if (x > 95) { + // Drop add-on + h -= 8; + y = 8; + } + if (linkageFlag && (x == 0) || (x == 94)) { + h += 2; + y -= 2; + } + } else { + if ((x < 4) || (x > 45)) { + h += shortLongDiff; + } + if (x > 52) { + // Drop add-on + h -= 8; + y = 8; + } + if (linkageFlag && (x == 0) || (x == 50)) { + h += 2; + y -= 2; + } + } + Rectangle2D.Double rect = new Rectangle2D.Double(x + 6, y + compositeOffset, w, h); + getRectangles().add(rect); + if ((x + w + 12) > symbolWidth) { + symbolWidth = x + w + 12; + } + } else { + black = true; + } + x += pattern[0].charAt(xBlock) - '0'; + } + + if (linkageFlag) { + // Add separator for composite symbology + if (mode == Mode.UPCA) { + getRectangles().add(new Rectangle2D.Double(6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(94 + 6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(-1 + 6, 2, 1, 2)); + getRectangles().add(new Rectangle2D.Double(95 + 6, 2, 1, 2)); + } else { // UPCE + getRectangles().add(new Rectangle2D.Double(0 + 6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(50 + 6, 0, 1, 2)); + getRectangles().add(new Rectangle2D.Double(-1 + 6, 2, 1, 2)); + getRectangles().add(new Rectangle2D.Double(51 + 6, 2, 1, 2)); + } + } + + symbolHeight = defaultHeight + 5; + + /* Now add the text */ + if (getHumanReadableLocation() != NONE) { + double baseline = getHeight() + fontSize - shortLongDiff + compositeOffset; + double addOnBaseline = 6.0 + compositeOffset; + if (mode == Mode.UPCA) { + getTexts().add(new TextBox(3, baseline, readable.substring(0, 1))); + getTexts().add(new TextBox(34, baseline, readable.substring(1, 6))); + getTexts().add(new TextBox(73, baseline, readable.substring(6, 11))); + getTexts().add(new TextBox(104, baseline, readable.substring(11, 12))); + if (useAddOn) { + if (addOnContent.length() == 2) { + getTexts().add(new TextBox(118, addOnBaseline, addOnContent)); + } else { + getTexts().add(new TextBox(133, addOnBaseline, addOnContent)); + } + } + } else { // UPCE + getTexts().add(new TextBox(3, baseline, readable.substring(0, 1))); + getTexts().add(new TextBox(30, baseline, readable.substring(1, 7))); + getTexts().add(new TextBox(61, baseline, readable.substring(7, 8))); + if (useAddOn) { + if (addOnContent.length() == 2) { + getTexts().add(new TextBox(75, addOnBaseline, addOnContent)); + } else { + getTexts().add(new TextBox(90, addOnBaseline, addOnContent)); + } + } + } + } + } + + public enum Mode { + UPCA, UPCE + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/UspsOneCode.java b/barcode/src/main/java/org/xbib/graphics/barcode/UspsOneCode.java new file mode 100755 index 0000000..e3461b8 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/UspsOneCode.java @@ -0,0 +1,451 @@ +package org.xbib.graphics.barcode; + +import static org.xbib.graphics.barcode.HumanReadableLocation.NONE; +import static org.xbib.graphics.barcode.HumanReadableLocation.TOP; +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Rectangle2D; +import java.math.BigInteger; + +/** + * USPS OneCode (also known as Intelligent Mail Barcode) + * According to USPS-B-3200F + * Specification at https://ribbs.usps.gov/intelligentmail_mailpieces/documents/tech_guides/SPUSPSG.pdf + * OneCode is a fixed length (65-bar) symbol which combines routing and + * customer information in a single symbol. Input data consists of a 20 digit + * tracking code, followed by a dash (-), followed by a delivery point + * zip-code which can be 0, 5, 9 or 11 digits in length. + */ +public class UspsOneCode extends Symbol { + + /* The following lookup tables were generated using the code in Appendix C */ + + private int[] byte_array = new int[13]; + + private int[] AppxD_I = { /* Appendix D Table 1 - 5 of 13 characters */ + 0x001F, 0x1F00, 0x002F, 0x1E80, 0x0037, 0x1D80, 0x003B, 0x1B80, 0x003D, 0x1780, + 0x003E, 0x0F80, 0x004F, 0x1E40, 0x0057, 0x1D40, 0x005B, 0x1B40, 0x005D, 0x1740, + 0x005E, 0x0F40, 0x0067, 0x1CC0, 0x006B, 0x1AC0, 0x006D, 0x16C0, 0x006E, 0x0EC0, + 0x0073, 0x19C0, 0x0075, 0x15C0, 0x0076, 0x0DC0, 0x0079, 0x13C0, 0x007A, 0x0BC0, + 0x007C, 0x07C0, 0x008F, 0x1E20, 0x0097, 0x1D20, 0x009B, 0x1B20, 0x009D, 0x1720, + 0x009E, 0x0F20, 0x00A7, 0x1CA0, 0x00AB, 0x1AA0, 0x00AD, 0x16A0, 0x00AE, 0x0EA0, + 0x00B3, 0x19A0, 0x00B5, 0x15A0, 0x00B6, 0x0DA0, 0x00B9, 0x13A0, 0x00BA, 0x0BA0, + 0x00BC, 0x07A0, 0x00C7, 0x1C60, 0x00CB, 0x1A60, 0x00CD, 0x1660, 0x00CE, 0x0E60, + 0x00D3, 0x1960, 0x00D5, 0x1560, 0x00D6, 0x0D60, 0x00D9, 0x1360, 0x00DA, 0x0B60, + 0x00DC, 0x0760, 0x00E3, 0x18E0, 0x00E5, 0x14E0, 0x00E6, 0x0CE0, 0x00E9, 0x12E0, + 0x00EA, 0x0AE0, 0x00EC, 0x06E0, 0x00F1, 0x11E0, 0x00F2, 0x09E0, 0x00F4, 0x05E0, + 0x00F8, 0x03E0, 0x010F, 0x1E10, 0x0117, 0x1D10, 0x011B, 0x1B10, 0x011D, 0x1710, + 0x011E, 0x0F10, 0x0127, 0x1C90, 0x012B, 0x1A90, 0x012D, 0x1690, 0x012E, 0x0E90, + 0x0133, 0x1990, 0x0135, 0x1590, 0x0136, 0x0D90, 0x0139, 0x1390, 0x013A, 0x0B90, + 0x013C, 0x0790, 0x0147, 0x1C50, 0x014B, 0x1A50, 0x014D, 0x1650, 0x014E, 0x0E50, + 0x0153, 0x1950, 0x0155, 0x1550, 0x0156, 0x0D50, 0x0159, 0x1350, 0x015A, 0x0B50, + 0x015C, 0x0750, 0x0163, 0x18D0, 0x0165, 0x14D0, 0x0166, 0x0CD0, 0x0169, 0x12D0, + 0x016A, 0x0AD0, 0x016C, 0x06D0, 0x0171, 0x11D0, 0x0172, 0x09D0, 0x0174, 0x05D0, + 0x0178, 0x03D0, 0x0187, 0x1C30, 0x018B, 0x1A30, 0x018D, 0x1630, 0x018E, 0x0E30, + 0x0193, 0x1930, 0x0195, 0x1530, 0x0196, 0x0D30, 0x0199, 0x1330, 0x019A, 0x0B30, + 0x019C, 0x0730, 0x01A3, 0x18B0, 0x01A5, 0x14B0, 0x01A6, 0x0CB0, 0x01A9, 0x12B0, + 0x01AA, 0x0AB0, 0x01AC, 0x06B0, 0x01B1, 0x11B0, 0x01B2, 0x09B0, 0x01B4, 0x05B0, + 0x01B8, 0x03B0, 0x01C3, 0x1870, 0x01C5, 0x1470, 0x01C6, 0x0C70, 0x01C9, 0x1270, + 0x01CA, 0x0A70, 0x01CC, 0x0670, 0x01D1, 0x1170, 0x01D2, 0x0970, 0x01D4, 0x0570, + 0x01D8, 0x0370, 0x01E1, 0x10F0, 0x01E2, 0x08F0, 0x01E4, 0x04F0, 0x01E8, 0x02F0, + 0x020F, 0x1E08, 0x0217, 0x1D08, 0x021B, 0x1B08, 0x021D, 0x1708, 0x021E, 0x0F08, + 0x0227, 0x1C88, 0x022B, 0x1A88, 0x022D, 0x1688, 0x022E, 0x0E88, 0x0233, 0x1988, + 0x0235, 0x1588, 0x0236, 0x0D88, 0x0239, 0x1388, 0x023A, 0x0B88, 0x023C, 0x0788, + 0x0247, 0x1C48, 0x024B, 0x1A48, 0x024D, 0x1648, 0x024E, 0x0E48, 0x0253, 0x1948, + 0x0255, 0x1548, 0x0256, 0x0D48, 0x0259, 0x1348, 0x025A, 0x0B48, 0x025C, 0x0748, + 0x0263, 0x18C8, 0x0265, 0x14C8, 0x0266, 0x0CC8, 0x0269, 0x12C8, 0x026A, 0x0AC8, + 0x026C, 0x06C8, 0x0271, 0x11C8, 0x0272, 0x09C8, 0x0274, 0x05C8, 0x0278, 0x03C8, + 0x0287, 0x1C28, 0x028B, 0x1A28, 0x028D, 0x1628, 0x028E, 0x0E28, 0x0293, 0x1928, + 0x0295, 0x1528, 0x0296, 0x0D28, 0x0299, 0x1328, 0x029A, 0x0B28, 0x029C, 0x0728, + 0x02A3, 0x18A8, 0x02A5, 0x14A8, 0x02A6, 0x0CA8, 0x02A9, 0x12A8, 0x02AA, 0x0AA8, + 0x02AC, 0x06A8, 0x02B1, 0x11A8, 0x02B2, 0x09A8, 0x02B4, 0x05A8, 0x02B8, 0x03A8, + 0x02C3, 0x1868, 0x02C5, 0x1468, 0x02C6, 0x0C68, 0x02C9, 0x1268, 0x02CA, 0x0A68, + 0x02CC, 0x0668, 0x02D1, 0x1168, 0x02D2, 0x0968, 0x02D4, 0x0568, 0x02D8, 0x0368, + 0x02E1, 0x10E8, 0x02E2, 0x08E8, 0x02E4, 0x04E8, 0x0307, 0x1C18, 0x030B, 0x1A18, + 0x030D, 0x1618, 0x030E, 0x0E18, 0x0313, 0x1918, 0x0315, 0x1518, 0x0316, 0x0D18, + 0x0319, 0x1318, 0x031A, 0x0B18, 0x031C, 0x0718, 0x0323, 0x1898, 0x0325, 0x1498, + 0x0326, 0x0C98, 0x0329, 0x1298, 0x032A, 0x0A98, 0x032C, 0x0698, 0x0331, 0x1198, + 0x0332, 0x0998, 0x0334, 0x0598, 0x0338, 0x0398, 0x0343, 0x1858, 0x0345, 0x1458, + 0x0346, 0x0C58, 0x0349, 0x1258, 0x034A, 0x0A58, 0x034C, 0x0658, 0x0351, 0x1158, + 0x0352, 0x0958, 0x0354, 0x0558, 0x0361, 0x10D8, 0x0362, 0x08D8, 0x0364, 0x04D8, + 0x0383, 0x1838, 0x0385, 0x1438, 0x0386, 0x0C38, 0x0389, 0x1238, 0x038A, 0x0A38, + 0x038C, 0x0638, 0x0391, 0x1138, 0x0392, 0x0938, 0x0394, 0x0538, 0x03A1, 0x10B8, + 0x03A2, 0x08B8, 0x03A4, 0x04B8, 0x03C1, 0x1078, 0x03C2, 0x0878, 0x03C4, 0x0478, + 0x040F, 0x1E04, 0x0417, 0x1D04, 0x041B, 0x1B04, 0x041D, 0x1704, 0x041E, 0x0F04, + 0x0427, 0x1C84, 0x042B, 0x1A84, 0x042D, 0x1684, 0x042E, 0x0E84, 0x0433, 0x1984, + 0x0435, 0x1584, 0x0436, 0x0D84, 0x0439, 0x1384, 0x043A, 0x0B84, 0x043C, 0x0784, + 0x0447, 0x1C44, 0x044B, 0x1A44, 0x044D, 0x1644, 0x044E, 0x0E44, 0x0453, 0x1944, + 0x0455, 0x1544, 0x0456, 0x0D44, 0x0459, 0x1344, 0x045A, 0x0B44, 0x045C, 0x0744, + 0x0463, 0x18C4, 0x0465, 0x14C4, 0x0466, 0x0CC4, 0x0469, 0x12C4, 0x046A, 0x0AC4, + 0x046C, 0x06C4, 0x0471, 0x11C4, 0x0472, 0x09C4, 0x0474, 0x05C4, 0x0487, 0x1C24, + 0x048B, 0x1A24, 0x048D, 0x1624, 0x048E, 0x0E24, 0x0493, 0x1924, 0x0495, 0x1524, + 0x0496, 0x0D24, 0x0499, 0x1324, 0x049A, 0x0B24, 0x049C, 0x0724, 0x04A3, 0x18A4, + 0x04A5, 0x14A4, 0x04A6, 0x0CA4, 0x04A9, 0x12A4, 0x04AA, 0x0AA4, 0x04AC, 0x06A4, + 0x04B1, 0x11A4, 0x04B2, 0x09A4, 0x04B4, 0x05A4, 0x04C3, 0x1864, 0x04C5, 0x1464, + 0x04C6, 0x0C64, 0x04C9, 0x1264, 0x04CA, 0x0A64, 0x04CC, 0x0664, 0x04D1, 0x1164, + 0x04D2, 0x0964, 0x04D4, 0x0564, 0x04E1, 0x10E4, 0x04E2, 0x08E4, 0x0507, 0x1C14, + 0x050B, 0x1A14, 0x050D, 0x1614, 0x050E, 0x0E14, 0x0513, 0x1914, 0x0515, 0x1514, + 0x0516, 0x0D14, 0x0519, 0x1314, 0x051A, 0x0B14, 0x051C, 0x0714, 0x0523, 0x1894, + 0x0525, 0x1494, 0x0526, 0x0C94, 0x0529, 0x1294, 0x052A, 0x0A94, 0x052C, 0x0694, + 0x0531, 0x1194, 0x0532, 0x0994, 0x0534, 0x0594, 0x0543, 0x1854, 0x0545, 0x1454, + 0x0546, 0x0C54, 0x0549, 0x1254, 0x054A, 0x0A54, 0x054C, 0x0654, 0x0551, 0x1154, + 0x0552, 0x0954, 0x0561, 0x10D4, 0x0562, 0x08D4, 0x0583, 0x1834, 0x0585, 0x1434, + 0x0586, 0x0C34, 0x0589, 0x1234, 0x058A, 0x0A34, 0x058C, 0x0634, 0x0591, 0x1134, + 0x0592, 0x0934, 0x05A1, 0x10B4, 0x05A2, 0x08B4, 0x05C1, 0x1074, 0x05C2, 0x0874, + 0x0607, 0x1C0C, 0x060B, 0x1A0C, 0x060D, 0x160C, 0x060E, 0x0E0C, 0x0613, 0x190C, + 0x0615, 0x150C, 0x0616, 0x0D0C, 0x0619, 0x130C, 0x061A, 0x0B0C, 0x061C, 0x070C, + 0x0623, 0x188C, 0x0625, 0x148C, 0x0626, 0x0C8C, 0x0629, 0x128C, 0x062A, 0x0A8C, + 0x062C, 0x068C, 0x0631, 0x118C, 0x0632, 0x098C, 0x0643, 0x184C, 0x0645, 0x144C, + 0x0646, 0x0C4C, 0x0649, 0x124C, 0x064A, 0x0A4C, 0x0651, 0x114C, 0x0652, 0x094C, + 0x0661, 0x10CC, 0x0662, 0x08CC, 0x0683, 0x182C, 0x0685, 0x142C, 0x0686, 0x0C2C, + 0x0689, 0x122C, 0x068A, 0x0A2C, 0x0691, 0x112C, 0x0692, 0x092C, 0x06A1, 0x10AC, + 0x06A2, 0x08AC, 0x06C1, 0x106C, 0x06C2, 0x086C, 0x0703, 0x181C, 0x0705, 0x141C, + 0x0706, 0x0C1C, 0x0709, 0x121C, 0x070A, 0x0A1C, 0x0711, 0x111C, 0x0712, 0x091C, + 0x0721, 0x109C, 0x0722, 0x089C, 0x0741, 0x105C, 0x0742, 0x085C, 0x0781, 0x103C, + 0x0782, 0x083C, 0x080F, 0x1E02, 0x0817, 0x1D02, 0x081B, 0x1B02, 0x081D, 0x1702, + 0x081E, 0x0F02, 0x0827, 0x1C82, 0x082B, 0x1A82, 0x082D, 0x1682, 0x082E, 0x0E82, + 0x0833, 0x1982, 0x0835, 0x1582, 0x0836, 0x0D82, 0x0839, 0x1382, 0x083A, 0x0B82, + 0x0847, 0x1C42, 0x084B, 0x1A42, 0x084D, 0x1642, 0x084E, 0x0E42, 0x0853, 0x1942, + 0x0855, 0x1542, 0x0856, 0x0D42, 0x0859, 0x1342, 0x085A, 0x0B42, 0x0863, 0x18C2, + 0x0865, 0x14C2, 0x0866, 0x0CC2, 0x0869, 0x12C2, 0x086A, 0x0AC2, 0x0871, 0x11C2, + 0x0872, 0x09C2, 0x0887, 0x1C22, 0x088B, 0x1A22, 0x088D, 0x1622, 0x088E, 0x0E22, + 0x0893, 0x1922, 0x0895, 0x1522, 0x0896, 0x0D22, 0x0899, 0x1322, 0x089A, 0x0B22, + 0x08A3, 0x18A2, 0x08A5, 0x14A2, 0x08A6, 0x0CA2, 0x08A9, 0x12A2, 0x08AA, 0x0AA2, + 0x08B1, 0x11A2, 0x08B2, 0x09A2, 0x08C3, 0x1862, 0x08C5, 0x1462, 0x08C6, 0x0C62, + 0x08C9, 0x1262, 0x08CA, 0x0A62, 0x08D1, 0x1162, 0x08D2, 0x0962, 0x08E1, 0x10E2, + 0x0907, 0x1C12, 0x090B, 0x1A12, 0x090D, 0x1612, 0x090E, 0x0E12, 0x0913, 0x1912, + 0x0915, 0x1512, 0x0916, 0x0D12, 0x0919, 0x1312, 0x091A, 0x0B12, 0x0923, 0x1892, + 0x0925, 0x1492, 0x0926, 0x0C92, 0x0929, 0x1292, 0x092A, 0x0A92, 0x0931, 0x1192, + 0x0932, 0x0992, 0x0943, 0x1852, 0x0945, 0x1452, 0x0946, 0x0C52, 0x0949, 0x1252, + 0x094A, 0x0A52, 0x0951, 0x1152, 0x0961, 0x10D2, 0x0983, 0x1832, 0x0985, 0x1432, + 0x0986, 0x0C32, 0x0989, 0x1232, 0x098A, 0x0A32, 0x0991, 0x1132, 0x09A1, 0x10B2, + 0x09C1, 0x1072, 0x0A07, 0x1C0A, 0x0A0B, 0x1A0A, 0x0A0D, 0x160A, 0x0A0E, 0x0E0A, + 0x0A13, 0x190A, 0x0A15, 0x150A, 0x0A16, 0x0D0A, 0x0A19, 0x130A, 0x0A1A, 0x0B0A, + 0x0A23, 0x188A, 0x0A25, 0x148A, 0x0A26, 0x0C8A, 0x0A29, 0x128A, 0x0A2A, 0x0A8A, + 0x0A31, 0x118A, 0x0A43, 0x184A, 0x0A45, 0x144A, 0x0A46, 0x0C4A, 0x0A49, 0x124A, + 0x0A51, 0x114A, 0x0A61, 0x10CA, 0x0A83, 0x182A, 0x0A85, 0x142A, 0x0A86, 0x0C2A, + 0x0A89, 0x122A, 0x0A91, 0x112A, 0x0AA1, 0x10AA, 0x0AC1, 0x106A, 0x0B03, 0x181A, + 0x0B05, 0x141A, 0x0B06, 0x0C1A, 0x0B09, 0x121A, 0x0B11, 0x111A, 0x0B21, 0x109A, + 0x0B41, 0x105A, 0x0B81, 0x103A, 0x0C07, 0x1C06, 0x0C0B, 0x1A06, 0x0C0D, 0x1606, + 0x0C0E, 0x0E06, 0x0C13, 0x1906, 0x0C15, 0x1506, 0x0C16, 0x0D06, 0x0C19, 0x1306, + 0x0C23, 0x1886, 0x0C25, 0x1486, 0x0C26, 0x0C86, 0x0C29, 0x1286, 0x0C31, 0x1186, + 0x0C43, 0x1846, 0x0C45, 0x1446, 0x0C49, 0x1246, 0x0C51, 0x1146, 0x0C61, 0x10C6, + 0x0C83, 0x1826, 0x0C85, 0x1426, 0x0C89, 0x1226, 0x0C91, 0x1126, 0x0CA1, 0x10A6, + 0x0CC1, 0x1066, 0x0D03, 0x1816, 0x0D05, 0x1416, 0x0D09, 0x1216, 0x0D11, 0x1116, + 0x0D21, 0x1096, 0x0D41, 0x1056, 0x0D81, 0x1036, 0x0E03, 0x180E, 0x0E05, 0x140E, + 0x0E09, 0x120E, 0x0E11, 0x110E, 0x0E21, 0x108E, 0x0E41, 0x104E, 0x0E81, 0x102E, + 0x0F01, 0x101E, 0x100F, 0x1E01, 0x1017, 0x1D01, 0x101B, 0x1B01, 0x101D, 0x1701, + 0x1027, 0x1C81, 0x102B, 0x1A81, 0x102D, 0x1681, 0x1033, 0x1981, 0x1035, 0x1581, + 0x1039, 0x1381, 0x1047, 0x1C41, 0x104B, 0x1A41, 0x104D, 0x1641, 0x1053, 0x1941, + 0x1055, 0x1541, 0x1059, 0x1341, 0x1063, 0x18C1, 0x1065, 0x14C1, 0x1069, 0x12C1, + 0x1071, 0x11C1, 0x1087, 0x1C21, 0x108B, 0x1A21, 0x108D, 0x1621, 0x1093, 0x1921, + 0x1095, 0x1521, 0x1099, 0x1321, 0x10A3, 0x18A1, 0x10A5, 0x14A1, 0x10A9, 0x12A1, + 0x10B1, 0x11A1, 0x10C3, 0x1861, 0x10C5, 0x1461, 0x10C9, 0x1261, 0x10D1, 0x1161, + 0x1107, 0x1C11, 0x110B, 0x1A11, 0x110D, 0x1611, 0x1113, 0x1911, 0x1115, 0x1511, + 0x1119, 0x1311, 0x1123, 0x1891, 0x1125, 0x1491, 0x1129, 0x1291, 0x1131, 0x1191, + 0x1143, 0x1851, 0x1145, 0x1451, 0x1149, 0x1251, 0x1183, 0x1831, 0x1185, 0x1431, + 0x1189, 0x1231, 0x1207, 0x1C09, 0x120B, 0x1A09, 0x120D, 0x1609, 0x1213, 0x1909, + 0x1215, 0x1509, 0x1219, 0x1309, 0x1223, 0x1889, 0x1225, 0x1489, 0x1229, 0x1289, + 0x1243, 0x1849, 0x1245, 0x1449, 0x1283, 0x1829, 0x1285, 0x1429, 0x1303, 0x1819, + 0x1305, 0x1419, 0x1407, 0x1C05, 0x140B, 0x1A05, 0x140D, 0x1605, 0x1413, 0x1905, + 0x1415, 0x1505, 0x1423, 0x1885, 0x1425, 0x1485, 0x1443, 0x1845, 0x1483, 0x1825, + 0x1503, 0x1815, 0x1603, 0x180D, 0x1807, 0x1C03, 0x180B, 0x1A03, 0x1813, 0x1903, + 0x1823, 0x1883, 0x1843, 0x1445, 0x1249, 0x1151, 0x10E1, 0x0C46, 0x0A4A, 0x0952, + 0x08E2, 0x064C, 0x0554, 0x04E4, 0x0358, 0x02E8, 0x01F0 + }; + private int[] AppxD_II = { /* Appendix D Table II - 2 of 13 characters */ + 0x0003, 0x1800, 0x0005, 0x1400, 0x0006, 0x0C00, 0x0009, 0x1200, 0x000A, 0x0A00, + 0x000C, 0x0600, 0x0011, 0x1100, 0x0012, 0x0900, 0x0014, 0x0500, 0x0018, 0x0300, + 0x0021, 0x1080, 0x0022, 0x0880, 0x0024, 0x0480, 0x0028, 0x0280, 0x0030, 0x0180, + 0x0041, 0x1040, 0x0042, 0x0840, 0x0044, 0x0440, 0x0048, 0x0240, 0x0050, 0x0140, + 0x0060, 0x00C0, 0x0081, 0x1020, 0x0082, 0x0820, 0x0084, 0x0420, 0x0088, 0x0220, + 0x0090, 0x0120, 0x0101, 0x1010, 0x0102, 0x0810, 0x0104, 0x0410, 0x0108, 0x0210, + 0x0201, 0x1008, 0x0202, 0x0808, 0x0204, 0x0408, 0x0401, 0x1004, 0x0402, 0x0804, + 0x0801, 0x1002, 0x1001, 0x0802, 0x0404, 0x0208, 0x0110, 0x00A0 + }; + private int[] AppxD_IV = { /* Appendix D Table IV - Bar-to-Character Mapping (reverse lookup) */ + 67, 6, 78, 16, 86, 95, 34, 40, 45, 113, 117, 121, 62, 87, 18, 104, 41, 76, 57, 119, 115, 72, 97, + 2, 127, 26, 105, 35, 122, 52, 114, 7, 24, 82, 68, 63, 94, 44, 77, 112, 70, 100, 39, 30, 107, + 15, 125, 85, 10, 65, 54, 88, 20, 106, 46, 66, 8, 116, 29, 61, 99, 80, 90, 37, 123, 51, 25, 84, + 129, 56, 4, 109, 96, 28, 36, 47, 11, 71, 33, 102, 21, 9, 17, 49, 124, 79, 64, 91, 42, 69, 53, + 60, 14, 1, 27, 103, 126, 75, 89, 50, 120, 19, 32, 110, 92, 111, 130, 59, 31, 12, 81, 43, 55, + 5, 74, 22, 101, 128, 58, 118, 48, 108, 38, 98, 93, 23, 83, 13, 73, 3 + }; + + public UspsOneCode() { + this.defaultHeight = 8; + setHumanReadableLocation(HumanReadableLocation.NONE); + } + + @Override + public boolean encode() { + StringBuilder zip = new StringBuilder(); + String zip_adder; + StringBuilder tracker = new StringBuilder(); + int i, j; + int length = content.length(); + BigInteger accum; + BigInteger x_reg; + BigInteger mask; + int usps_crc; + int[] codeword = new int[10]; + int[] characters = new int[10]; + boolean[] bar_map = new boolean[130]; + char c; + + if (!content.matches("[0-9\u002D]+")) { + errorMsg.append("Invalid characters in input data"); + return false; + } + + if (length > 32) { + errorMsg.append("Input too long"); + return false; + } + + /* separate the tracking code from the routing code */ + j = 0; + for (i = 0; i < length; i++) { + if (content.charAt(i) == '-') { + j = 1; + } else { + if (j == 0) { + /* reading tracker */ + tracker.append(content.charAt(i)); + } else { + /* reading zip code */ + zip.append(content.charAt(i)); + } + } + } + + if (tracker.length() != 20) { + errorMsg.append("Invalid length tracking code"); + return false; + } + + if (zip.length() > 11) { + errorMsg.append("Invalid ZIP code"); + return false; + } + + /* *** Step 1 - Conversion of Data Fields into Binary Data *** */ + /* Routing code first */ + if (zip.length() > 0) { + x_reg = new BigInteger(zip.toString()); + } else { + x_reg = new BigInteger("0"); + } + + /* add weight to routing code */ + if (zip.length() > 9) { + zip_adder = "1000100001"; + } else { + if (zip.length() > 5) { + zip_adder = "100001"; + } else { + if (zip.length() > 0) { + zip_adder = "1"; + } else { + zip_adder = "0"; + } + } + } + + accum = new BigInteger(zip_adder); + accum = accum.add(x_reg); + accum = accum.multiply(BigInteger.valueOf(10)); + accum = accum.add(BigInteger.valueOf(Character.getNumericValue(tracker.charAt(0)))); + accum = accum.multiply(BigInteger.valueOf(5)); + accum = accum.add(BigInteger.valueOf(Character.getNumericValue(tracker.charAt(1)))); + for (i = 2; i < tracker.length(); i++) { + accum = accum.multiply(BigInteger.valueOf(10)); + accum = accum.add(BigInteger.valueOf(Character.getNumericValue(tracker.charAt(i)))); + } + + /* *** Step 2 - Generation of 11-bit CRC on Binary Data *** */ + + for (i = 0; i < 13; i++) { + mask = accum.shiftRight(96 - (8 * i)); + mask = mask.and(new BigInteger("255")); + byte_array[i] = mask.intValue(); + } + + usps_crc = USPS_MSB_Math_CRC11GenerateFrameCheckSequence(); + + /* *** Step 3 - Conversion from Binary Data to Codewords *** */ + /* start with codeword J which is base 636 */ + + x_reg = accum.mod(BigInteger.valueOf(636)); + codeword[9] = x_reg.intValue(); + accum = accum.subtract(x_reg); + accum = accum.divide(BigInteger.valueOf(636)); + + for (i = 8; i >= 0; i--) { + x_reg = accum.mod(BigInteger.valueOf(1365)); + codeword[i] = x_reg.intValue(); + accum = accum.subtract(x_reg); + accum = accum.divide(BigInteger.valueOf(1365)); + } + + for (i = 0; i < 9; i++) { + if (codeword[i] == 1365) { + codeword[i] = 0; + } + } + + /* *** Step 4 - Inserting Additional Information into Codewords *** */ + codeword[9] = codeword[9] * 2; + + if (usps_crc >= 1024) { + codeword[0] += 659; + } + + encodeInfo.append("Codewords: "); + for (i = 0; i < 10; i++) { + encodeInfo.append(Integer.toString(codeword[i])).append(" "); + } + encodeInfo.append("\n"); + + /* *** Step 5 - Conversion from Codewords to Characters *** */ + + for (i = 0; i < 10; i++) { + if (codeword[i] < 1287) { + characters[i] = AppxD_I[codeword[i]]; + } else { + characters[i] = AppxD_II[codeword[i] - 1287]; + } + } + + for (i = 0; i < 10; i++) { + if ((usps_crc & (1 << i)) != 0) { + characters[i] = 0x1FFF - characters[i]; + } + } + + /* *** Step 6 - Conversion from Characters to the Intelligent Mail Barcode *** */ + + for (i = 0; i < 10; i++) { + for (j = 0; j < 13; j++) { + bar_map[AppxD_IV[(13 * i) + j] - 1] = (characters[i] & (1 << j)) != 0; + } + } + + readable = new StringBuilder(content); + pattern = new String[1]; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + + pattern[0] = ""; + for (i = 0; i < 65; i++) { + c = 'T'; + if (bar_map[i]) { + c = 'D'; + } + if (bar_map[i + 65]) { + c = 'A'; + } + if (bar_map[i] && bar_map[i + 65]) { + c = 'F'; + } + pattern[0] += c; + } + + encodeInfo.append("Encoding: ").append(pattern[0]).append("\n"); + + plotSymbol(); + return true; + } + + private int USPS_MSB_Math_CRC11GenerateFrameCheckSequence() { + int GeneratorPolynomial = 0x0F35; + int FrameCheckSequence = 0x07FF; + int Data; + int ByteIndex, Bit; + int ByteArrayPtr = 0; + + /* Do most significant byte skipping the 2 most significant bits */ + Data = byte_array[ByteArrayPtr] << 5; + ByteArrayPtr++; + for (Bit = 2; Bit < 8; Bit++) { + if (((FrameCheckSequence ^ Data) & 0x400) != 0) + FrameCheckSequence = (FrameCheckSequence << 1) ^ GeneratorPolynomial; + else + FrameCheckSequence = (FrameCheckSequence << 1); + FrameCheckSequence &= 0x7FF; + Data <<= 1; + } + /* Do rest of the bytes */ + for (ByteIndex = 1; ByteIndex < 13; ByteIndex++) { + Data = byte_array[ByteArrayPtr] << 3; + ByteArrayPtr++; + for (Bit = 0; Bit < 8; Bit++) { + if (((FrameCheckSequence ^ Data) & 0x0400) != 0) { + FrameCheckSequence = (FrameCheckSequence << 1) ^ GeneratorPolynomial; + } else { + FrameCheckSequence = (FrameCheckSequence << 1); + } + FrameCheckSequence &= 0x7FF; + Data <<= 1; + } + } + return FrameCheckSequence; + } + + @Override + protected void plotSymbol() { + int xBlock, shortHeight, longHeight; + double x, y, w, h; + getRectangles().clear(); + getTexts().clear(); + int baseY; + if (getHumanReadableLocation() == TOP) { + baseY = getTheoreticalHumanReadableHeight(); + } else { + baseY = 0; + } + + x = 0; + w = moduleWidth; + y = 0; + h = 0; + shortHeight = (int) (0.25 * defaultHeight); + longHeight = (int) (0.625 * defaultHeight); + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + + switch (pattern[0].charAt(xBlock)) { + case 'A': + y = baseY; + h = longHeight; + break; + case 'D': + y = baseY + defaultHeight - longHeight; + h = longHeight; + break; + case 'F': + y = baseY; + h = defaultHeight; + break; + case 'T': + y = baseY + defaultHeight - longHeight; + h = shortHeight; + break; + } + Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); + getRectangles().add(rect); + x += (2.43 * w); + } + symbolWidth = (int) Math.ceil(((pattern[0].length() - 1) * 2.43 * w) + w); // final bar doesn't need extra whitespace + symbolHeight = defaultHeight; + if (getHumanReadableLocation() != NONE && readable.length() > 0) { + double baseline; + if (getHumanReadableLocation() == TOP) { + baseline = fontSize; + } else { + baseline = getHeight() + fontSize; + } + double centerX = getWidth() / 2.0; + getTexts().add(new TextBox(centerX, baseline, readable.toString())); + } + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/UspsPackage.java b/barcode/src/main/java/org/xbib/graphics/barcode/UspsPackage.java new file mode 100755 index 0000000..0289810 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/UspsPackage.java @@ -0,0 +1,114 @@ +package org.xbib.graphics.barcode; + +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.geom.Rectangle2D; + +/** + * USPS Intelligent Mail Package Barcode (IMpb)
+ * A linear barcode based on GS1-128. Includes additional data checks. + * Specification at https://ribbs.usps.gov/intelligentmail_package/documents/tech_guides/BarcodePackageIMSpec.pdf + */ +public class UspsPackage extends Symbol { + + @Override + public boolean encode() { + StringBuilder hrt; + StringBuilder spacedHrt; + boolean fourTwenty = false; + int bracketCount = 0; + + if (!(content.matches("[0-9\\[\\]]+"))) { + /* Input must be numeric only */ + errorMsg.append("Invalid IMpd data"); + return false; + } + + if ((content.length() % 2) != 0) { + /* Input must be even length */ + errorMsg.append("Invalid IMpd data"); + return false; + } + + Code128 code128 = new Code128(); + code128.unsetCc(); + code128.setDataType(DataType.GS1); + code128.setContent(content); + + if (content.length() > 4) { + fourTwenty = ((content.charAt(1) == '4') && (content.charAt(2) == '2') && + (content.charAt(3) == '0')); + } + + hrt = new StringBuilder(); + for (int i = 0; i < content.length(); i++) { + if (content.charAt(i) == '[') { + bracketCount++; + } + if (!(fourTwenty && bracketCount < 2)) { + if ((content.charAt(i) >= '0') && (content.charAt(i) <= '9')) { + hrt.append(content.charAt(i)); + } + } + } + + spacedHrt = new StringBuilder(); + for (int i = 0; i < hrt.length(); i++) { + spacedHrt.append(hrt.charAt(i)); + if (i % 4 == 3) { + spacedHrt.append(" "); + } + } + + readable = new StringBuilder(spacedHrt.toString()); + pattern = new String[1]; + pattern[0] = code128.pattern[0]; + rowCount = 1; + rowHeight = new int[1]; + rowHeight[0] = -1; + plotSymbol(); + + return true; + } + + @Override + protected void plotSymbol() { + int xBlock; + int x, y, w, h; + boolean black; + int offset = 20; + int yoffset = 15; + String banner = "USPS TRACKING #"; + getRectangles().clear(); + getTexts().clear(); + y = yoffset; + h = 0; + black = true; + x = 0; + for (xBlock = 0; xBlock < pattern[0].length(); xBlock++) { + w = pattern[0].charAt(xBlock) - '0'; + if (black) { + if (rowHeight[0] == -1) { + h = defaultHeight; + } else { + h = rowHeight[0]; + } + if (w != 0 && h != 0) { + Rectangle2D.Double rect = new Rectangle2D.Double(x + offset, y, w, h); + getRectangles().add(rect); + } + symbolWidth = x + w + (2 * offset); + } + black = !black; + x += w; + } + symbolHeight = h + (2 * yoffset); + // Add boundary bars + Rectangle2D.Double topBar = new Rectangle2D.Double(0, 0, symbolWidth, 2); + Rectangle2D.Double bottomBar = new Rectangle2D.Double(0, symbolHeight - 2, symbolWidth, 2); + getRectangles().add(topBar); + getRectangles().add(bottomBar); + double centerX = getWidth() / 2.0; + getTexts().add(new TextBox(centerX, getHeight() - 6.0, readable.toString())); + getTexts().add(new TextBox(centerX, 12.0, banner)); + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/render/GraphicsRenderer.java b/barcode/src/main/java/org/xbib/graphics/barcode/render/GraphicsRenderer.java new file mode 100755 index 0000000..59f2609 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/render/GraphicsRenderer.java @@ -0,0 +1,150 @@ +package org.xbib.graphics.barcode.render; + +import org.xbib.graphics.barcode.HumanReadableLocation; +import org.xbib.graphics.barcode.Symbol; +import org.xbib.graphics.barcode.util.Hexagon; +import org.xbib.graphics.barcode.util.TextBox; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.font.TextAttribute; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Renders symbologies using the Java Graphics API. + */ +public class GraphicsRenderer { + + /** + * The graphics to render to. + */ + private final Graphics2D g2d; + + /** + * The scaling factor. + */ + private final double scalingFactor; + + /** + * The paper (background) color. + */ + private final Color background; + + /** + * The ink (foreground) color. + */ + private final Color foreground; + + private final boolean antialias; + + private final boolean transparentBackground; + + /** + * Creates a new Java 2D renderer. + * + * @param g2d the graphics to render to + * @param scalingFactor the scaling factor to apply + * @param background the paper (background) color + * @param foreground the ink (foreground) color + * @param antialias if true give anti alias hint + */ + public GraphicsRenderer(Graphics2D g2d, + double scalingFactor, + Color background, + Color foreground, + boolean antialias, + boolean transparentBackground) { + this.g2d = g2d; + this.scalingFactor = scalingFactor; + this.background = background; + this.foreground = foreground; + this.antialias = antialias; + this.transparentBackground = transparentBackground; + } + + public void render(Symbol symbol) { + RenderingHints oldRenderingHints = g2d.getRenderingHints(); + Color oldColor = g2d.getColor(); + Color oldBackground = g2d.getBackground(); + if (antialias) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } else { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + } + double marginX = symbol.getQuietZoneHorizontal() * scalingFactor; + double marginY = symbol.getQuietZoneVertical() * scalingFactor; + g2d.setBackground(background); + if (!transparentBackground) { + g2d.setColor(background); + g2d.fill(g2d.getDeviceConfiguration().getBounds()); + g2d.setColor(foreground); + } + for (Rectangle2D.Double rect : symbol.getRectangles()) { + double x = rect.x * scalingFactor + marginX; + double y = rect.y * scalingFactor + marginY; + double w = rect.width * scalingFactor; + double h = rect.height * scalingFactor; + Path2D path = new Path2D.Double(); + path.moveTo(x, y); + path.lineTo(x + w, y); + path.lineTo(x + w, y + h); + path.lineTo(x, y + h); + path.closePath(); + g2d.fill(path); + } + if (symbol.getHumanReadableLocation() != HumanReadableLocation.NONE) { + Map attributes = new HashMap<>(); + attributes.put(TextAttribute.TRACKING, 0); + Font f = new Font(symbol.getFontName(), Font.PLAIN, (int) (symbol.getFontSize() * scalingFactor)).deriveFont(attributes); + Font oldFont = g2d.getFont(); + g2d.setFont(f); + FontMetrics fm = g2d.getFontMetrics(); + for (TextBox text : symbol.getTexts()) { + Rectangle2D bounds = fm.getStringBounds(text.text, g2d); + double x = (text.x * scalingFactor) - (bounds.getWidth() / 2) + marginX; + double y = (text.y * scalingFactor) + marginY; + g2d.drawString(text.text, (float) x, (float) y); + } + g2d.setFont(oldFont); + } + for (Hexagon hexagon : symbol.getHexagons()) { + Path2D path = new Path2D.Double(); + path.moveTo(hexagon.pointX[0] * scalingFactor + marginX, hexagon.pointY[0] * scalingFactor + marginY); + for(int i = 1; i < 6; ++i) { + double x = hexagon.pointX[i] * scalingFactor + marginX; + double y = hexagon.pointY[i] * scalingFactor + marginY; + path.lineTo(x, y); + } + path.closePath(); + g2d.fill(path); + } + List targets = symbol.getTarget(); + for (int i = 0; i < targets.size(); i++) { + Ellipse2D.Double ellipse = targets.get(i); + double x = ellipse.x * scalingFactor + marginX; + double y = ellipse.y * scalingFactor + marginY; + double w = ellipse.width * scalingFactor; + double h = ellipse.height * scalingFactor; + if ((i & 1) == 0) { + g2d.setColor(foreground); + } else { + g2d.setColor(background); + } + g2d.fill(new Ellipse2D.Double(x, y, w, h)); + } + g2d.setColor(oldColor); + g2d.setBackground(oldBackground); + g2d.setRenderingHints(oldRenderingHints); + } + + public void close() { + g2d.dispose(); + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/util/AddOn.java b/barcode/src/main/java/org/xbib/graphics/barcode/util/AddOn.java new file mode 100755 index 0000000..4499e80 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/util/AddOn.java @@ -0,0 +1,112 @@ +package org.xbib.graphics.barcode.util; + +/** + * Encode Add-On barcodes from UPC/EAN. + */ +public class AddOn { + private String content; + private String dest; + + private String[] EANsetA = { + "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213", + "3112" + }; + private String[] EANsetB = { + "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121", + "2113" + }; + + private String[] EAN2Parity = { + "AA", "AB", "BA", "BB" + }; + private String[] EAN5Parity = { + "BBAAA", "BABAA", "BAABA", "BAAAB", "ABBAA", "AABBA", "AAABB", "ABABA", + "ABAAB", "AABAB" + }; + + public String calcAddOn(String input) { + dest = ""; + content = input; + + if (!(content.matches("[0-9]{1,5}"))) { + return ""; + } + + if (content.length() > 2) { + ean5(); + } else { + ean2(); + } + + return dest; + } + + private void ean2() { + String parity; + String accumulator = ""; + int i, code_value; + + if (!(content.matches("[0-9]+?"))) { + return; + } + + for (i = content.length(); i < 2; i++) { + accumulator += "0"; + } + accumulator += content; + + code_value = ((accumulator.charAt(0) - '0') * 10) + + (accumulator.charAt(1) - '0'); + parity = EAN2Parity[code_value % 4]; + + dest = "112"; /* Start */ + for (i = 0; i < 2; i++) { + if ((parity.charAt(i) == 'B')) { + dest += EANsetB[Character.getNumericValue(accumulator.charAt(i))]; + } else { + dest += EANsetA[Character.getNumericValue(accumulator.charAt(i))]; + } + if (i != 1) { /* Glyph separator */ + dest += "11"; + } + } + } + + private void ean5() { + String parity; + String accumulator = ""; + int i, parity_sum; + + if (!(content.matches("[0-9]+?"))) { + return; + } + + for (i = content.length(); i < 5; i++) { + accumulator += "0"; + } + accumulator += content; + + parity_sum = 0; + for (i = 0; i < 5; i++) { + if ((i % 2) == 0) { + parity_sum += 3 * (accumulator.charAt(i) - '0'); + } else { + parity_sum += 9 * (accumulator.charAt(i) - '0'); + } + } + + parity = EAN5Parity[parity_sum % 10]; + + dest = "112"; /* Start */ + for (i = 0; i < 5; i++) { + if ((parity.charAt(i) == 'B')) { + dest += EANsetB[Character.getNumericValue(accumulator.charAt(i))]; + } else { + dest += EANsetA[Character.getNumericValue(accumulator.charAt(i))]; + } + if (i != 4) { /* Glyph separator */ + dest += "11"; + } + } + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/util/Hexagon.java b/barcode/src/main/java/org/xbib/graphics/barcode/util/Hexagon.java new file mode 100755 index 0000000..8b27f37 --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/util/Hexagon.java @@ -0,0 +1,23 @@ +package org.xbib.graphics.barcode.util; + +/** + * Calculate a set of points to make a hexagon. + */ +public class Hexagon { + + private static final double INK_SPREAD = 1.25; + + private static final double[] OFFSET_X = {0.0, 0.86, 0.86, 0.0, -0.86, -0.86}; + + private static final double[] OFFSET_Y = {1.0, 0.5, -0.5, -1.0, -0.5, 0.5}; + + public final double[] pointX = new double[6]; + public final double[] pointY = new double[6]; + + public Hexagon(double centreX, double centreY) { + for (int i = 0; i < 6; i++) { + pointX[i] = centreX + (OFFSET_X[i] * INK_SPREAD); + pointY[i] = centreY + (OFFSET_Y[i] * INK_SPREAD); + } + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/util/ReedSolomon.java b/barcode/src/main/java/org/xbib/graphics/barcode/util/ReedSolomon.java new file mode 100755 index 0000000..30770dc --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/util/ReedSolomon.java @@ -0,0 +1,86 @@ +package org.xbib.graphics.barcode.util; + +/** + */ +public class ReedSolomon { + private int[] res; + private int logmod; + private int rlen; + private int[] logt; + private int[] alog; + private int[] rspoly; + + public int getResult(int count) { + return res[count]; + } + + public void init_gf(int poly) { + int m, b, p, v; + + // Find the top bit, and hence the symbol size + for (b = 1, m = 0; b <= poly; b <<= 1) { + m++; + } + b >>= 1; + m--; + + // Calculate the log/alog tables + logmod = (1 << m) - 1; + logt = new int[logmod + 1]; + alog = new int[logmod]; + + for (p = 1, v = 0; v < logmod; v++) { + alog[v] = p; + logt[p] = v; + p <<= 1; + if ((p & b) != 0) { + p ^= poly; + } + } + } + + public void init_code(int nsym, int index) { + int i, k; + + rspoly = new int[nsym + 1]; + + rlen = nsym; + + rspoly[0] = 1; + for (i = 1; i <= nsym; i++) { + rspoly[i] = 1; + for (k = i - 1; k > 0; k--) { + if (rspoly[k] != 0) { + rspoly[k] = alog[(logt[rspoly[k]] + index) % logmod]; + } + rspoly[k] ^= rspoly[k - 1]; + } + rspoly[0] = alog[(logt[rspoly[0]] + index) % logmod]; + index++; + } + } + + public void encode(int len, int[] data) { + int i, k, m; + + res = new int[rlen]; + for (i = 0; i < rlen; i++) { + res[i] = 0; + } + for (i = 0; i < len; i++) { + m = res[rlen - 1] ^ data[i]; + for (k = rlen - 1; k > 0; k--) { + if ((m != 0) && (rspoly[k] != 0)) { + res[k] = res[k - 1] ^ alog[(logt[m] + logt[rspoly[k]]) % logmod]; + } else { + res[k] = res[k - 1]; + } + } + if ((m != 0) && (rspoly[0] != 0)) { + res[0] = alog[(logt[m] + logt[rspoly[0]]) % logmod]; + } else { + res[0] = 0; + } + } + } +} diff --git a/barcode/src/main/java/org/xbib/graphics/barcode/util/TextBox.java b/barcode/src/main/java/org/xbib/graphics/barcode/util/TextBox.java new file mode 100755 index 0000000..e5a438e --- /dev/null +++ b/barcode/src/main/java/org/xbib/graphics/barcode/util/TextBox.java @@ -0,0 +1,40 @@ +package org.xbib.graphics.barcode.util; + +/** + * A simple text item class. + */ +public class TextBox { + + /** + * X position that the text should be centered on horizontally. + */ + public final double x; + + /** + * Y position of the text baseline. + */ + public final double y; + + /** + * Text value. + */ + public final String text; + + /** + * Creates a new instance. + * + * @param x the X position that the text should be centered on horizontally + * @param y the Y position of the text baseline + * @param text the text value + */ + public TextBox(double x, double y, String text) { + this.x = x; + this.y = y; + this.text = text; + } + + @Override + public String toString() { + return "TextBox[x=" + x + ", y=" + y + ", text=" + text + "]"; + } +} diff --git a/barcode/src/test/java/org/xbib/graphics/barcode/MaxiCodeTest.java b/barcode/src/test/java/org/xbib/graphics/barcode/MaxiCodeTest.java new file mode 100755 index 0000000..b0aecce --- /dev/null +++ b/barcode/src/test/java/org/xbib/graphics/barcode/MaxiCodeTest.java @@ -0,0 +1,19 @@ +package org.xbib.graphics.barcode; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +/** + * {@link MaxiCode} tests that can't be run via the {@link SymbolTest}. + */ +public class MaxiCodeTest { + + @Test + public void testHumanReadableHeight() { + MaxiCode maxicode = new MaxiCode(); + maxicode.setMode(4); + maxicode.setContent("ABC"); + assertEquals(0, maxicode.getHumanReadableHeight()); + } + +} diff --git a/barcode/src/test/java/org/xbib/graphics/barcode/ParameterizedExtension.java b/barcode/src/test/java/org/xbib/graphics/barcode/ParameterizedExtension.java new file mode 100644 index 0000000..41a5eac --- /dev/null +++ b/barcode/src/test/java/org/xbib/graphics/barcode/ParameterizedExtension.java @@ -0,0 +1,253 @@ +package org.xbib.graphics.barcode; + +import static java.util.Collections.singletonList; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.platform.commons.util.CollectionUtils; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.runners.Parameterized; + +public class ParameterizedExtension implements TestTemplateInvocationContextProvider { + + private final static ExtensionContext.Namespace PARAMETERS = ExtensionContext.Namespace.create( + ParameterizedExtension.class); + + /** + * Indicate whether we can provide parameterized support. + * This requires the testClass to either have a static {@code @Parameters} method + * and correct {@code @Parameter} and their corresponding values + * or to have a constructor that could be injected. + */ + public boolean supportsTestTemplate(ExtensionContext context) { + return hasParametersMethod(context) && validInjectionMix(context); + } + + private static boolean validInjectionMix(ExtensionContext context) { + List fields = parametersFields(context); + boolean hasParameterFields = !fields.isEmpty(); + boolean hasCorrectParameterFields = areParametersFormedCorrectly(fields); + boolean hasArgsConstructor = hasArgsConstructor(context); + + if (hasArgsConstructor) { + return !hasParameterFields; + } + else { + return !hasParameterFields || hasCorrectParameterFields; + } + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + // grabbing the parent ensures the PARAMETERS are stored in the same store across multiple TestTemplates. + return context.getParent().flatMap(ParameterizedExtension::parameters).map( + o -> testTemplateContextsFromParameters(o, context)).orElse(Stream.empty()); + } + + private static boolean areParametersFormedCorrectly(List fields) { + List parameterValues = parameterIndexes(fields); + List duplicateIndexes = duplicatedIndexes(parameterValues); + boolean hasAllIndexes = indexRangeComplete(parameterValues); + + return hasAllIndexes && duplicateIndexes.isEmpty(); + } + + private static List parameterIndexes(List fields) { + // @formatter:off + return fields.stream() + .map(f -> f.getAnnotation(Parameterized.Parameter.class)) + .map(Parameterized.Parameter::value) + .collect(toList()); + // @formatter:on + } + + private static List duplicatedIndexes(List parameterValues) { + // @formatter:off + return parameterValues.stream().collect(groupingBy(identity())).entrySet().stream() + .filter(e -> e.getValue().size() > 1) + .map(Map.Entry::getKey) + .collect(toList()); + // @formatter:on + } + + private static Boolean indexRangeComplete(List parameterValues) { + // @formatter:off + return parameterValues.stream() + .max(Integer::compareTo) + .map(i -> parameterValues.containsAll(IntStream.range(0, i).boxed().collect(toList()))) + .orElse(false); + // @formatter:on + } + + private static Optional> parameters(ExtensionContext context) { + return context.getStore(PARAMETERS).getOrComputeIfAbsent("parameterMethod", + k -> new ParameterWrapper(callParameters(context)), ParameterWrapper.class).getValue(); + + } + + private static Optional> callParameters(ExtensionContext context) { + // @formatter:off + return findParametersMethod(context) + .map(m -> ReflectionUtils.invokeMethod(m, null)) + .map(ParameterizedExtension::convertParametersMethodReturnType); + // @formatter:on + } + + private static boolean hasParametersMethod(ExtensionContext context) { + return findParametersMethod(context).isPresent(); + } + + private static Optional findParametersMethod(ExtensionContext extensionContext) { + // @formatter:off + return extensionContext.getTestClass() + .flatMap(ParameterizedExtension::ensureSingleParametersMethod) + .filter(ReflectionUtils::isPublic); + // @formatter:on + } + + private static Optional ensureSingleParametersMethod(Class testClass) { + return ReflectionUtils.findMethods(testClass, + m -> m.isAnnotationPresent(Parameterized.Parameters.class)).stream().findFirst(); + } + + private static Stream testTemplateContextsFromParameters(Collection o, + ExtensionContext context) { + List fields = parametersFields(context); + boolean hasParameterFields = !fields.isEmpty(); + boolean hasCorrectParameterFields = areParametersFormedCorrectly(fields); + + if (!hasParameterFields) { + return o.stream().map(ParameterizedExtension::parameterResolver); + } + else if (hasCorrectParameterFields) { + return o.stream().map(ParameterizedExtension::contextFactory); + } + + return Stream.empty(); + } + + private static TestTemplateInvocationContext parameterResolver(Object[] objects) { + List parameterResolvers = singletonList(new ParameterResolver() { + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + final Executable declaringExecutable = parameterContext.getDeclaringExecutable(); + return declaringExecutable instanceof Constructor + && declaringExecutable.getParameterCount() == objects.length; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return objects[parameterContext.getIndex()]; + } + }); + + return templateWithExtensions(parameterResolvers); + } + + private static TestTemplateInvocationContext contextFactory(Object[] parameters) { + return templateWithExtensions(singletonList(new InjectionExtension(parameters))); + } + + private static class InjectionExtension implements TestInstancePostProcessor { + + private final Object[] parameters; + + public InjectionExtension(Object[] parameters) { + this.parameters = parameters; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { + List parameters = parametersFields(context); + + if (!parameters.isEmpty() && parameters.size() != this.parameters.length) { + throw unMatchedAmountOfParametersException(); + } + + for (Field param : parameters) { + Parameterized.Parameter annotation = param.getAnnotation(Parameterized.Parameter.class); + int paramIndex = annotation.value(); + param.set(testInstance, this.parameters[paramIndex]); + } + } + + } + + private static TestTemplateInvocationContext templateWithExtensions(List extensions) { + return new TestTemplateInvocationContext() { + @Override + public List getAdditionalExtensions() { + return extensions; + } + }; + } + + private static boolean hasArgsConstructor(ExtensionContext context) { + // @formatter:off + return context.getTestClass() + .map(ReflectionUtils::getDeclaredConstructor) + .filter(c -> c.getParameterCount() > 0) + .isPresent(); + // @formatter:on + } + + private static List parametersFields(ExtensionContext context) { + // @formatter:off + Stream fieldStream = context.getTestClass() + .map(Class::getDeclaredFields) + .map(Stream::of) + .orElse(Stream.empty()); + // @formatter:on + + return fieldStream.filter(f -> f.isAnnotationPresent(Parameterized.Parameter.class)).filter( + ReflectionUtils::isPublic).collect(toList()); + } + + private static ParameterResolutionException unMatchedAmountOfParametersException() { + return new ParameterResolutionException( + "The amount of parametersFields in the constructor doesn't match those in the provided parametersFields"); + } + + private static Collection convertParametersMethodReturnType(Object obj) { + return CollectionUtils.toStream(obj).map(o -> { + if (o instanceof Object[]) { + return (Object[]) o; + } + return new Object[] { o }; + }).collect(toList()); + } + + private static class ParameterWrapper { + private final Optional> value; + + public ParameterWrapper(Optional> value) { + this.value = value; + } + + public Optional> getValue() { + return value; + } + } +} diff --git a/barcode/src/test/java/org/xbib/graphics/barcode/SymbolTest.java b/barcode/src/test/java/org/xbib/graphics/barcode/SymbolTest.java new file mode 100755 index 0000000..ef199be --- /dev/null +++ b/barcode/src/test/java/org/xbib/graphics/barcode/SymbolTest.java @@ -0,0 +1,608 @@ +package org.xbib.graphics.barcode; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.LuminanceSource; +import com.google.zxing.Reader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.oned.CodaBarReader; +import com.google.zxing.oned.Code39Reader; +import com.google.zxing.oned.Code93Reader; +import com.google.zxing.oned.EAN13Reader; +import com.google.zxing.oned.EAN8Reader; +import com.google.zxing.oned.UPCAReader; +import com.google.zxing.oned.UPCEReader; +import com.google.zxing.pdf417.PDF417Reader; +import com.google.zxing.qrcode.QRCodeReader; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runners.Parameterized; +import org.reflections.Reflections; +import org.xbib.graphics.barcode.render.GraphicsRenderer; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Logger; +import javax.imageio.ImageIO; + +/** + * Scans the test resources for file-based bar code tests. + * + * Tests that verify successful behavior will contain the following sets of files: + * + *
+ *   /src/test/resources/uk/org/okapibarcode/backend/[symbol-name]/[test-name].properties (bar code initialization attributes)
+ *   /src/test/resources/uk/org/okapibarcode/backend/[symbol-name]/[test-name].codewords  (expected intermediate coding of the bar code)
+ *   /src/test/resources/uk/org/okapibarcode/backend/[symbol-name]/[test-name].png        (expected final rendering of the bar code)
+ * 
+ * + *

+ * Tests that verify error conditions will contain the following sets of files: + * + *

+ *   /src/test/resources/uk/org/okapibarcode/backend/[symbol-name]/[test-name].properties (bar code initialization attributes)
+ *   /src/test/resources/uk/org/okapibarcode/backend/[symbol-name]/[test-name].error      (expected error message)
+ * 
+ * + * If a properties file is found with no matching expectation files, we assume that it was recently added to the test suite and + * that we need to generate suitable expectation files for it. + * + * A single properties file can contain multiple test configurations (separated by an empty line), as long as the expected output + * is the same for all of those tests. + */ +@ExtendWith(ParameterizedExtension.class) +public class SymbolTest { + + /** The font used to render human-readable text when drawing the symbologies; allows for consistent results across operating systems. */ + private static Font DEJA_VU_SANS; + + /** The type of symbology being tested. */ + private final Class< ? extends Symbol> symbolType; + + /** The test configuration properties. */ + private final Map< String, String > properties; + + /** The file containing the expected intermediate coding of the bar code, if this test verifies successful behavior. */ + private final File codewordsFile; + + /** The file containing the expected final rendering of the bar code, if this test verifies successful behavior. */ + private final File pngFile; + + /** The file containing the expected error message, if this test verifies a failure. */ + private final File errorFile; + + /** + * Finds all test resources and returns the information that JUnit needs to dynamically create the corresponding test cases. + * + * @return the test data needed to dynamically create the test cases + * @throws IOException if there is an error reading a file + */ + @Parameterized.Parameters + public static List< Object[] > data() throws IOException { + String path = "/org/xbib/graphics/barcode/fonts/OkapiDejaVuSans.ttf"; + try { + InputStream is = SymbolTest.class.getResourceAsStream(path); + DEJA_VU_SANS = Font.createFont(Font.TRUETYPE_FONT, is); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + boolean registered = ge.registerFont(DEJA_VU_SANS); + assertTrue(registered); + } catch (IOException | FontFormatException e) { + throw new IOException(e.getMessage(), e); + } + String backend = "org.xbib.graphics.barcode"; + Reflections reflections = new Reflections(backend); + Set< Class< ? extends Symbol >> symbols = reflections.getSubTypesOf(Symbol.class); + List< Object[] > data = new ArrayList<>(); + for (Class< ? extends Symbol > symbol : symbols) { + String symbolName = symbol.getSimpleName().toLowerCase(); + String dir = "src/test/resources/" + backend.replace('.', '/') + "/" + symbolName; + for (File file : getPropertiesFiles(dir)) { + String fileBaseName = file.getName().replaceAll(".properties", ""); + File codewordsFile = new File(file.getParentFile(), fileBaseName + ".codewords"); + File pngFile = new File(file.getParentFile(), fileBaseName + ".png"); + File errorFile = new File(file.getParentFile(), fileBaseName + ".error"); + for (Map< String, String > properties : readProperties(file)) { + data.add(new Object[] { symbol, properties, codewordsFile, pngFile, errorFile }); + } + } + } + return data; + } + /** + * Creates a new test. + * + * @param symbolType the type of symbol being tested + * @param properties the test configuration properties + * @param codewordsFile the file containing the expected intermediate coding of the bar code, if this test verifies successful behavior + * @param pngFile the file containing the expected final rendering of the bar code, if this test verifies successful behavior + * @param errorFile the file containing the expected error message, if this test verifies a failure + */ + public SymbolTest(Class< ? extends Symbol > symbolType, + Map< String, String > properties, + File codewordsFile, + File pngFile, + File errorFile) { + this.symbolType = symbolType; + this.properties = properties; + this.codewordsFile = codewordsFile; + this.pngFile = pngFile; + this.errorFile = errorFile; + } + + /** + * Runs the test. If there are no expectation files yet, we generate them instead of checking against them. + * + * @throws Exception if any error occurs during the test + */ + @TestTemplate + public void test() throws Exception { + Symbol symbol = symbolType.getDeclaredConstructor().newInstance(); + symbol.setFontName(DEJA_VU_SANS.getFontName()); + try { + setProperties(symbol, properties); + } catch (InvocationTargetException e) { + symbol.errorMsg.append(e.getCause().getMessage()); + } + if (codewordsFile.exists()) { + verifySuccess(symbol); + } else if (errorFile.exists()) { + verifyError(symbol); + } + if (!pngFile.exists()) { + generateExpectationFiles(symbol); + } + } + + /** + * Verifies that the specified symbol was encoded and rendered in a way that matches expectations. + * + * @param symbol the symbol to check + * @throws IOException if there is any I/O error + * @throws ReaderException if ZXing has an issue decoding the barcode image + */ + private void verifySuccess(Symbol symbol) throws IOException, ReaderException { + if (symbol.errorMsg.length() > 0) { + fail("got error message: " + symbol.errorMsg); + } + List< String > expectedList = Files.readAllLines(codewordsFile.toPath(), StandardCharsets.UTF_8); + try { + // try to verify codewords + int[] actualCodewords = symbol.getCodewords(); + assertEquals(expectedList.size(), actualCodewords.length); + for (int i = 0; i < actualCodewords.length; i++) { + int expected = getInt(expectedList.get(i)); + int actual = actualCodewords[i]; + assertEquals(expected, actual, "at codeword index " + i); + } + } catch (UnsupportedOperationException e) { + // codewords aren't supported, try to verify patterns + String[] actualPatterns = symbol.pattern; + if (actualPatterns != null) { + assertEquals(expectedList.size(), actualPatterns.length); + for (int i = 0; i < actualPatterns.length; i++) { + String expected = expectedList.get(i); + String actual = actualPatterns[i]; + assertEquals(expected, actual, "at pattern index " + i); + } + } + } + // make sure the barcode images match + if (pngFile.exists()) { + BufferedImage expected = ImageIO.read(pngFile); + BufferedImage actual = draw(symbol, 10.0d); + if (expected != null && actual != null) { + assertEqualImage(pngFile.getName(), expected, actual); + } + // if possible, ensure an independent third party (ZXing) can read the generated barcode and agrees on what it represents + Reader zxingReader = findReader(symbol); + if (zxingReader != null && expected != null) { + LuminanceSource source = new BufferedImageLuminanceSource(expected); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + Map hints = Collections.singletonMap(DecodeHintType.PURE_BARCODE, Boolean.TRUE); + Result result = zxingReader.decode(bitmap, hints); + String zxingData = removeChecksum(result.getText(), symbol); + String ourData = removeStartStopChars(symbol.getContent(), symbol); + assertEquals(ourData, zxingData, "checking against ZXing results"); + } + } + } + + /** + * Returns a ZXing reader that can read the specified symbol. + * + * @param symbol the symbol to be read + * @return a ZXing reader that can read the specified symbol + */ + private static Reader findReader(Symbol symbol) { + if (symbol instanceof Code93) { + return new Code93Reader(); + } else if (symbol instanceof Code3Of9) { + return new Code39Reader(); + } else if (symbol instanceof Codabar) { + return new CodaBarReader(); + } else if (symbol instanceof QrCode) { + return new QRCodeReader(); + } else if (symbol instanceof Ean) { + Ean ean = (Ean) symbol; + if (ean.getMode() == Ean.Mode.EAN8) { + return new EAN8Reader(); + } else { + return new EAN13Reader(); + } + } else if (symbol instanceof Pdf417) { + Pdf417 pdf417 = (Pdf417) symbol; + if (pdf417.getMode() != Pdf417.Mode.MICRO) { + return new PDF417Reader(); + } + } else if (symbol instanceof Upc) { + Upc upc = (Upc) symbol; + if (upc.getMode() == Upc.Mode.UPCA) { + return new UPCAReader(); + } else { + return new UPCEReader(); + } + } + // no corresponding ZXing reader exists, or it behaves badly so we don't use it for testing + return null; + } + + /** + * Removes the checksum from the specified barcode content, according to the type of symbol that encoded the content. + * + * @param s the barcode content + * @param symbol the symbol which encoded the content + * @return the barcode content, without the checksum + */ + private static String removeChecksum(String s, Symbol symbol) { + if (symbol instanceof Ean || symbol instanceof Upc) { + return s.substring(0, s.length() - 1); + } else { + return s; + } + } + + /** + * Removes the start/stop characters from the specified barcode content, according to the type of symbol that encoded the + * content. + * + * @param s the barcode content + * @param symbol the symbol which encoded the content + * @return the barcode content, without the start/stop characters + */ + private static String removeStartStopChars(String s, Symbol symbol) { + if (symbol instanceof Codabar) { + return s.substring(1, s.length() - 1); + } else { + return s; + } + } + + /** + * Verifies that the specified symbol encountered the expected error during encoding. + * + * @param symbol the symbol to check + * @throws IOException if there is any I/O error + */ + private void verifyError(Symbol symbol) throws IOException { + String expectedError = Files.readAllLines(errorFile.toPath(), StandardCharsets.UTF_8).get(0); + assertTrue(symbol.errorMsg.toString().startsWith(expectedError)); + } + + /** + * Generates the expectation files for the specified symbol. + * + * @param symbol the symbol to generate expectation files for + * @throws IOException if there is any I/O error + */ + private void generateExpectationFiles(Symbol symbol) throws IOException { + if (symbol.errorMsg != null && symbol.errorMsg.length() > 0) { + generateErrorExpectationFile(symbol); + } else { + generateCodewordsExpectationFile(symbol); + generatePngExpectationFile(symbol); + } + } + + /** + * Generates the error expectation file for the specified symbol. + * + * @param symbol the symbol to generate the error expectation file for + * @throws IOException if there is any I/O error + */ + private void generateErrorExpectationFile(Symbol symbol) throws IOException { + if (!errorFile.exists()) { + PrintWriter writer = new PrintWriter(errorFile); + writer.println(symbol.errorMsg); + writer.close(); + } + } + + /** + * Generates the codewords expectation file for the specified symbol. + * + * @param symbol the symbol to generate codewords for + * @throws IOException if there is any I/O error + */ + private void generateCodewordsExpectationFile(Symbol symbol) throws IOException { + if (!codewordsFile.exists()) { + PrintWriter writer = new PrintWriter(codewordsFile); + try { + int[] codewords = symbol.getCodewords(); + for (int codeword : codewords) { + writer.println(codeword); + } + } catch (UnsupportedOperationException e) { + for (String pattern : symbol.pattern) { + writer.println(pattern); + } + } + writer.close(); + } + } + + /** + * Generates the image expectation file for the specified symbol. + * + * @param symbol the symbol to draw + * @throws IOException if there is any I/O error + */ + private void generatePngExpectationFile(Symbol symbol) throws IOException { + BufferedImage img = draw(symbol, 10.0d); + if (img != null) { + ImageIO.write(img, "png", pngFile); + } + } + + /** + * Returns the integer contained in the specified string. If the string contains a tab character, it and everything after it + * is ignored. + * + * @param s the string to extract the integer from + * @return the integer contained in the specified string + */ + private static int getInt(String s) { + int i = s.indexOf('\t'); + if (i != -1) { + s = s.substring(0, i); + } + return Integer.parseInt(s); + } + + /** + * Draws the specified symbol and returns the resultant image. + * + * @param symbol the symbol to draw + * @return the resultant image + */ + private static BufferedImage draw(Symbol symbol, double scalingFactor) { + int width = (int) (symbol.getWidth() * scalingFactor); + int height = (int) (symbol.getHeight() * scalingFactor); + if (width > 0 && height > 0) { + BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + Graphics2D g2d = img.createGraphics(); + g2d.setPaint(Color.WHITE); + g2d.fillRect(0, 0, width, height); + GraphicsRenderer renderer = new GraphicsRenderer(g2d, scalingFactor, Color.WHITE, Color.BLACK, true, true); + renderer.render(symbol); + g2d.dispose(); + return img; + } + return null; + } + + /** + * Initializes the specified symbol using the specified properties, where keys are attribute names and values are attribute + * values. + * + * @param symbol the symbol to initialize + * @param properties the attribute names and values to set + * @throws ReflectiveOperationException if there is any reflection error + */ + private static void setProperties(Symbol symbol, Map< String, String > properties) throws ReflectiveOperationException { + for (Map.Entry< String, String > entry : properties.entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + String setterName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); + Method setter = getMethod(symbol.getClass(), setterName); + invoke(symbol, setter, value); + } + } + + /** + * Returns the method with the specified name in the specified class, or throws an exception if the specified method cannot be + * found. + * + * @param clazz the class to search in + * @param name the name of the method to search for + * @return the method with the specified name in the specified class + */ + private static Method getMethod(Class< ? > clazz, String name) { + for (Method method : clazz.getMethods()) { + if (method.getName().equals(name)) { + return method; + } + } + throw new IllegalArgumentException("Unable to find method: " + name); + } + + /** + * Invokes the specified method on the specified object with the specified parameter. + * + * @param object the object to invoke the method on + * @param setter the method to invoke + * @param parameter the parameter to pass to the method + * @throws ReflectiveOperationException if there is any reflection error + * @throws IllegalArgumentException if the specified parameter is not valid + */ + @SuppressWarnings("unchecked") + private static < E extends Enum< E >> void invoke(Object object, Method setter, Object parameter) + throws ReflectiveOperationException, IllegalArgumentException { + Class< ? > paramType = setter.getParameterTypes()[0]; + if (String.class.equals(paramType)) { + setter.invoke(object, parameter.toString()); + } else if (boolean.class.equals(paramType)) { + setter.invoke(object, Boolean.valueOf(parameter.toString())); + } else if (int.class.equals(paramType)) { + setter.invoke(object, Integer.parseInt(parameter.toString())); + } else if (double.class.equals(paramType)) { + setter.invoke(object, Double.parseDouble(parameter.toString())); + } else if (Character.class.equals(paramType)) { + setter.invoke(object, parameter.toString().charAt(0)); + } else if (paramType.isEnum()) { + Class< E > e = (Class< E >) paramType; + setter.invoke(object, Enum.valueOf(e, parameter.toString())); + } else { + throw new IllegalArgumentException("Unknown setter type: " + paramType); + } + } + + /** + * Returns all .properties files in the specified directory, or an empty array if none are found. + * + * @param dir the directory to search in + * @return all .properties files in the specified directory, or an empty array if none are found + */ + private static File[] getPropertiesFiles(String dir) { + File[] files = new File(dir).listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".properties"); + } + }); + return Objects.requireNonNullElseGet(files, () -> new File[0]); + } + + /** + * Verifies that the specified images match. + * + * @param expected the expected image to check against + * @param actual the actual image + */ + private static void assertEqualImage(String name, BufferedImage expected, BufferedImage actual) { + int w = expected.getWidth(); + int h = expected.getHeight(); + assertEquals(w, actual.getWidth(), "width"); + assertEquals(h, actual.getHeight(), "height"); + int[] expectedPixels = new int[w * h]; + expected.getRGB(0, 0, w, h, expectedPixels, 0, w); + int[] actualPixels = new int[w * h]; + actual.getRGB(0, 0, w, h, actualPixels, 0, w); + for (int i = 0; i < expectedPixels.length; i++) { + int expectedPixel = expectedPixels[i]; + int actualPixel = actualPixels[i]; + if (expectedPixel != actualPixel) { + int x = i % w; + int y = i / w; + fail(name + ": pixel mismatch at " + x + ", " + y + " " + + Integer.toHexString(expectedPixel) + " " + Integer.toHexString(actualPixel)); + } + } + } + + /** + * Extracts test configuration properties from the specified properties file. A single properties file can contain + * configuration properties for multiple tests. + * + * @param propertiesFile the properties file to read + * @return the test configuration properties in the specified file + * @throws IOException if there is an error reading the properties file + */ + private static List> readProperties(File propertiesFile) throws IOException { + String content; + try { + byte[] bytes = Files.readAllBytes(propertiesFile.toPath()); + content = replacePlaceholders(decode(bytes, StandardCharsets.UTF_8)); + } catch (CharacterCodingException e) { + throw new IOException("Invalid UTF-8 content in file " + propertiesFile.getAbsolutePath(), e); + } + + String eol = System.lineSeparator(); + String[] lines = content.split(eol); + + List< Map< String, String > > allProperties = new ArrayList<>(); + Map< String, String > properties = new LinkedHashMap<>(); + + for (String line : lines) { + if (line.isEmpty()) { + // an empty line signals the start of a new test configuration within this single file + if (!properties.isEmpty()) { + allProperties.add(properties); + properties = new LinkedHashMap<>(); + } + } else if (!line.startsWith("#")) { + int index = line.indexOf('='); + if (index != -1) { + String name = line.substring(0, index); + String value = line.substring(index + 1); + properties.put(name, value); + } else { + throw new IOException(propertiesFile.getAbsolutePath() + ": found line without '=' character; unintentional newline?"); + } + } + } + if (!properties.isEmpty()) { + allProperties.add(properties); + } + return allProperties; + } + + /** + * Equivalent to {@link String#String(byte[], Charset)}, + * except that encoding errors result in exceptions instead of + * silent character replacement. + * + * @param bytes the bytes to decode + * @param charset the character set use to decode the bytes + * @return the specified bytes, as a string + * @throws CharacterCodingException if there is an error decoding the specified bytes + */ + private static String decode(byte[] bytes, Charset charset) throws CharacterCodingException { + CharsetDecoder decoder = charset.newDecoder(); + decoder.onMalformedInput(CodingErrorAction.REPORT); + decoder.onUnmappableCharacter(CodingErrorAction.REPORT); + CharBuffer chars = decoder.decode(ByteBuffer.wrap(bytes)); + return chars.toString(); + } + + /** + * Replaces any special placeholders supported in test properties files with their raw values. + * + * @param s the string to check for placeholders + * @return the specified string, with placeholders replaced + */ + private static String replacePlaceholders(String s) { + return s.replaceAll("\\\\r", "\r") // "\r" -> CR + .replaceAll("\\\\n", "\n"); // "\n" -> LF + } +} diff --git a/barcode/src/test/java/org/xbib/graphics/barcode/output/Code39Test.java b/barcode/src/test/java/org/xbib/graphics/barcode/output/Code39Test.java new file mode 100644 index 0000000..c5e38bb --- /dev/null +++ b/barcode/src/test/java/org/xbib/graphics/barcode/output/Code39Test.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.barcode.output; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.barcode.Code3Of9; +import org.xbib.graphics.barcode.HumanReadableLocation; +import org.xbib.graphics.barcode.render.GraphicsRenderer; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import javax.imageio.ImageIO; + +public class Code39Test { + + @Test + public void createBarcode1() throws IOException { + Code3Of9 code3Of9 = new Code3Of9(); + code3Of9.setContent("20180123456"); + code3Of9.setHumanReadableLocation(HumanReadableLocation.BOTTOM); + // pixels = (mm * dpi) / 25.4 + double scalingFactor = (1.0d * 72.0d) / 25.4; + int width = (int) (code3Of9.getWidth() * scalingFactor); + int height = (int) (code3Of9.getHeight() * scalingFactor); + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + GraphicsRenderer renderer = createRenderer(bufferedImage, scalingFactor); + renderer.render(code3Of9); + renderer.close(); + OutputStream outputStream = Files.newOutputStream(Paths.get("build/barcode1.png")); + ImageIO.write(bufferedImage, "png", outputStream); + outputStream.close(); + } + + @Test + public void createBarcode2() throws IOException { + int width = 512; + int height = 150; + Code3Of9 code3Of9 = new Code3Of9(); + //code3Of9.setContent("20180123456"); + code3Of9.setContent("11111111111"); + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + GraphicsRenderer renderer = createRenderer(bufferedImage, 3.0d); + renderer.render(code3Of9); + renderer.close(); + OutputStream outputStream = Files.newOutputStream(Paths.get("build/barcode2.png")); + ImageIO.write(bufferedImage, "png", outputStream); + outputStream.close(); + } + + private GraphicsRenderer createRenderer(BufferedImage bufferedImage, double scalingFactor) { + Graphics2D g2d = bufferedImage.createGraphics(); + g2d.setPaint(Color.WHITE); + g2d.setBackground(Color.BLACK); + g2d.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); + return new GraphicsRenderer(g2d, scalingFactor, Color.WHITE, Color.BLACK, false, false); + } +} diff --git a/barcode/src/test/java/org/xbib/graphics/barcode/output/EPSRendererTest.java b/barcode/src/test/java/org/xbib/graphics/barcode/output/EPSRendererTest.java new file mode 100755 index 0000000..55187e5 --- /dev/null +++ b/barcode/src/test/java/org/xbib/graphics/barcode/output/EPSRendererTest.java @@ -0,0 +1,121 @@ +package org.xbib.graphics.barcode.output; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.barcode.Code93; +import org.xbib.graphics.barcode.render.GraphicsRenderer; +import org.xbib.graphics.barcode.MaxiCode; +import org.xbib.graphics.barcode.Symbol; +import org.xbib.graphics.io.vector.eps.EPSGraphics2D; +import java.awt.Color; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Locale; + +public class EPSRendererTest { + + private Locale originalDefaultLocale; + + @BeforeEach + public void before() { + // ensure use of correct decimal separator (period), regardless of default locale + originalDefaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.GERMANY); + } + + @AfterEach + public void after() { + Locale.setDefault(originalDefaultLocale); + } + + @Test + public void testCode93Basic() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 5, "code93-basic.eps"); + } + + @Test + public void testCode93Margin() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 20, "code93-margin-size-20.eps"); + } + + @Test + public void testCode93Magnification() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 2, Color.WHITE, Color.BLACK, 5, "code93-magnification-2.eps"); + } + + @Test + public void testCode93Colors() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.GREEN, Color.RED, 5, "code93-colors.eps"); + } + + @Test + public void testCode93CustomFont() throws IOException { + Code93 code93 = new Code93(); + code93.setFontName("Arial"); + code93.setFontSize(26); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 5, "code93-custom-font.eps"); + } + + @Test + public void testMaxiCodeBasic() throws IOException { + MaxiCode maxicode = new MaxiCode(); + maxicode.setMode(4); + maxicode.setContent("123456789"); + test(maxicode, 5, Color.WHITE, Color.BLACK, 5, "maxicode-basic.eps"); + } + + private void test(Symbol symbol, + double magnification, + Color paper, + Color ink, + int margin, + String expectationFile) throws IOException { + symbol.setQuietZoneHorizontal(margin); + symbol.setQuietZoneVertical(margin); + int width = (int) (symbol.getWidth() * magnification); + int height = (int) (symbol.getHeight() * magnification); + EPSGraphics2D epsGraphics2D = new EPSGraphics2D(0, 0, width, height); + GraphicsRenderer graphicsRenderer = new GraphicsRenderer(epsGraphics2D, magnification, paper, ink, false, false); + graphicsRenderer.render(symbol); + graphicsRenderer.close(); + byte[] actualBytes = epsGraphics2D.getBytes(); + String actual = new String(actualBytes, StandardCharsets.UTF_8); + try (BufferedWriter bufferedWriter = Files.newBufferedWriter(Paths.get("build/" + expectationFile))) { + bufferedWriter.write(actual); + } + BufferedReader actualReader = new BufferedReader(new StringReader(actual)); + InputStream is = getClass().getResourceAsStream(expectationFile); + if (is != null) { + byte[] expectedBytes = new byte[is.available()]; + is.read(expectedBytes); + String expected = new String(expectedBytes, StandardCharsets.UTF_8); + BufferedReader expectedReader = new BufferedReader(new StringReader(expected)); + int line = 1; + String actualLine = actualReader.readLine(); + String expectedLine = expectedReader.readLine(); + while (actualLine != null && expectedLine != null) { + assertEquals(expectedLine, actualLine, "Line " + line); + actualLine = actualReader.readLine(); + expectedLine = expectedReader.readLine(); + line++; + } + } + } +} diff --git a/barcode/src/test/java/org/xbib/graphics/barcode/output/PDFRendererTest.java b/barcode/src/test/java/org/xbib/graphics/barcode/output/PDFRendererTest.java new file mode 100755 index 0000000..f0f3833 --- /dev/null +++ b/barcode/src/test/java/org/xbib/graphics/barcode/output/PDFRendererTest.java @@ -0,0 +1,121 @@ +package org.xbib.graphics.barcode.output; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.barcode.Code93; +import org.xbib.graphics.barcode.MaxiCode; +import org.xbib.graphics.barcode.Symbol; +import org.xbib.graphics.barcode.render.GraphicsRenderer; +import org.xbib.graphics.io.vector.pdf.PDFGraphics2D; +import java.awt.Color; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Locale; + +public class PDFRendererTest { + + private Locale originalDefaultLocale; + + @BeforeEach + public void before() { + // ensure use of correct decimal separator (period), regardless of default locale + originalDefaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.GERMANY); + } + + @AfterEach + public void after() { + Locale.setDefault(originalDefaultLocale); + } + + @Test + public void testCode93Basic() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 5, "code93-basic.pdf"); + } + + @Test + public void testCode93Margin() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 20, "code93-margin-size-20.pdf"); + } + + @Test + public void testCode93Magnification() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 2, Color.WHITE, Color.BLACK, 5, "code93-magnification-2.pdf"); + } + + @Test + public void testCode93Colors() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.GREEN, Color.RED, 5, "code93-colors.pdf"); + } + + @Test + public void testCode93CustomFont() throws IOException { + Code93 code93 = new Code93(); + code93.setFontName("Arial"); + code93.setFontSize(26); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 5, "code93-custom-font.pdf"); + } + + @Test + public void testMaxiCodeBasic() throws IOException { + MaxiCode maxicode = new MaxiCode(); + maxicode.setMode(4); + maxicode.setContent("123456789"); + test(maxicode, 5, Color.WHITE, Color.BLACK, 5, "maxicode-basic.pdf"); + } + + private void test(Symbol symbol, + double magnification, + Color paper, + Color ink, + int margin, + String expectationFile) throws IOException { + symbol.setQuietZoneHorizontal(margin); + symbol.setQuietZoneVertical(margin); + int width = (int) (symbol.getWidth() * magnification); + int height = (int) (symbol.getHeight() * magnification); + PDFGraphics2D pdfGraphics2D = new PDFGraphics2D(0, 0, width, height); + GraphicsRenderer graphicsRenderer = new GraphicsRenderer(pdfGraphics2D, magnification, paper, ink, false, false); + graphicsRenderer.render(symbol); + graphicsRenderer.close(); + byte[] actualBytes = pdfGraphics2D.getBytes(); + String actual = new String(actualBytes, StandardCharsets.UTF_8); + try (BufferedWriter bufferedWriter = Files.newBufferedWriter(Paths.get("build/" + expectationFile))) { + bufferedWriter.write(actual); + } + BufferedReader actualReader = new BufferedReader(new StringReader(actual)); + InputStream is = getClass().getResourceAsStream(expectationFile); + if (is != null) { + byte[] expectedBytes = new byte[is.available()]; + is.read(expectedBytes); + String expected = new String(expectedBytes, StandardCharsets.UTF_8); + BufferedReader expectedReader = new BufferedReader(new StringReader(expected)); + int line = 1; + String actualLine = actualReader.readLine(); + String expectedLine = expectedReader.readLine(); + while (actualLine != null && expectedLine != null) { + assertEquals(expectedLine, actualLine, "Line " + line); + actualLine = actualReader.readLine(); + expectedLine = expectedReader.readLine(); + line++; + } + } + } +} diff --git a/barcode/src/test/java/org/xbib/graphics/barcode/output/SvgRendererTest.java b/barcode/src/test/java/org/xbib/graphics/barcode/output/SvgRendererTest.java new file mode 100755 index 0000000..d97ee80 --- /dev/null +++ b/barcode/src/test/java/org/xbib/graphics/barcode/output/SvgRendererTest.java @@ -0,0 +1,121 @@ +package org.xbib.graphics.barcode.output; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.barcode.Code93; +import org.xbib.graphics.barcode.MaxiCode; +import org.xbib.graphics.barcode.Symbol; +import org.xbib.graphics.barcode.render.GraphicsRenderer; +import org.xbib.graphics.io.vector.svg.SVGGraphics2D; +import java.awt.Color; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Locale; + +public class SvgRendererTest { + + private Locale originalDefaultLocale; + + @BeforeEach + public void before() { + // ensure use of correct decimal separator (period), regardless of default locale + originalDefaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.GERMANY); + } + + @AfterEach + public void after() { + Locale.setDefault(originalDefaultLocale); + } + + @Test + public void testCode93Basic() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 5, "code93-basic.svg"); + } + + @Test + public void testCode93Margin() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 20, "code93-margin-size-20.svg"); + } + + @Test + public void testCode93Magnification() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 2, Color.WHITE, Color.BLACK, 5, "code93-magnification-2.svg"); + } + + @Test + public void testCode93Colors() throws IOException { + Code93 code93 = new Code93(); + code93.setContent("123456789"); + test(code93, 1, Color.GREEN, Color.RED, 5, "code93-colors.svg"); + } + + @Test + public void testCode93CustomFont() throws IOException { + Code93 code93 = new Code93(); + code93.setFontName("Arial"); + code93.setFontSize(26); + code93.setContent("123456789"); + test(code93, 1, Color.WHITE, Color.BLACK, 5, "code93-custom-font.svg"); + } + + @Test + public void testMaxiCodeBasic() throws IOException { + MaxiCode maxicode = new MaxiCode(); + maxicode.setMode(4); + maxicode.setContent("123456789"); + test(maxicode, 1.0, Color.WHITE, Color.BLACK, 5, "maxicode-basic.svg"); + } + + private void test(Symbol symbol, + double magnification, + Color paper, + Color ink, + int margin, + String expectationFile) throws IOException { + symbol.setQuietZoneHorizontal(margin); + symbol.setQuietZoneVertical(margin); + int width = (int) (symbol.getWidth() * magnification); + int height = (int) (symbol.getHeight() * magnification); + SVGGraphics2D svgGraphics2D = new SVGGraphics2D(0, 0, width, height); + GraphicsRenderer graphicsRenderer = new GraphicsRenderer(svgGraphics2D, magnification, paper, ink, false, false); + graphicsRenderer.render(symbol); + graphicsRenderer.close(); + byte[] actualBytes = svgGraphics2D.getBytes(); + String actual = new String(actualBytes, StandardCharsets.UTF_8); + try (BufferedWriter bufferedWriter = Files.newBufferedWriter(Paths.get("build/" + expectationFile))) { + bufferedWriter.write(actual); + } + BufferedReader actualReader = new BufferedReader(new StringReader(actual)); + InputStream is = getClass().getResourceAsStream(expectationFile); + if (is != null) { + byte[] expectedBytes = new byte[is.available()]; + is.read(expectedBytes); + String expected = new String(expectedBytes, StandardCharsets.UTF_8); + BufferedReader expectedReader = new BufferedReader(new StringReader(expected)); + int line = 1; + String actualLine = actualReader.readLine(); + String expectedLine = expectedReader.readLine(); + while (actualLine != null && expectedLine != null) { + assertEquals(expectedLine, actualLine, "Line " + line); + actualLine = actualReader.readLine(); + expectedLine = expectedReader.readLine(); + line++; + } + } + } +} diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.codewords new file mode 100755 index 0000000..7a53df3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.codewords @@ -0,0 +1,18 @@ +12121121 +11112211 +11122111 +11121121 +21112121 +22111111 +21212111 +11211211 +11221111 +21111211 +21211121 +12111121 +11212121 +12112111 +12211111 +21121111 +11111221 +11121221 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.png new file mode 100644 index 0000000..ec4e4b1 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.properties new file mode 100755 index 0000000..e66bd7b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/basic.properties @@ -0,0 +1 @@ +content=B1-2:3.4$5/6+7890C diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.codewords new file mode 100755 index 0000000..7a53df3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.codewords @@ -0,0 +1,18 @@ +12121121 +11112211 +11122111 +11121121 +21112121 +22111111 +21212111 +11211211 +11221111 +21111211 +21211121 +12111121 +11212121 +12112111 +12211111 +21121111 +11111221 +11121221 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.png b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.png new file mode 100644 index 0000000..27d37a2 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.properties new file mode 100755 index 0000000..e930b0c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/module-width-ratio-2.5.properties @@ -0,0 +1,2 @@ +moduleWidthRatio=2.5 +content=B1-2:3.4$5/6+7890C diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-start-char.error b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-start-char.error new file mode 100755 index 0000000..61fd553 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-start-char.error @@ -0,0 +1 @@ +Invalid characters in input diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-start-char.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-start-char.properties new file mode 100755 index 0000000..21f4bb1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-start-char.properties @@ -0,0 +1 @@ +content=1234A diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-stop-char.error b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-stop-char.error new file mode 100755 index 0000000..61fd553 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-stop-char.error @@ -0,0 +1 @@ +Invalid characters in input diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-stop-char.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-stop-char.properties new file mode 100755 index 0000000..d51a764 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/codabar/no-stop-char.properties @@ -0,0 +1 @@ +content=A1234 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.codewords new file mode 100755 index 0000000..95d25b3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.codewords @@ -0,0 +1,14 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.png new file mode 100644 index 0000000..e33390c Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.properties new file mode 100755 index 0000000..51b6523 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic-one-check-digit.properties @@ -0,0 +1,2 @@ +checkDigitCount=1 +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.codewords new file mode 100755 index 0000000..6785c36 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.codewords @@ -0,0 +1,15 @@ +112211 start +111121 0 +211121 1 +121121 2 +221111 3 +112121 4 +112111 - +212111 5 +122111 6 +111221 7 +211211 8 +211111 9 +112121 4 (check digit C) +221111 3 (check digit K) +112211 stop diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.png new file mode 100644 index 0000000..6c89f39 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.properties new file mode 100755 index 0000000..7ac46f2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/basic.properties @@ -0,0 +1,4 @@ +content=01234-56789 + +checkDigitCount=2 +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.codewords new file mode 100755 index 0000000..5fbc040 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.codewords @@ -0,0 +1,15 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +221111 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.png new file mode 100644 index 0000000..e7bad66 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.properties new file mode 100755 index 0000000..7d0abb7 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-none.properties @@ -0,0 +1,2 @@ +humanReadableLocation=NONE +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.codewords new file mode 100755 index 0000000..5fbc040 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.codewords @@ -0,0 +1,15 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +221111 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.png new file mode 100644 index 0000000..737b500 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.properties new file mode 100755 index 0000000..1274d28 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/human-readable-location-top.properties @@ -0,0 +1,2 @@ +humanReadableLocation=TOP +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/invalid-content.error b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/invalid-content.error new file mode 100755 index 0000000..61fd553 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/invalid-content.error @@ -0,0 +1 @@ +Invalid characters in input diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/invalid-content.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/invalid-content.properties new file mode 100755 index 0000000..6e8967c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/invalid-content.properties @@ -0,0 +1 @@ +content=01234+56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.codewords new file mode 100755 index 0000000..5fbc040 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.codewords @@ -0,0 +1,15 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +221111 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.png new file mode 100644 index 0000000..b24b7e6 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.properties new file mode 100755 index 0000000..b436aa1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-2.5.properties @@ -0,0 +1,2 @@ +moduleWidthRatio=2.5 +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.codewords new file mode 100755 index 0000000..5fbc040 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.codewords @@ -0,0 +1,15 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +221111 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.png new file mode 100644 index 0000000..1b07e5c Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.properties new file mode 100755 index 0000000..f7e1110 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/module-width-ratio-3.0.properties @@ -0,0 +1,2 @@ +moduleWidthRatio=3 +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.codewords new file mode 100755 index 0000000..5fbc040 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.codewords @@ -0,0 +1,15 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +221111 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.png new file mode 100644 index 0000000..6c89f39 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.properties new file mode 100755 index 0000000..4b140f4 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-delimiter.properties @@ -0,0 +1,2 @@ +startDelimiter=* +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.codewords new file mode 100755 index 0000000..5fbc040 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.codewords @@ -0,0 +1,15 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +221111 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.png new file mode 100644 index 0000000..298b55f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.properties new file mode 100755 index 0000000..51c3508 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/start-stop-delimiter.properties @@ -0,0 +1,3 @@ +startDelimiter=$ +stopDelimiter=% +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.codewords new file mode 100755 index 0000000..5fbc040 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.codewords @@ -0,0 +1,15 @@ +112211 +111121 +211121 +121121 +221111 +112121 +112111 +212111 +122111 +111221 +211211 +211111 +112121 +221111 +112211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.png new file mode 100644 index 0000000..62016a8 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.properties new file mode 100755 index 0000000..004d4bc --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/stop-delimiter.properties @@ -0,0 +1,2 @@ +stopDelimiter=+ +content=01234-56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/three-check-digits.error b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/three-check-digits.error new file mode 100755 index 0000000..6bb2fec --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/three-check-digits.error @@ -0,0 +1 @@ +Check digit count must be 1 or 2. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/three-check-digits.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/three-check-digits.properties new file mode 100755 index 0000000..562d383 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/three-check-digits.properties @@ -0,0 +1 @@ +checkDigitCount=3 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/zero-check-digits.error b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/zero-check-digits.error new file mode 100755 index 0000000..6bb2fec --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/zero-check-digits.error @@ -0,0 +1 @@ +Check digit count must be 1 or 2. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code11/zero-check-digits.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/zero-check-digits.properties new file mode 100755 index 0000000..9e66f91 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code11/zero-check-digits.properties @@ -0,0 +1 @@ +checkDigitCount=0 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.codewords new file mode 100755 index 0000000..61126aa --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.codewords @@ -0,0 +1 @@ +31111131113113113133111111313131311113311111133131131113131111331131113113113133111131111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.png new file mode 100644 index 0000000..f376cd3 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.properties new file mode 100755 index 0000000..2911d95 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/basic.properties @@ -0,0 +1 @@ +content=1234567890123 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.codewords new file mode 100755 index 0000000..61126aa --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.codewords @@ -0,0 +1 @@ +31111131113113113133111111313131311113311111133131131113131111331131113113113133111131111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.png new file mode 100644 index 0000000..0e28092 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.properties new file mode 100755 index 0000000..94de91d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-none.properties @@ -0,0 +1,2 @@ +humanReadableLocation=NONE +content=1234567890123 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.codewords new file mode 100755 index 0000000..61126aa --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.codewords @@ -0,0 +1 @@ +31111131113113113133111111313131311113311111133131131113131111331131113113113133111131111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.png new file mode 100644 index 0000000..45e4c47 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.properties new file mode 100755 index 0000000..282ee68 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/human-readable-location-top.properties @@ -0,0 +1,2 @@ +humanReadableLocation=TOP +content=1234567890123 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.codewords new file mode 100755 index 0000000..61126aa --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.codewords @@ -0,0 +1 @@ +31111131113113113133111111313131311113311111133131131113131111331131113113113133111131111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.png new file mode 100644 index 0000000..d7e6260 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.properties new file mode 100755 index 0000000..687cd75 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code2of5/module-width-ratio-2.5.properties @@ -0,0 +1,2 @@ +moduleWidthRatio=2.5 +content=1234567890123 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.codewords new file mode 100755 index 0000000..8bc811b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.codewords @@ -0,0 +1,12 @@ +1211212111 +2112111121 +1122111121 +2122111111 +1112211121 +2112211111 +1122211111 +1112112121 +2112112111 +1122112111 +1112212111 +121121211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.png new file mode 100644 index 0000000..1bcc949 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.properties new file mode 100755 index 0000000..4507f64 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/basic.properties @@ -0,0 +1 @@ +content=1234567890 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.codewords new file mode 100755 index 0000000..8bc811b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.codewords @@ -0,0 +1,12 @@ +1211212111 +2112111121 +1122111121 +2122111111 +1112211121 +2112211111 +1122211111 +1112112121 +2112112111 +1122112111 +1112212111 +121121211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.png new file mode 100644 index 0000000..67a6c03 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.properties new file mode 100755 index 0000000..6d89967 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code3of9/module-width-ratio-3.properties @@ -0,0 +1,2 @@ +moduleWidthRatio=3 +content=1234567890 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.codewords new file mode 100755 index 0000000..9532651 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.codewords @@ -0,0 +1,15 @@ +111141 +131112 +111213 +111312 +111411 +121113 +121212 +121311 +111114 +131211 +141111 +121122 +122211 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.png new file mode 100644 index 0000000..81fb6a8 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.properties new file mode 100755 index 0000000..b31d857 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/hide-check-digits.properties @@ -0,0 +1,2 @@ +showCheckDigits=false +content=0123456789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.codewords new file mode 100755 index 0000000..51e135a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.codewords @@ -0,0 +1,23 @@ +111141 +122211 +211113 +122211 +211212 +122211 +211311 +122211 +221112 +122211 +221211 +122211 +231111 +122211 +112113 +122211 +112212 +122211 +112311 +222111 +123111 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.png new file mode 100644 index 0000000..157d459 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.properties new file mode 100755 index 0000000..4995fc9 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-1.properties @@ -0,0 +1 @@ +content=abcdefghi diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.codewords new file mode 100755 index 0000000..8c1038f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.codewords @@ -0,0 +1,23 @@ +111141 +122211 +122112 +122211 +132111 +122211 +111123 +122211 +111222 +122211 +111321 +122211 +121122 +122211 +131121 +122211 +212112 +122211 +212211 +131211 +123111 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.png new file mode 100644 index 0000000..bd415da Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.properties new file mode 100755 index 0000000..f892b9b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-2.properties @@ -0,0 +1 @@ +content=jklmnopqr diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.codewords new file mode 100755 index 0000000..e9f6ae4 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.codewords @@ -0,0 +1,21 @@ +111141 +122211 +211122 +122211 +211221 +122211 +221121 +122211 +222111 +122211 +112122 +122211 +112221 +122211 +122121 +122211 +123111 +212211 +122121 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.png new file mode 100644 index 0000000..aa911a3 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.properties new file mode 100755 index 0000000..e53732a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-lowercase-3.properties @@ -0,0 +1 @@ +content=stuvwxyz diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.codewords new file mode 100755 index 0000000..1a37a3f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.codewords @@ -0,0 +1,14 @@ +111141 +211113 +211212 +211311 +221112 +221211 +231111 +112113 +112212 +112311 +121311 +312111 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.png new file mode 100644 index 0000000..c2b0467 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.properties new file mode 100755 index 0000000..b627ba3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-1.properties @@ -0,0 +1 @@ +content=ABCDEFGHI diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.codewords new file mode 100755 index 0000000..624464d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.codewords @@ -0,0 +1,14 @@ +111141 +122112 +132111 +111123 +111222 +111321 +121122 +131121 +212112 +212211 +123111 +211131 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.png new file mode 100644 index 0000000..7a28a02 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.properties new file mode 100755 index 0000000..05f475e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-2.properties @@ -0,0 +1 @@ +content=JKLMNOPQR diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.codewords new file mode 100755 index 0000000..2bfb512 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.codewords @@ -0,0 +1,13 @@ +111141 +211122 +211221 +221121 +222111 +112122 +112221 +122121 +123111 +211212 +321111 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.png new file mode 100644 index 0000000..a37ce08 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.properties new file mode 100755 index 0000000..1991c87 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/letters-uppercase-3.properties @@ -0,0 +1 @@ +content=STUVWXYZ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.codewords new file mode 100755 index 0000000..9532651 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.codewords @@ -0,0 +1,15 @@ +111141 +131112 +111213 +111312 +111411 +121113 +121212 +121311 +111114 +131211 +141111 +121122 +122211 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.png new file mode 100644 index 0000000..10894b1 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.properties new file mode 100755 index 0000000..fad6b30 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-bar-height.properties @@ -0,0 +1,2 @@ +barHeight=100 +content=0123456789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.codewords new file mode 100755 index 0000000..9532651 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.codewords @@ -0,0 +1,15 @@ +111141 +131112 +111213 +111312 +111411 +121113 +121212 +121311 +111114 +131211 +141111 +121122 +122211 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.png new file mode 100644 index 0000000..2ac613a Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.properties new file mode 100755 index 0000000..875c664 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers-with-custom-module-width.properties @@ -0,0 +1,2 @@ +moduleWidth=5 +content=0123456789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.codewords new file mode 100755 index 0000000..9532651 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.codewords @@ -0,0 +1,15 @@ +111141 +131112 +111213 +111312 +111411 +121113 +121212 +121311 +111114 +131211 +141111 +121122 +122211 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.png new file mode 100644 index 0000000..851ea8f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.properties new file mode 100755 index 0000000..7b4bbb1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/numbers.properties @@ -0,0 +1 @@ +content=0123456789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.codewords new file mode 100755 index 0000000..304c478 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.codewords @@ -0,0 +1,12 @@ +111141 +121131 +311112 +321111 +311211 +112131 +113121 +211131 +222111 +121311 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.png new file mode 100644 index 0000000..e51b8ec Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.properties new file mode 100755 index 0000000..fcf4d20 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-no-ctrl.properties @@ -0,0 +1 @@ +content=-.$ /+% diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.codewords new file mode 100755 index 0000000..71cbb84 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.codewords @@ -0,0 +1,23 @@ +111141 +312111 +112113 +121221 +221112 +121221 +112311 +121221 +132111 +312111 +211113 +312111 +221112 +312111 +221211 +312111 +112311 +312111 +122112 +112131 +111312 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.png new file mode 100644 index 0000000..09824dd Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.properties new file mode 100755 index 0000000..cb5235e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-1.properties @@ -0,0 +1 @@ +content=< >? diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.codewords new file mode 100755 index 0000000..a4689c5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.codewords @@ -0,0 +1,21 @@ +111141 +311121 +211113 +311121 +211212 +311121 +211311 +311121 +231111 +121221 +212112 +121221 +211122 +121221 +112122 +311121 +112113 +231111 +311211 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.png new file mode 100644 index 0000000..f05d1e7 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.properties new file mode 100755 index 0000000..d403b80 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-2.properties @@ -0,0 +1 @@ +content=!"#&' diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.codewords new file mode 100755 index 0000000..f38b861 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.codewords @@ -0,0 +1,19 @@ +111141 +311121 +112212 +311121 +112311 +311121 +122112 +311121 +111123 +312111 +131121 +312111 +212211 +312111 +211122 +111213 +122211 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.png new file mode 100644 index 0000000..d462510 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.properties new file mode 100755 index 0000000..26e0d21 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/special-characters-with-ctrl-3.properties @@ -0,0 +1 @@ +content=()*,{}~ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.codewords new file mode 100755 index 0000000..9532651 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.codewords @@ -0,0 +1,15 @@ +111141 +131112 +111213 +111312 +111411 +121113 +121212 +121311 +111114 +131211 +141111 +121122 +122211 +111141 +1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.png b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.png new file mode 100644 index 0000000..93066b9 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.properties new file mode 100755 index 0000000..e43c9e3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/code93/start-stop-delimiter.properties @@ -0,0 +1,2 @@ +startStopDelimiter=* +content=0123456789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.codewords new file mode 100755 index 0000000..47fbd8e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.codewords @@ -0,0 +1 @@ +1112122141123111231411121311111112133112321122212122121311191121222112122111141111132111231 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.png new file mode 100644 index 0000000..288d934 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.properties new file mode 100755 index 0000000..fc6e7f5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-13-basic.properties @@ -0,0 +1,2 @@ +mode=EAN13 +content=123456789012+12345 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.codewords new file mode 100755 index 0000000..bec10d3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.codewords @@ -0,0 +1 @@ +111222121221411113211111123111141312321111191121222112122111141111132111231 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.png new file mode 100644 index 0000000..82d9874 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.properties new file mode 100755 index 0000000..bbf49bd --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/ean/ean-8-basic.properties @@ -0,0 +1,2 @@ +mode=EAN8 +content=1234567+12345 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/fonts/OkapiDejaVuSans.ttf b/barcode/src/test/resources/org/xbib/graphics/barcode/fonts/OkapiDejaVuSans.ttf new file mode 100755 index 0000000..9642811 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/fonts/OkapiDejaVuSans.ttf differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.codewords new file mode 100755 index 0000000..659934a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.codewords @@ -0,0 +1 @@ +FDFFTFTTADFFTTFTTAFDFTFFDATFTFFTDAFTFTDFATDATDATDATDATDATDATDAFFTDF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.png new file mode 100644 index 0000000..e05f54f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.properties new file mode 100755 index 0000000..82ebd55 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/basic.properties @@ -0,0 +1 @@ +content=10800752-16-3 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.codewords new file mode 100755 index 0000000..51359d2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.codewords @@ -0,0 +1 @@ +FDFFTFTTADFFTTFTTAFDFTFFDATFTFFTDAFTFTFADTDATDATDATDATDATDATDAFTTDF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.png b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.png new file mode 100644 index 0000000..7589003 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.properties new file mode 100755 index 0000000..fab9d53 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/japanpost/bug-50.properties @@ -0,0 +1 @@ +content=10800752-16-4 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.codewords new file mode 100755 index 0000000..348d970 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.codewords @@ -0,0 +1 @@ +131131311131111311311131131131313113111111113311313111331111113133111111111331313111133111113113311111113331111111133131131131311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.png b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.png new file mode 100644 index 0000000..5d5c0fc Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.properties new file mode 100755 index 0000000..ed0e34a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters1.properties @@ -0,0 +1 @@ +content=ABCDEFGHIJ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.codewords new file mode 100755 index 0000000..0fca8f6 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.codewords @@ -0,0 +1 @@ +131131311131111113311131111331313111131111113113313111311311113131131111111133313111113311113111331111113133113311111131131131311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.png new file mode 100644 index 0000000..048958d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.properties new file mode 100755 index 0000000..fb01a6b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters2.properties @@ -0,0 +1 @@ +content=KLMNOPQRST diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.codewords new file mode 100755 index 0000000..7747129 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.codewords @@ -0,0 +1 @@ +13113131113311111131133111113133311111111311311131331131111113313111111111311331131131311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.png new file mode 100644 index 0000000..53e891f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.properties new file mode 100755 index 0000000..943bf42 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/letters3.properties @@ -0,0 +1 @@ +content=UVWXYZ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.codewords new file mode 100755 index 0000000..26b4c5b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.codewords @@ -0,0 +1 @@ +131131311131131111311133111131313311111111133111313113311111113331111111131131313113113111113311311111133131111133111131131131311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.png new file mode 100644 index 0000000..b0321bd Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.properties new file mode 100755 index 0000000..090bd8e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/module-width-ratio-2.properties @@ -0,0 +1,2 @@ +moduleWidthRatio=2 +content=1234567890 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.codewords new file mode 100755 index 0000000..26b4c5b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.codewords @@ -0,0 +1 @@ +131131311131131111311133111131313311111111133111313113311111113331111111131131313113113111113311311111133131111133111131131131311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.png b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.png new file mode 100644 index 0000000..435f272 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.properties new file mode 100755 index 0000000..4507f64 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/numbers.properties @@ -0,0 +1 @@ +content=1234567890 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.codewords new file mode 100755 index 0000000..dfd3585 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.codewords @@ -0,0 +1 @@ +131131311113111131313311113111133111311113131311111313111311131113131111131313111131331111131131311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.png b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.png new file mode 100644 index 0000000..ec27120 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.properties new file mode 100755 index 0000000..f5ff26d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/logmars/special.properties @@ -0,0 +1 @@ +content=-. $/+% diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-0.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-0.error new file mode 100755 index 0000000..6bd6b1a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-0.error @@ -0,0 +1 @@ +Invalid MaxiCode mode: 0 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-0.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-0.properties new file mode 100755 index 0000000..d9e332a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-0.properties @@ -0,0 +1 @@ +mode=0 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-1.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-1.error new file mode 100755 index 0000000..c8ac80f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-1.error @@ -0,0 +1 @@ +Invalid MaxiCode mode: 1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-1.properties new file mode 100755 index 0000000..6999e49 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-1.properties @@ -0,0 +1 @@ +mode=1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-invalid-primary.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-invalid-primary.error new file mode 100755 index 0000000..9c1bf4b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-invalid-primary.error @@ -0,0 +1 @@ +Invalid Primary String diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-invalid-primary.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-invalid-primary.properties new file mode 100755 index 0000000..650662d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-invalid-primary.properties @@ -0,0 +1,3 @@ +mode=2 +primary=152382802840X01 +content=[)>01961Z00004951UPSN06X61015912345671/1Y634 ALPHA DRPITTSBURGHPA diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.codewords new file mode 100755 index 0000000..50fe863 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.codewords @@ -0,0 +1,144 @@ +2 +16 +58 +17 +57 +18 +2 +18 +11 +0 +22 +61 +42 +19 +2 +16 +54 +10 +14 +2 +59 +42 +41 +59 +40 +30 +48 +49 +29 +57 +54 +49 +26 +48 +48 +48 +48 +50 +54 +51 +56 +29 +21 +16 +19 +14 +29 +24 +48 +51 +49 +49 +53 +30 +48 +55 +32 +47 +23 +45 +39 +46 +0 +18 +40 +6 +43 +57 +20 +2 +23 +37 +52 +47 +2 +34 +15 +20 +4 +57 +21 +10 +57 +38 +50 +37 +34 +50 +14 +14 +56 +26 +43 +13 +22 +41 +37 +8 +40 +10 +0 +30 +62 +4 +62 +60 +4 +63 +52 +28 +18 +62 +57 +21 +32 +53 +19 +50 +17 +3 +51 +52 +34 +9 +21 +8 +23 +42 +9 +60 +59 +44 +32 +52 +56 +49 +15 +35 +55 +4 +18 +34 +52 +3 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.png new file mode 100644 index 0000000..f7c37d5 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.properties new file mode 100755 index 0000000..aad4dc5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-max-data.properties @@ -0,0 +1,3 @@ +mode=2 +primary=194280000840002 +content=[)>01961Z00002638UPSNX0311507 /W-'. R(F+9TBW%4/B"OTD9UJ9&2%"2NN8Z+MV)%H(J  diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.codewords new file mode 100755 index 0000000..911cf43 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.codewords @@ -0,0 +1,144 @@ +34 mode 2 + part of the postal code +20 +45 +20 +17 +18 +2 +18 +7 +0 end primary message +61 start primary message error correction +53 +12 +1 +38 +55 +55 +6 +31 +40 end primary message error correction +59 [Shift B] +42 [ +41 ) +59 [Shift B] +40 > +30 RS +48 0 +49 1 +29 GS +57 9 +54 6 +49 1 +26 Z +48 0 +48 0 +48 0 +48 0 +52 4 +57 9 +53 5 +49 1 +29 GS +21 U +16 P +19 S +14 N +29 GS +48 0 +54 6 +24 X +54 6 +49 1 +48 0 +29 GS +49 1 +53 5 +57 9 +29 GS +49 1 +50 2 +51 3 +52 4 +53 5 +54 6 +55 7 +29 GS +49 1 +47 / +49 1 +29 GS +29 GS +25 Y +29 GS +54 6 +51 3 +52 4 +32 +1 A +12 L +16 P +8 H +1 A +32 +4 D +18 R +29 GS +16 P +9 I +20 T +20 T +19 S +2 B +21 U +18 R +7 G +8 H +29 GS +16 P +1 A +30 RS +62 [Shift E] +4 EOT +33 start padding +33 +44 start secondary message error correction +40 +52 +23 +54 +59 +30 +31 +49 +1 +41 +53 +21 +35 +8 +47 +10 +11 +32 +26 +60 +53 +5 +40 +39 +32 +8 +44 +38 +51 +43 +8 +55 +14 +23 +53 +40 +17 +49 +31 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.png new file mode 100644 index 0000000..2baf8d9 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.properties new file mode 100755 index 0000000..5c691f9 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-standard.properties @@ -0,0 +1,3 @@ +mode=2 +primary=152382802840001 +content=[)>01961Z00004951UPSN06X61015912345671/1Y634 ALPHA DRPITTSBURGHPA diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.codewords new file mode 100755 index 0000000..c5714ed --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.codewords @@ -0,0 +1,144 @@ +34 mode 2 + part of the postal code +20 +45 +20 +17 +18 +2 +18 +7 +0 end primary message +61 start primary message error correction +53 +12 +1 +38 +55 +55 +6 +31 +40 end primary message error correction +33 padding +7 structured append 1 of 8 = 000 111 = 7 +59 +42 +41 +59 +40 +30 +48 +49 +29 +57 +54 +49 +26 +48 +48 +48 +48 +52 +57 +53 +49 +29 +21 +16 +19 +14 +29 +48 +54 +24 +54 +49 +48 +29 +49 +53 +57 +29 +49 +50 +51 +52 +53 +54 +55 +29 +49 +47 +49 +29 +29 +25 +29 +54 +51 +52 +32 +1 +12 +16 +8 +32 +4 +18 +29 +16 +9 +20 +20 +19 +2 +21 +18 +7 +8 +29 +16 +1 +30 +62 +4 +33 +58 +8 +2 +57 +53 +62 +35 +42 +14 +50 +1 +28 +53 +1 +55 +2 +38 +19 +47 +47 +38 +13 +19 +51 +39 +63 +40 +5 +14 +28 +53 +34 +31 +4 +40 +36 +5 +20 +46 +26 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.png new file mode 100644 index 0000000..2b55ec9 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.properties new file mode 100755 index 0000000..0a1662d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-2-structured-append-1-of-8.properties @@ -0,0 +1,5 @@ +mode=2 +structuredAppendPosition=1 +structuredAppendTotal=8 +primary=152382802840001 +content=[)>01961Z00004951UPSN06X61015912345671/1Y634 ALPH DRPITTSBURGHPA diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.codewords new file mode 100755 index 0000000..db2e4c2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.codewords @@ -0,0 +1,144 @@ +19 mode 3 + part of the postal code +28 +16 +28 +16 +28 +0 +31 +4 +0 +59 start primary message error correction +33 +49 +2 +48 +62 +59 +0 +48 +25 +59 start secondary message +42 +41 +59 +40 +30 +48 +49 +29 +57 +54 +49 +26 +48 +48 +48 +48 +52 +57 +53 +49 +29 +21 +16 +19 +14 +29 +48 +54 +24 +54 +49 +48 +29 +49 +53 +57 +29 +49 +50 +51 +52 +53 +54 +55 +29 +49 +47 +49 +29 +29 +25 +29 +54 +51 +52 +32 +1 +12 +16 +8 +1 +32 +4 +18 +29 +16 +9 +20 +20 +19 +2 +21 +18 +7 +8 +29 +16 +1 +30 +62 +4 +33 +33 +44 start secondary message error correction +40 +52 +23 +54 +59 +30 +31 +49 +1 +41 +53 +21 +35 +8 +47 +10 +11 +32 +26 +60 +53 +5 +40 +39 +32 +8 +44 +38 +51 +43 +8 +55 +14 +23 +53 +40 +17 +49 +31 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.png new file mode 100644 index 0000000..36d9932 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.properties new file mode 100755 index 0000000..c6cd4cc --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-3-standard.properties @@ -0,0 +1,3 @@ +mode=3 +primary=A1A1A1 124001 +content=[)>01961Z00004951UPSN06X61015912345671/1Y634 ALPHA DRPITTSBURGHPA diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.codewords new file mode 100755 index 0000000..cecd045 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.codewords @@ -0,0 +1,144 @@ +4 mode 4 +1 A +2 B +3 C +33 start padding +33 +33 +33 +33 +33 +13 start primary message error correction +1 +28 +60 +35 +56 +0 +48 +52 +15 end primary message error correction +33 start secondary message +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +60 start secondary message error correction +60 +40 +40 +9 +9 +43 +43 +14 +14 +50 +50 +12 +12 +53 +53 +57 +57 +58 +58 +36 +36 +28 +28 +10 +10 +53 +53 +37 +37 +30 +30 +14 +14 +5 +5 +31 +31 +40 +40 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.png new file mode 100644 index 0000000..050f89a Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.properties new file mode 100755 index 0000000..fbca35f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic-structured-append-total-1.properties @@ -0,0 +1,4 @@ +# setting structured append total = 1 should have no impact, and should result in a symbol identical to one where it wasn't set at all +mode=4 +content=ABC +structuredAppendTotal=1 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.codewords new file mode 100755 index 0000000..cecd045 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.codewords @@ -0,0 +1,144 @@ +4 mode 4 +1 A +2 B +3 C +33 start padding +33 +33 +33 +33 +33 +13 start primary message error correction +1 +28 +60 +35 +56 +0 +48 +52 +15 end primary message error correction +33 start secondary message +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +60 start secondary message error correction +60 +40 +40 +9 +9 +43 +43 +14 +14 +50 +50 +12 +12 +53 +53 +57 +57 +58 +58 +36 +36 +28 +28 +10 +10 +53 +53 +37 +37 +30 +30 +14 +14 +5 +5 +31 +31 +40 +40 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.png new file mode 100644 index 0000000..050f89a Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.properties new file mode 100755 index 0000000..0fe6268 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-basic.properties @@ -0,0 +1,2 @@ +mode=4 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.codewords new file mode 100755 index 0000000..12a96a0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.codewords @@ -0,0 +1,144 @@ +4 mode 4 +0 start primary message +1 +2 +3 +4 +5 +6 +7 +8 end primary message +52 start primary message error correction +20 +12 +4 +9 +63 +12 +15 +8 +39 end primary message error correction +9 start secondary message +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +28 +29 +30 +32 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +58 +53 +54 +55 +56 +57 +33 start padding +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 end secondary message +57 start secondary message error correction +9 +46 +29 +12 +42 +48 +34 +18 +30 +42 +10 +57 +50 +23 +42 +14 +52 +42 +43 +5 +58 +27 +16 +9 +60 +33 +33 +6 +3 +47 +4 +49 +13 +42 +2 +34 +31 +61 +46 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.png new file mode 100644 index 0000000..c437156 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.properties new file mode 100755 index 0000000..a90a529 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-a-all-symbols.properties @@ -0,0 +1,2 @@ +mode=4 +content= ABCDEFGHIJKLMNOPQRSTUVWXYZ "#$%&'()*+,-./01234:56789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.codewords new file mode 100755 index 0000000..fe1d3d2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.codewords @@ -0,0 +1,144 @@ +4 mode 4 +63 [Latch B], since next 2+ chars are in Code Set B +0 +1 +2 +3 +4 +5 +6 +7 +42 start primary message error correction +34 +1 +61 +21 +61 +59 +47 +38 +7 end primary message error correction +8 start secondary message +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +28 +29 +30 +32 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +33 start padding +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 end secondary message +8 start secondary message error correction +25 +22 +26 +53 +57 +54 +49 +52 +5 +34 +13 +11 +7 +21 +42 +61 +11 +4 +9 +32 +29 +3 +15 +26 +24 +24 +3 +53 +12 +22 +26 +36 +45 +34 +13 +48 +40 +9 +10 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.png new file mode 100644 index 0000000..1241727 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.properties new file mode 100755 index 0000000..77c36db --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-set-b-all-symbols.properties @@ -0,0 +1,2 @@ +mode=4 +content=`abcdefghijklmnopqrstuvwxyz{}~;<=>?[\]^_ ,./:@!| diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.codewords new file mode 100755 index 0000000..f19a9b2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.codewords @@ -0,0 +1,144 @@ +4 mode 4 +1 A +59 [Shift B], since only next 1 char is in Code Set B +2 b +1 A +1 A +1 A +63 [Latch B], since next 2 chars are in Code Set B +2 b +2 b +11 start primary message error correction +13 +16 +33 +48 +38 +48 +15 +56 +56 end primary message error correction +59 [Shift A], since only next 1 char is in Code Set A +1 A +2 b +2 b +2 b +63 [Latch A], since next 4 chars are in Code Set A +1 A +1 A +1 A +1 A +63 [Latch B], since next 2 chars are in Code Set B +2 b +2 b +57 [3 Shift A], since next 3 chars are in Code Set A +1 A +1 A +1 A +2 b +2 b +56 [2 Shift A], since next 2 chars are in Code Set A +1 A +1 A +2 b +2 b +63 [Latch A], since next 4 chars (including padding) are in Code Set A +1 A +33 start padding +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +18 start secondary message error correction +45 +20 +58 +41 +11 +12 +58 +20 +31 +11 +7 +63 +62 +33 +52 +46 +4 +50 +49 +26 +59 +25 +37 +25 +36 +15 +9 +22 +49 +13 +9 +53 +5 +47 +63 +52 +57 +59 +53 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.png new file mode 100644 index 0000000..6c7f145 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.properties new file mode 100755 index 0000000..8145ca5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-a-and-b.properties @@ -0,0 +1,2 @@ +mode=4 +content=AbAAAbbAbbbAAAAbbAAAbbAAbbA diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.codewords new file mode 100755 index 0000000..c18a172 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.codewords @@ -0,0 +1,144 @@ +4 mode 4 +60 [Shift C] +7 +61 [Shift D] +17 +62 [Shift E] +39 +60 [Shift C] +7 +60 [Shift C] +34 start primary message error correction +18 +16 +28 +24 +58 +1 +47 +51 +8 end primary message error correction +7 start secondary message +61 [Shift D] +17 +61 [Shift D] +17 +62 [Shift E] +39 +62 [Shift E] +39 +60 [Shift C] +7 +60 [Shift C] +7 +60 [Shift C] +7 +61 [Shift D] +17 +61 [Shift D] +17 +61 [Shift D] +17 +62 [Shift E] +39 +62 [Shift E] +39 +62 [Shift E] +39 +60 [Shift C] +60 [Lock In C] +7 +7 +7 +7 +61 [Shift D] +61 [Lock In D] +17 +17 +17 +17 +62 [Shift E] +62 [Lock In E] +39 +39 +39 +39 +60 [Shift C] +60 [Lock In C] +7 +7 +7 +7 +7 +61 [Shift D] +61 [Lock In D] +17 +17 +17 +17 +17 +62 [Shift E] +62 [Lock In E] +39 +39 +39 +39 +39 +63 [Latch B] +33 start padding +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +5 start secondary message error correction +43 +15 +62 +60 +32 +13 +5 +41 +10 +1 +22 +12 +60 +42 +63 +51 +22 +35 +25 +6 +58 +46 +47 +43 +33 +44 +26 +17 +46 +35 +62 +1 +39 +11 +54 +29 +16 +61 +5 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.png new file mode 100644 index 0000000..1bacd8d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.properties new file mode 100755 index 0000000..b21445f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-code-sets-c-d-e.properties @@ -0,0 +1,2 @@ +mode=4 +content=Çñ£ÇÇññ££ÇÇÇñññ£££ÇÇÇÇññññ££££ÇÇÇÇÇñññññ£££££ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.codewords new file mode 100755 index 0000000..2f0b147 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.codewords @@ -0,0 +1,144 @@ +4 mode 4 +1 +2 +3 +4 +5 +6 +7 +8 +9 +2 start primary message error correction +35 +59 +36 +15 +56 +42 +17 +52 +51 end primary message error correction +10 start secondary message +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 end secondary message +58 start secondary message error correction +47 +56 +17 +12 +38 +46 +48 +30 +29 +29 +12 +50 +45 +12 +19 +58 +14 +21 +6 +39 +8 +9 +19 +63 +42 +7 +17 +41 +41 +5 +30 +6 +20 +36 +10 +26 +25 +39 +9 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.png new file mode 100644 index 0000000..f328de4 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.properties new file mode 100755 index 0000000..d837bda --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-max-data.properties @@ -0,0 +1,2 @@ +mode=4 +content=ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNO diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.codewords new file mode 100755 index 0000000..bad69e1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.codewords @@ -0,0 +1,144 @@ +4 mode 4 +31 start numeric shift [NS] +7 numeric compression codeword 1 +22 numeric compression codeword 2 +60 numeric compression codeword 3 +52 numeric compression codeword 4 +21 numeric compression codeword 5 +1 A +1 A +1 A +40 start primary message error correction +60 +12 +24 +63 +32 +21 +40 +5 +55 end primary message error correction +1 A +1 A +31 start numeric shift [NS] +58 numeric compression codeword 1 +55 numeric compression codeword 2 +38 numeric compression codeword 3 +34 numeric compression codeword 4 +49 numeric compression codeword 5 +33 start padding +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +47 start secondary message error correction +46 +50 +54 +58 +45 +5 +4 +48 +55 +23 +48 +19 +32 +9 +28 +54 +12 +49 +18 +27 +62 +35 +59 +56 +59 +0 +38 +4 +50 +14 +21 +8 +19 +19 +16 +21 +26 +43 +47 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.png new file mode 100644 index 0000000..bd503aa Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.properties new file mode 100755 index 0000000..8069709 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-numeric-shift.properties @@ -0,0 +1,2 @@ +mode=4 +content=123456789AAAAA987654321 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-0-of-5.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-0-of-5.error new file mode 100755 index 0000000..f3fa885 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-0-of-5.error @@ -0,0 +1 @@ +Invalid MaxiCode structured append position: 0 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-0-of-5.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-0-of-5.properties new file mode 100755 index 0000000..4680198 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-0-of-5.properties @@ -0,0 +1,4 @@ +mode=4 +structuredAppendTotal=5 +structuredAppendPosition=0 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.codewords new file mode 100755 index 0000000..cd9a1cc --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.codewords @@ -0,0 +1,144 @@ +4 mode 4 +33 padding +2 structured append 1 of 3 = 000 010 = 2 +1 +2 +3 +33 +33 +33 +33 +45 +22 +35 +13 +18 +36 +47 +1 +32 +15 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +60 +60 +40 +40 +9 +9 +43 +43 +14 +14 +50 +50 +12 +12 +53 +53 +57 +57 +58 +58 +36 +36 +28 +28 +10 +10 +53 +53 +37 +37 +30 +30 +14 +14 +5 +5 +31 +31 +40 +40 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.png new file mode 100644 index 0000000..740155b Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.properties new file mode 100755 index 0000000..d0473d1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-1-of-3.properties @@ -0,0 +1,3 @@ +mode=4 +structuredAppendTotal=3 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.codewords new file mode 100755 index 0000000..ab8376d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.codewords @@ -0,0 +1,144 @@ +4 mode 4 +33 padding +10 structured append 2 of 3 = 001 010 = 10 +4 +5 +6 +33 +33 +33 +33 +56 +39 +59 +0 +45 +11 +47 +18 +61 +26 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +60 +60 +40 +40 +9 +9 +43 +43 +14 +14 +50 +50 +12 +12 +53 +53 +57 +57 +58 +58 +36 +36 +28 +28 +10 +10 +53 +53 +37 +37 +30 +30 +14 +14 +5 +5 +31 +31 +40 +40 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.png new file mode 100644 index 0000000..c53022d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.properties new file mode 100755 index 0000000..8d1d855 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-2-of-3.properties @@ -0,0 +1,4 @@ +mode=4 +structuredAppendTotal=3 +structuredAppendPosition=2 +content=DEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.codewords new file mode 100755 index 0000000..29c6d75 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.codewords @@ -0,0 +1,144 @@ +4 mode 4 +33 padding +18 structured append 3 of 3 = 010 010 = 18 +7 +8 +9 +33 +33 +33 +33 +56 +52 +42 +57 +35 +19 +25 +36 +54 +46 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +60 +60 +40 +40 +9 +9 +43 +43 +14 +14 +50 +50 +12 +12 +53 +53 +57 +57 +58 +58 +36 +36 +28 +28 +10 +10 +53 +53 +37 +37 +30 +30 +14 +14 +5 +5 +31 +31 +40 +40 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.png new file mode 100644 index 0000000..d415e00 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.properties new file mode 100755 index 0000000..11ab9eb --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-3-of-3.properties @@ -0,0 +1,4 @@ +mode=4 +structuredAppendTotal=3 +structuredAppendPosition=3 +content=GHI diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-5-of-9.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-5-of-9.error new file mode 100755 index 0000000..9fd2ce5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-5-of-9.error @@ -0,0 +1 @@ +Invalid MaxiCode structured append total: 9 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-5-of-9.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-5-of-9.properties new file mode 100755 index 0000000..709ef05 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-structured-append-5-of-9.properties @@ -0,0 +1,4 @@ +mode=4 +structuredAppendPosition=5 +structuredAppendTotal=9 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-too-much-data.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-too-much-data.error new file mode 100755 index 0000000..74ec7c8 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-too-much-data.error @@ -0,0 +1 @@ +Input data too long diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-too-much-data.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-too-much-data.properties new file mode 100755 index 0000000..b52076d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-4-too-much-data.properties @@ -0,0 +1,2 @@ +mode=4 +content=ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.codewords new file mode 100755 index 0000000..78bfe26 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.codewords @@ -0,0 +1,144 @@ +5 mode 5 +1 A +2 B +3 C +33 start padding +33 +33 +33 +33 +33 +26 start primary message error correction +33 +12 +53 +26 +49 +21 +37 +45 +59 end primary message error correction +33 start secondary message +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 +33 end secondary message +5 start secondary message error correction +5 +39 +39 +9 +9 +62 +62 +6 +6 +31 +31 +53 +53 +56 +56 +6 +6 +39 +39 +36 +36 +3 +3 +63 +63 +18 +18 +9 +9 +28 +28 +53 +53 +59 +59 +37 +37 +38 +38 +40 +40 +28 +28 +31 +31 +41 +41 +20 +20 +24 +24 +44 +44 +34 +34 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.png new file mode 100644 index 0000000..b439a4d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.properties new file mode 100755 index 0000000..5302a27 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-basic.properties @@ -0,0 +1,2 @@ +mode=5 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.codewords new file mode 100755 index 0000000..f32c2aa --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.codewords @@ -0,0 +1,144 @@ +5 mode 5 +1 +2 +3 +4 +5 +6 +7 +8 +9 +21 start primary message error correction +3 +43 +45 +54 +49 +63 +4 +45 +7 end primary message error correction +10 start secondary message +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 end secondary message +5 start secondary message error correction +60 +20 +38 +7 +14 +59 +19 +52 +60 +54 +12 +17 +37 +62 +32 +43 +39 +16 +22 +3 +13 +0 +23 +20 +36 +47 +0 +17 +59 +4 +21 +39 +16 +38 +31 +63 +42 +32 +22 +3 +26 +16 +28 +37 +59 +13 +42 +59 +45 +60 +18 +36 +53 +20 +11 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.png new file mode 100644 index 0000000..0d33bb5 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.properties new file mode 100755 index 0000000..11dec84 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-max-data.properties @@ -0,0 +1,2 @@ +mode=5 +content=ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXY diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-too-much-data.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-too-much-data.error new file mode 100755 index 0000000..74ec7c8 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-too-much-data.error @@ -0,0 +1 @@ +Input data too long diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-too-much-data.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-too-much-data.properties new file mode 100755 index 0000000..1c25f21 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-5-too-much-data.properties @@ -0,0 +1,2 @@ +mode=5 +content=ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.codewords new file mode 100755 index 0000000..0f1d58a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.codewords @@ -0,0 +1,144 @@ +6 mode 6 +47 start primary message +47 +47 +47 +47 +47 +47 +47 +47 end primary message +37 start primary message error correction +32 +5 +29 +0 +5 +35 +21 +27 +58 end primary message error correction +47 start secondary message +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 +47 end secondary message +49 start secondary message error correction +49 +31 +31 +48 +48 +56 +56 +32 +32 +17 +17 +26 +26 +1 +1 +27 +27 +60 +60 +5 +5 +3 +3 +23 +23 +1 +1 +24 +24 +57 +57 +32 +32 +42 +42 +36 +36 +31 +31 end secondary message error correction diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.png b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.png new file mode 100644 index 0000000..71aec56 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.properties new file mode 100755 index 0000000..ead3ae3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-max-data.properties @@ -0,0 +1,2 @@ +mode=6 +content=///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-too-much-data.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-too-much-data.error new file mode 100755 index 0000000..74ec7c8 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-too-much-data.error @@ -0,0 +1 @@ +Input data too long diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-too-much-data.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-too-much-data.properties new file mode 100755 index 0000000..0dae1b1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-6-too-much-data.properties @@ -0,0 +1,2 @@ +mode=6 +content=////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-7.error b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-7.error new file mode 100755 index 0000000..8e1e16d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-7.error @@ -0,0 +1 @@ +Invalid MaxiCode mode: 7 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-7.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-7.properties new file mode 100755 index 0000000..0b770e5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/maxicode/mode-7.properties @@ -0,0 +1 @@ +mode=7 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-basic.eps b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-basic.eps new file mode 100755 index 0000000..62bea42 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-basic.eps @@ -0,0 +1,71 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: OkapiBarcode +%%Title: 123456789 +%%Pages: 0 +%%BoundingBox: 0 0 128 60 +%%EndComments +/TL { setlinewidth moveto lineto stroke } bind def +/TC { moveto 0 360 arc 360 0 arcn fill } bind def +/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def +/TB { 2 copy } bind def +/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def +/TE { pop pop } bind def +newpath +0.00 0.00 0.00 setrgbcolor +1.00 1.00 1.00 setrgbcolor +60.00 0.00 TB 0.00 128.00 TR +TE +0.00 0.00 0.00 setrgbcolor +40.00 15.00 TB 5.00 1.00 TR +TB 7.00 1.00 TR +TB 9.00 4.00 TR +TB 14.00 1.00 TR +TB 16.00 1.00 TR +TB 19.00 1.00 TR +TB 23.00 1.00 TR +TB 25.00 1.00 TR +TB 29.00 1.00 TR +TB 32.00 1.00 TR +TB 34.00 1.00 TR +TB 39.00 1.00 TR +TB 41.00 1.00 TR +TB 44.00 1.00 TR +TB 46.00 1.00 TR +TB 50.00 1.00 TR +TB 53.00 1.00 TR +TB 56.00 1.00 TR +TB 59.00 1.00 TR +TB 62.00 1.00 TR +TB 66.00 1.00 TR +TB 68.00 1.00 TR +TB 70.00 1.00 TR +TB 72.00 1.00 TR +TB 77.00 1.00 TR +TB 81.00 1.00 TR +TB 84.00 1.00 TR +TB 86.00 1.00 TR +TB 91.00 1.00 TR +TB 93.00 1.00 TR +TB 95.00 1.00 TR +TB 98.00 1.00 TR +TB 100.00 2.00 TR +TB 104.00 1.00 TR +TB 107.00 2.00 TR +TB 111.00 1.00 TR +TB 113.00 1.00 TR +TB 115.00 1.00 TR +TB 117.00 4.00 TR +TB 122.00 1.00 TR +TE +0.00 0.00 0.00 setrgbcolor +matrix currentmatrix +/Helvetica findfont +8.00 scalefont setfont + 0 0 moveto 64.00 7.00 translate 0.00 rotate 0 0 moveto + (123456789Od) stringwidth +pop +-2 div 0 rmoveto + (123456789Od) show +setmatrix + +showpage diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-basic.svg b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-basic.svg new file mode 100755 index 0000000..9d52bd0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-basic.svg @@ -0,0 +1,53 @@ + + + + 123456789 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789Od + + + diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-colors.eps b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-colors.eps new file mode 100755 index 0000000..1853e2f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-colors.eps @@ -0,0 +1,71 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: OkapiBarcode +%%Title: 123456789 +%%Pages: 0 +%%BoundingBox: 0 0 128 60 +%%EndComments +/TL { setlinewidth moveto lineto stroke } bind def +/TC { moveto 0 360 arc 360 0 arcn fill } bind def +/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def +/TB { 2 copy } bind def +/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def +/TE { pop pop } bind def +newpath +1.00 0.00 0.00 setrgbcolor +0.00 1.00 0.00 setrgbcolor +60.00 0.00 TB 0.00 128.00 TR +TE +1.00 0.00 0.00 setrgbcolor +40.00 15.00 TB 5.00 1.00 TR +TB 7.00 1.00 TR +TB 9.00 4.00 TR +TB 14.00 1.00 TR +TB 16.00 1.00 TR +TB 19.00 1.00 TR +TB 23.00 1.00 TR +TB 25.00 1.00 TR +TB 29.00 1.00 TR +TB 32.00 1.00 TR +TB 34.00 1.00 TR +TB 39.00 1.00 TR +TB 41.00 1.00 TR +TB 44.00 1.00 TR +TB 46.00 1.00 TR +TB 50.00 1.00 TR +TB 53.00 1.00 TR +TB 56.00 1.00 TR +TB 59.00 1.00 TR +TB 62.00 1.00 TR +TB 66.00 1.00 TR +TB 68.00 1.00 TR +TB 70.00 1.00 TR +TB 72.00 1.00 TR +TB 77.00 1.00 TR +TB 81.00 1.00 TR +TB 84.00 1.00 TR +TB 86.00 1.00 TR +TB 91.00 1.00 TR +TB 93.00 1.00 TR +TB 95.00 1.00 TR +TB 98.00 1.00 TR +TB 100.00 2.00 TR +TB 104.00 1.00 TR +TB 107.00 2.00 TR +TB 111.00 1.00 TR +TB 113.00 1.00 TR +TB 115.00 1.00 TR +TB 117.00 4.00 TR +TB 122.00 1.00 TR +TE +1.00 0.00 0.00 setrgbcolor +matrix currentmatrix +/Helvetica findfont +8.00 scalefont setfont + 0 0 moveto 64.00 7.00 translate 0.00 rotate 0 0 moveto + (123456789Od) stringwidth +pop +-2 div 0 rmoveto + (123456789Od) show +setmatrix + +showpage diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-colors.svg b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-colors.svg new file mode 100755 index 0000000..cab3f5b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-colors.svg @@ -0,0 +1,53 @@ + + + + 123456789 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789Od + + + diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-custom-font.eps b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-custom-font.eps new file mode 100755 index 0000000..18ba809 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-custom-font.eps @@ -0,0 +1,71 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: OkapiBarcode +%%Title: 123456789 +%%Pages: 0 +%%BoundingBox: 0 0 128 82 +%%EndComments +/TL { setlinewidth moveto lineto stroke } bind def +/TC { moveto 0 360 arc 360 0 arcn fill } bind def +/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def +/TB { 2 copy } bind def +/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def +/TE { pop pop } bind def +newpath +0.00 0.00 0.00 setrgbcolor +1.00 1.00 1.00 setrgbcolor +82.00 0.00 TB 0.00 128.00 TR +TE +0.00 0.00 0.00 setrgbcolor +40.00 37.00 TB 5.00 1.00 TR +TB 7.00 1.00 TR +TB 9.00 4.00 TR +TB 14.00 1.00 TR +TB 16.00 1.00 TR +TB 19.00 1.00 TR +TB 23.00 1.00 TR +TB 25.00 1.00 TR +TB 29.00 1.00 TR +TB 32.00 1.00 TR +TB 34.00 1.00 TR +TB 39.00 1.00 TR +TB 41.00 1.00 TR +TB 44.00 1.00 TR +TB 46.00 1.00 TR +TB 50.00 1.00 TR +TB 53.00 1.00 TR +TB 56.00 1.00 TR +TB 59.00 1.00 TR +TB 62.00 1.00 TR +TB 66.00 1.00 TR +TB 68.00 1.00 TR +TB 70.00 1.00 TR +TB 72.00 1.00 TR +TB 77.00 1.00 TR +TB 81.00 1.00 TR +TB 84.00 1.00 TR +TB 86.00 1.00 TR +TB 91.00 1.00 TR +TB 93.00 1.00 TR +TB 95.00 1.00 TR +TB 98.00 1.00 TR +TB 100.00 2.00 TR +TB 104.00 1.00 TR +TB 107.00 2.00 TR +TB 111.00 1.00 TR +TB 113.00 1.00 TR +TB 115.00 1.00 TR +TB 117.00 4.00 TR +TB 122.00 1.00 TR +TE +0.00 0.00 0.00 setrgbcolor +matrix currentmatrix +/Arial findfont +26.00 scalefont setfont + 0 0 moveto 64.00 11.00 translate 0.00 rotate 0 0 moveto + (123456789Od) stringwidth +pop +-2 div 0 rmoveto + (123456789Od) show +setmatrix + +showpage diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-custom-font.svg b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-custom-font.svg new file mode 100755 index 0000000..5db0be1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-custom-font.svg @@ -0,0 +1,53 @@ + + + + 123456789 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789Od + + + diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-magnification-2.eps b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-magnification-2.eps new file mode 100755 index 0000000..1d8d66e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-magnification-2.eps @@ -0,0 +1,71 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: OkapiBarcode +%%Title: 123456789 +%%Pages: 0 +%%BoundingBox: 0 0 256 120 +%%EndComments +/TL { setlinewidth moveto lineto stroke } bind def +/TC { moveto 0 360 arc 360 0 arcn fill } bind def +/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def +/TB { 2 copy } bind def +/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def +/TE { pop pop } bind def +newpath +0.00 0.00 0.00 setrgbcolor +1.00 1.00 1.00 setrgbcolor +120.00 0.00 TB 0.00 256.00 TR +TE +0.00 0.00 0.00 setrgbcolor +80.00 30.00 TB 10.00 2.00 TR +TB 14.00 2.00 TR +TB 18.00 8.00 TR +TB 28.00 2.00 TR +TB 32.00 2.00 TR +TB 38.00 2.00 TR +TB 46.00 2.00 TR +TB 50.00 2.00 TR +TB 58.00 2.00 TR +TB 64.00 2.00 TR +TB 68.00 2.00 TR +TB 78.00 2.00 TR +TB 82.00 2.00 TR +TB 88.00 2.00 TR +TB 92.00 2.00 TR +TB 100.00 2.00 TR +TB 106.00 2.00 TR +TB 112.00 2.00 TR +TB 118.00 2.00 TR +TB 124.00 2.00 TR +TB 132.00 2.00 TR +TB 136.00 2.00 TR +TB 140.00 2.00 TR +TB 144.00 2.00 TR +TB 154.00 2.00 TR +TB 162.00 2.00 TR +TB 168.00 2.00 TR +TB 172.00 2.00 TR +TB 182.00 2.00 TR +TB 186.00 2.00 TR +TB 190.00 2.00 TR +TB 196.00 2.00 TR +TB 200.00 4.00 TR +TB 208.00 2.00 TR +TB 214.00 4.00 TR +TB 222.00 2.00 TR +TB 226.00 2.00 TR +TB 230.00 2.00 TR +TB 234.00 8.00 TR +TB 244.00 2.00 TR +TE +0.00 0.00 0.00 setrgbcolor +matrix currentmatrix +/Helvetica findfont +16.00 scalefont setfont + 0 0 moveto 128.00 14.00 translate 0.00 rotate 0 0 moveto + (123456789Od) stringwidth +pop +-2 div 0 rmoveto + (123456789Od) show +setmatrix + +showpage diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-magnification-2.svg b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-magnification-2.svg new file mode 100755 index 0000000..34baea3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-magnification-2.svg @@ -0,0 +1,53 @@ + + + + 123456789 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789Od + + + diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-margin-size-20.eps b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-margin-size-20.eps new file mode 100755 index 0000000..2dfa31b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-margin-size-20.eps @@ -0,0 +1,71 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: OkapiBarcode +%%Title: 123456789 +%%Pages: 0 +%%BoundingBox: 0 0 158 90 +%%EndComments +/TL { setlinewidth moveto lineto stroke } bind def +/TC { moveto 0 360 arc 360 0 arcn fill } bind def +/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def +/TB { 2 copy } bind def +/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def +/TE { pop pop } bind def +newpath +0.00 0.00 0.00 setrgbcolor +1.00 1.00 1.00 setrgbcolor +90.00 0.00 TB 0.00 158.00 TR +TE +0.00 0.00 0.00 setrgbcolor +40.00 30.00 TB 20.00 1.00 TR +TB 22.00 1.00 TR +TB 24.00 4.00 TR +TB 29.00 1.00 TR +TB 31.00 1.00 TR +TB 34.00 1.00 TR +TB 38.00 1.00 TR +TB 40.00 1.00 TR +TB 44.00 1.00 TR +TB 47.00 1.00 TR +TB 49.00 1.00 TR +TB 54.00 1.00 TR +TB 56.00 1.00 TR +TB 59.00 1.00 TR +TB 61.00 1.00 TR +TB 65.00 1.00 TR +TB 68.00 1.00 TR +TB 71.00 1.00 TR +TB 74.00 1.00 TR +TB 77.00 1.00 TR +TB 81.00 1.00 TR +TB 83.00 1.00 TR +TB 85.00 1.00 TR +TB 87.00 1.00 TR +TB 92.00 1.00 TR +TB 96.00 1.00 TR +TB 99.00 1.00 TR +TB 101.00 1.00 TR +TB 106.00 1.00 TR +TB 108.00 1.00 TR +TB 110.00 1.00 TR +TB 113.00 1.00 TR +TB 115.00 2.00 TR +TB 119.00 1.00 TR +TB 122.00 2.00 TR +TB 126.00 1.00 TR +TB 128.00 1.00 TR +TB 130.00 1.00 TR +TB 132.00 4.00 TR +TB 137.00 1.00 TR +TE +0.00 0.00 0.00 setrgbcolor +matrix currentmatrix +/Helvetica findfont +8.00 scalefont setfont + 0 0 moveto 79.00 22.00 translate 0.00 rotate 0 0 moveto + (123456789Od) stringwidth +pop +-2 div 0 rmoveto + (123456789Od) show +setmatrix + +showpage diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-margin-size-20.svg b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-margin-size-20.svg new file mode 100755 index 0000000..f27da66 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/code93-margin-size-20.svg @@ -0,0 +1,53 @@ + + + + 123456789 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789Od + + + diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/maxicode-basic.eps b/barcode/src/test/resources/org/xbib/graphics/barcode/output/maxicode-basic.eps new file mode 100755 index 0000000..c256da2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/maxicode-basic.eps @@ -0,0 +1,393 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: OkapiBarcode +%%Title: 123456789 +%%Pages: 0 +%%BoundingBox: 0 0 420 410 +%%EndComments +/TL { setlinewidth moveto lineto stroke } bind def +/TC { moveto 0 360 arc 360 0 arcn fill } bind def +/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def +/TB { 2 copy } bind def +/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def +/TE { pop pop } bind def +newpath +0.00 0.00 0.00 setrgbcolor +1.00 1.00 1.00 setrgbcolor +410.00 0.00 TB 0.00 420.00 TR +TE +0.00 0.00 0.00 setrgbcolor +0.00 0.00 0.00 setrgbcolor +60.76 349.40 10.85 60.76 349.40 8.97 69.73 349.40 TC +60.76 349.40 7.10 60.76 349.40 5.22 65.98 349.40 TC +60.76 349.40 3.31 60.76 349.40 1.43 62.19 349.40 TC +28.69 382.32 29.77 382.95 29.77 384.20 28.69 384.82 27.62 384.20 27.62 382.95 TH +33.61 382.32 34.69 382.95 34.69 384.20 33.61 384.82 32.54 384.20 32.54 382.95 TH +38.53 382.32 39.61 382.95 39.61 384.20 38.53 384.82 37.46 384.20 37.46 382.95 TH +43.45 382.32 44.53 382.95 44.53 384.20 43.45 384.82 42.38 384.20 42.38 382.95 TH +48.37 382.32 49.45 382.95 49.45 384.20 48.37 384.82 47.30 384.20 47.30 382.95 TH +53.29 382.32 54.36 382.95 54.36 384.20 53.29 384.82 52.22 384.20 52.22 382.95 TH +58.21 382.32 59.29 382.95 59.29 384.20 58.21 384.82 57.14 384.20 57.14 382.95 TH +63.13 382.32 64.21 382.95 64.21 384.20 63.13 384.82 62.05 384.20 62.05 382.95 TH +68.05 382.32 69.13 382.95 69.13 384.20 68.05 384.82 66.98 384.20 66.98 382.95 TH +72.97 382.32 74.05 382.95 74.05 384.20 72.97 384.82 71.90 384.20 71.90 382.95 TH +77.89 382.32 78.97 382.95 78.97 384.20 77.89 384.82 76.82 384.20 76.82 382.95 TH +82.81 382.32 83.88 382.95 83.88 384.20 82.81 384.82 81.73 384.20 81.73 382.95 TH +87.73 382.32 88.81 382.95 88.81 384.20 87.73 384.82 86.66 384.20 86.66 382.95 TH +92.65 382.32 93.73 382.95 93.73 384.20 92.65 384.82 91.58 384.20 91.58 382.95 TH +95.11 382.32 96.19 382.95 96.19 384.20 95.11 384.82 94.04 384.20 94.04 382.95 TH +97.57 382.32 98.65 382.95 98.65 384.20 97.57 384.82 96.50 384.20 96.50 382.95 TH +26.23 378.05 27.31 378.68 27.31 379.93 26.23 380.55 25.16 379.93 25.16 378.68 TH +31.15 378.05 32.23 378.68 32.23 379.93 31.15 380.55 30.08 379.93 30.08 378.68 TH +36.07 378.05 37.14 378.68 37.14 379.93 36.07 380.55 35.00 379.93 35.00 378.68 TH +40.99 378.05 42.07 378.68 42.07 379.93 40.99 380.55 39.92 379.93 39.92 378.68 TH +45.91 378.05 46.99 378.68 46.99 379.93 45.91 380.55 44.84 379.93 44.84 378.68 TH +50.83 378.05 51.91 378.68 51.91 379.93 50.83 380.55 49.76 379.93 49.76 378.68 TH +55.75 378.05 56.83 378.68 56.83 379.93 55.75 380.55 54.68 379.93 54.68 378.68 TH +60.67 378.05 61.75 378.68 61.75 379.93 60.67 380.55 59.59 379.93 59.59 378.68 TH +65.59 378.05 66.66 378.68 66.66 379.93 65.59 380.55 64.51 379.93 64.51 378.68 TH +70.51 378.05 71.59 378.68 71.59 379.93 70.51 380.55 69.44 379.93 69.44 378.68 TH +75.43 378.05 76.51 378.68 76.51 379.93 75.43 380.55 74.35 379.93 74.35 378.68 TH +80.35 378.05 81.43 378.68 81.43 379.93 80.35 380.55 79.27 379.93 79.27 378.68 TH +85.27 378.05 86.35 378.68 86.35 379.93 85.27 380.55 84.20 379.93 84.20 378.68 TH +90.19 378.05 91.27 378.68 91.27 379.93 90.19 380.55 89.12 379.93 89.12 378.68 TH +95.11 378.05 96.19 378.68 96.19 379.93 95.11 380.55 94.04 379.93 94.04 378.68 TH +29.92 375.92 31.00 376.54 31.00 377.79 29.92 378.42 28.85 377.79 28.85 376.54 TH +34.84 375.92 35.92 376.54 35.92 377.79 34.84 378.42 33.77 377.79 33.77 376.54 TH +39.76 375.92 40.84 376.54 40.84 377.79 39.76 378.42 38.69 377.79 38.69 376.54 TH +44.68 375.92 45.75 376.54 45.75 377.79 44.68 378.42 43.61 377.79 43.61 376.54 TH +49.60 375.92 50.68 376.54 50.68 377.79 49.60 378.42 48.53 377.79 48.53 376.54 TH +54.52 375.92 55.60 376.54 55.60 377.79 54.52 378.42 53.45 377.79 53.45 376.54 TH +59.44 375.92 60.52 376.54 60.52 377.79 59.44 378.42 58.36 377.79 58.36 376.54 TH +64.36 375.92 65.44 376.54 65.44 377.79 64.36 378.42 63.28 377.79 63.28 376.54 TH +69.28 375.92 70.35 376.54 70.35 377.79 69.28 378.42 68.20 377.79 68.20 376.54 TH +74.20 375.92 75.28 376.54 75.28 377.79 74.20 378.42 73.13 377.79 73.13 376.54 TH +79.12 375.92 80.20 376.54 80.20 377.79 79.12 378.42 78.04 377.79 78.04 376.54 TH +84.04 375.92 85.12 376.54 85.12 377.79 84.04 378.42 82.96 377.79 82.96 376.54 TH +88.96 375.92 90.04 376.54 90.04 377.79 88.96 378.42 87.88 377.79 87.88 376.54 TH +93.88 375.92 94.96 376.54 94.96 377.79 93.88 378.42 92.81 377.79 92.81 376.54 TH +96.34 375.92 97.42 376.54 97.42 377.79 96.34 378.42 95.27 377.79 95.27 376.54 TH +97.57 373.78 98.65 374.41 98.65 375.66 97.57 376.28 96.50 375.66 96.50 374.41 TH +27.46 371.65 28.54 372.27 28.54 373.52 27.46 374.15 26.39 373.52 26.39 372.27 TH +32.38 371.65 33.46 372.27 33.46 373.52 32.38 374.15 31.31 373.52 31.31 372.27 TH +37.30 371.65 38.38 372.27 38.38 373.52 37.30 374.15 36.23 373.52 36.23 372.27 TH +42.22 371.65 43.30 372.27 43.30 373.52 42.22 374.15 41.14 373.52 41.14 372.27 TH +47.14 371.65 48.22 372.27 48.22 373.52 47.14 374.15 46.07 373.52 46.07 372.27 TH +52.06 371.65 53.14 372.27 53.14 373.52 52.06 374.15 50.99 373.52 50.99 372.27 TH +56.98 371.65 58.06 372.27 58.06 373.52 56.98 374.15 55.91 373.52 55.91 372.27 TH +61.90 371.65 62.97 372.27 62.97 373.52 61.90 374.15 60.82 373.52 60.82 372.27 TH +66.82 371.65 67.90 372.27 67.90 373.52 66.82 374.15 65.74 373.52 65.74 372.27 TH +71.74 371.65 72.82 372.27 72.82 373.52 71.74 374.15 70.66 373.52 70.66 372.27 TH +76.66 371.65 77.74 372.27 77.74 373.52 76.66 374.15 75.59 373.52 75.59 372.27 TH +81.58 371.65 82.66 372.27 82.66 373.52 81.58 374.15 80.51 373.52 80.51 372.27 TH +86.50 371.65 87.57 372.27 87.57 373.52 86.50 374.15 85.42 373.52 85.42 372.27 TH +91.42 371.65 92.50 372.27 92.50 373.52 91.42 374.15 90.35 373.52 90.35 372.27 TH +28.69 369.51 29.77 370.14 29.77 371.39 28.69 372.01 27.62 371.39 27.62 370.14 TH +33.61 369.51 34.69 370.14 34.69 371.39 33.61 372.01 32.54 371.39 32.54 370.14 TH +38.53 369.51 39.61 370.14 39.61 371.39 38.53 372.01 37.46 371.39 37.46 370.14 TH +43.45 369.51 44.53 370.14 44.53 371.39 43.45 372.01 42.38 371.39 42.38 370.14 TH +48.37 369.51 49.45 370.14 49.45 371.39 48.37 372.01 47.30 371.39 47.30 370.14 TH +53.29 369.51 54.36 370.14 54.36 371.39 53.29 372.01 52.22 371.39 52.22 370.14 TH +58.21 369.51 59.29 370.14 59.29 371.39 58.21 372.01 57.14 371.39 57.14 370.14 TH +63.13 369.51 64.21 370.14 64.21 371.39 63.13 372.01 62.05 371.39 62.05 370.14 TH +68.05 369.51 69.13 370.14 69.13 371.39 68.05 372.01 66.98 371.39 66.98 370.14 TH +72.97 369.51 74.05 370.14 74.05 371.39 72.97 372.01 71.90 371.39 71.90 370.14 TH +77.89 369.51 78.97 370.14 78.97 371.39 77.89 372.01 76.82 371.39 76.82 370.14 TH +82.81 369.51 83.88 370.14 83.88 371.39 82.81 372.01 81.73 371.39 81.73 370.14 TH +87.73 369.51 88.81 370.14 88.81 371.39 87.73 372.01 86.66 371.39 86.66 370.14 TH +92.65 369.51 93.73 370.14 93.73 371.39 92.65 372.01 91.58 371.39 91.58 370.14 TH +95.11 369.51 96.19 370.14 96.19 371.39 95.11 372.01 94.04 371.39 94.04 370.14 TH +96.34 367.38 97.42 368.00 97.42 369.25 96.34 369.88 95.27 369.25 95.27 368.00 TH +26.23 365.24 27.31 365.87 27.31 367.12 26.23 367.74 25.16 367.12 25.16 365.87 TH +31.15 365.24 32.23 365.87 32.23 367.12 31.15 367.74 30.08 367.12 30.08 365.87 TH +36.07 365.24 37.14 365.87 37.14 367.12 36.07 367.74 35.00 367.12 35.00 365.87 TH +40.99 365.24 42.07 365.87 42.07 367.12 40.99 367.74 39.92 367.12 39.92 365.87 TH +45.91 365.24 46.99 365.87 46.99 367.12 45.91 367.74 44.84 367.12 44.84 365.87 TH +50.83 365.24 51.91 365.87 51.91 367.12 50.83 367.74 49.76 367.12 49.76 365.87 TH +55.75 365.24 56.83 365.87 56.83 367.12 55.75 367.74 54.68 367.12 54.68 365.87 TH +60.67 365.24 61.75 365.87 61.75 367.12 60.67 367.74 59.59 367.12 59.59 365.87 TH +65.59 365.24 66.66 365.87 66.66 367.12 65.59 367.74 64.51 367.12 64.51 365.87 TH +70.51 365.24 71.59 365.87 71.59 367.12 70.51 367.74 69.44 367.12 69.44 365.87 TH +75.43 365.24 76.51 365.87 76.51 367.12 75.43 367.74 74.35 367.12 74.35 365.87 TH +80.35 365.24 81.43 365.87 81.43 367.12 80.35 367.74 79.27 367.12 79.27 365.87 TH +85.27 365.24 86.35 365.87 86.35 367.12 85.27 367.74 84.20 367.12 84.20 365.87 TH +90.19 365.24 91.27 365.87 91.27 367.12 90.19 367.74 89.12 367.12 89.12 365.87 TH +97.57 365.24 98.65 365.87 98.65 367.12 97.57 367.74 96.50 367.12 96.50 365.87 TH +29.92 363.11 31.00 363.73 31.00 364.98 29.92 365.61 28.85 364.98 28.85 363.73 TH +34.84 363.11 35.92 363.73 35.92 364.98 34.84 365.61 33.77 364.98 33.77 363.73 TH +39.76 363.11 40.84 363.73 40.84 364.98 39.76 365.61 38.69 364.98 38.69 363.73 TH +44.68 363.11 45.75 363.73 45.75 364.98 44.68 365.61 43.61 364.98 43.61 363.73 TH +47.14 363.11 48.22 363.73 48.22 364.98 47.14 365.61 46.07 364.98 46.07 363.73 TH +49.60 363.11 50.68 363.73 50.68 364.98 49.60 365.61 48.53 364.98 48.53 363.73 TH +52.06 363.11 53.14 363.73 53.14 364.98 52.06 365.61 50.99 364.98 50.99 363.73 TH +54.52 363.11 55.60 363.73 55.60 364.98 54.52 365.61 53.45 364.98 53.45 363.73 TH +61.90 363.11 62.97 363.73 62.97 364.98 61.90 365.61 60.82 364.98 60.82 363.73 TH +76.66 363.11 77.74 363.73 77.74 364.98 76.66 365.61 75.59 364.98 75.59 363.73 TH +79.12 363.11 80.20 363.73 80.20 364.98 79.12 365.61 78.04 364.98 78.04 363.73 TH +84.04 363.11 85.12 363.73 85.12 364.98 84.04 365.61 82.96 364.98 82.96 363.73 TH +88.96 363.11 90.04 363.73 90.04 364.98 88.96 365.61 87.88 364.98 87.88 363.73 TH +93.88 363.11 94.96 363.73 94.96 364.98 93.88 365.61 92.81 364.98 92.81 363.73 TH +53.29 360.97 54.36 361.60 54.36 362.85 53.29 363.47 52.22 362.85 52.22 361.60 TH +55.75 360.97 56.83 361.60 56.83 362.85 55.75 363.47 54.68 362.85 54.68 361.60 TH +60.67 360.97 61.75 361.60 61.75 362.85 60.67 363.47 59.59 362.85 59.59 361.60 TH +65.59 360.97 66.66 361.60 66.66 362.85 65.59 363.47 64.51 362.85 64.51 361.60 TH +75.43 360.97 76.51 361.60 76.51 362.85 75.43 363.47 74.35 362.85 74.35 361.60 TH +77.89 360.97 78.97 361.60 78.97 362.85 77.89 363.47 76.82 362.85 76.82 361.60 TH +27.46 358.84 28.54 359.46 28.54 360.71 27.46 361.34 26.39 360.71 26.39 359.46 TH +32.38 358.84 33.46 359.46 33.46 360.71 32.38 361.34 31.31 360.71 31.31 359.46 TH +37.30 358.84 38.38 359.46 38.38 360.71 37.30 361.34 36.23 360.71 36.23 359.46 TH +42.22 358.84 43.30 359.46 43.30 360.71 42.22 361.34 41.14 360.71 41.14 359.46 TH +52.06 358.84 53.14 359.46 53.14 360.71 52.06 361.34 50.99 360.71 50.99 359.46 TH +71.74 358.84 72.82 359.46 72.82 360.71 71.74 361.34 70.66 360.71 70.66 359.46 TH +76.66 358.84 77.74 359.46 77.74 360.71 76.66 361.34 75.59 360.71 75.59 359.46 TH +81.58 358.84 82.66 359.46 82.66 360.71 81.58 361.34 80.51 360.71 80.51 359.46 TH +86.50 358.84 87.57 359.46 87.57 360.71 86.50 361.34 85.42 360.71 85.42 359.46 TH +91.42 358.84 92.50 359.46 92.50 360.71 91.42 361.34 90.35 360.71 90.35 359.46 TH +96.34 358.84 97.42 359.46 97.42 360.71 96.34 361.34 95.27 360.71 95.27 359.46 TH +28.69 356.70 29.77 357.33 29.77 358.58 28.69 359.20 27.62 358.58 27.62 357.33 TH +33.61 356.70 34.69 357.33 34.69 358.58 33.61 359.20 32.54 358.58 32.54 357.33 TH +38.53 356.70 39.61 357.33 39.61 358.58 38.53 359.20 37.46 358.58 37.46 357.33 TH +43.45 356.70 44.53 357.33 44.53 358.58 43.45 359.20 42.38 358.58 42.38 357.33 TH +48.37 356.70 49.45 357.33 49.45 358.58 48.37 359.20 47.30 358.58 47.30 357.33 TH +50.83 356.70 51.91 357.33 51.91 358.58 50.83 359.20 49.76 358.58 49.76 357.33 TH +72.97 356.70 74.05 357.33 74.05 358.58 72.97 359.20 71.90 358.58 71.90 357.33 TH +75.43 356.70 76.51 357.33 76.51 358.58 75.43 359.20 74.35 358.58 74.35 357.33 TH +82.81 356.70 83.88 357.33 83.88 358.58 82.81 359.20 81.73 358.58 81.73 357.33 TH +87.73 356.70 88.81 357.33 88.81 358.58 87.73 359.20 86.66 358.58 86.66 357.33 TH +92.65 356.70 93.73 357.33 93.73 358.58 92.65 359.20 91.58 358.58 91.58 357.33 TH +95.11 356.70 96.19 357.33 96.19 358.58 95.11 359.20 94.04 358.58 94.04 357.33 TH +42.22 354.57 43.30 355.19 43.30 356.44 42.22 357.07 41.14 356.44 41.14 355.19 TH +71.74 354.57 72.82 355.19 72.82 356.44 71.74 357.07 70.66 356.44 70.66 355.19 TH +74.20 354.57 75.28 355.19 75.28 356.44 74.20 357.07 73.13 356.44 73.13 355.19 TH +76.66 354.57 77.74 355.19 77.74 356.44 76.66 357.07 75.59 356.44 75.59 355.19 TH +79.12 354.57 80.20 355.19 80.20 356.44 79.12 357.07 78.04 356.44 78.04 355.19 TH +26.23 352.43 27.31 353.06 27.31 354.31 26.23 354.93 25.16 354.31 25.16 353.06 TH +31.15 352.43 32.23 353.06 32.23 354.31 31.15 354.93 30.08 354.31 30.08 353.06 TH +36.07 352.43 37.14 353.06 37.14 354.31 36.07 354.93 35.00 354.31 35.00 353.06 TH +40.99 352.43 42.07 353.06 42.07 354.31 40.99 354.93 39.92 354.31 39.92 353.06 TH +45.91 352.43 46.99 353.06 46.99 354.31 45.91 354.93 44.84 354.31 44.84 353.06 TH +75.43 352.43 76.51 353.06 76.51 354.31 75.43 354.93 74.35 354.31 74.35 353.06 TH +80.35 352.43 81.43 353.06 81.43 354.31 80.35 354.93 79.27 354.31 79.27 353.06 TH +85.27 352.43 86.35 353.06 86.35 354.31 85.27 354.93 84.20 354.31 84.20 353.06 TH +90.19 352.43 91.27 353.06 91.27 354.31 90.19 354.93 89.12 354.31 89.12 353.06 TH +29.92 350.30 31.00 350.92 31.00 352.17 29.92 352.80 28.85 352.17 28.85 350.92 TH +34.84 350.30 35.92 350.92 35.92 352.17 34.84 352.80 33.77 352.17 33.77 350.92 TH +39.76 350.30 40.84 350.92 40.84 352.17 39.76 352.80 38.69 352.17 38.69 350.92 TH +42.22 350.30 43.30 350.92 43.30 352.17 42.22 352.80 41.14 352.17 41.14 350.92 TH +44.68 350.30 45.75 350.92 45.75 352.17 44.68 352.80 43.61 352.17 43.61 350.92 TH +47.14 350.30 48.22 350.92 48.22 352.17 47.14 352.80 46.07 352.17 46.07 350.92 TH +76.66 350.30 77.74 350.92 77.74 352.17 76.66 352.80 75.59 352.17 75.59 350.92 TH +84.04 350.30 85.12 350.92 85.12 352.17 84.04 352.80 82.96 352.17 82.96 350.92 TH +88.96 350.30 90.04 350.92 90.04 352.17 88.96 352.80 87.88 352.17 87.88 350.92 TH +93.88 350.30 94.96 350.92 94.96 352.17 93.88 352.80 92.81 352.17 92.81 350.92 TH +96.34 350.30 97.42 350.92 97.42 352.17 96.34 352.80 95.27 352.17 95.27 350.92 TH +45.91 348.16 46.99 348.79 46.99 350.04 45.91 350.66 44.84 350.04 44.84 348.79 TH +75.43 348.16 76.51 348.79 76.51 350.04 75.43 350.66 74.35 350.04 74.35 348.79 TH +95.11 348.16 96.19 348.79 96.19 350.04 95.11 350.66 94.04 350.04 94.04 348.79 TH +27.46 346.03 28.54 346.65 28.54 347.90 27.46 348.53 26.39 347.90 26.39 346.65 TH +32.38 346.03 33.46 346.65 33.46 347.90 32.38 348.53 31.31 347.90 31.31 346.65 TH +37.30 346.03 38.38 346.65 38.38 347.90 37.30 348.53 36.23 347.90 36.23 346.65 TH +47.14 346.03 48.22 346.65 48.22 347.90 47.14 348.53 46.07 347.90 46.07 346.65 TH +76.66 346.03 77.74 346.65 77.74 347.90 76.66 348.53 75.59 347.90 75.59 346.65 TH +79.12 346.03 80.20 346.65 80.20 347.90 79.12 348.53 78.04 347.90 78.04 346.65 TH +81.58 346.03 82.66 346.65 82.66 347.90 81.58 348.53 80.51 347.90 80.51 346.65 TH +86.50 346.03 87.57 346.65 87.57 347.90 86.50 348.53 85.42 347.90 85.42 346.65 TH +91.42 346.03 92.50 346.65 92.50 347.90 91.42 348.53 90.35 347.90 90.35 346.65 TH +28.69 343.89 29.77 344.52 29.77 345.77 28.69 346.39 27.62 345.77 27.62 344.52 TH +33.61 343.89 34.69 344.52 34.69 345.77 33.61 346.39 32.54 345.77 32.54 344.52 TH +38.53 343.89 39.61 344.52 39.61 345.77 38.53 346.39 37.46 345.77 37.46 344.52 TH +43.45 343.89 44.53 344.52 44.53 345.77 43.45 346.39 42.38 345.77 42.38 344.52 TH +48.37 343.89 49.45 344.52 49.45 345.77 48.37 346.39 47.30 345.77 47.30 344.52 TH +72.97 343.89 74.05 344.52 74.05 345.77 72.97 346.39 71.90 345.77 71.90 344.52 TH +75.43 343.89 76.51 344.52 76.51 345.77 75.43 346.39 74.35 345.77 74.35 344.52 TH +77.89 343.89 78.97 344.52 78.97 345.77 77.89 346.39 76.82 345.77 76.82 344.52 TH +82.81 343.89 83.88 344.52 83.88 345.77 82.81 346.39 81.73 345.77 81.73 344.52 TH +87.73 343.89 88.81 344.52 88.81 345.77 87.73 346.39 86.66 345.77 86.66 344.52 TH +92.65 343.89 93.73 344.52 93.73 345.77 92.65 346.39 91.58 345.77 91.58 344.52 TH +95.11 343.89 96.19 344.52 96.19 345.77 95.11 346.39 94.04 345.77 94.04 344.52 TH +97.57 343.89 98.65 344.52 98.65 345.77 97.57 346.39 96.50 345.77 96.50 344.52 TH +42.22 341.76 43.30 342.38 43.30 343.63 42.22 344.26 41.14 343.63 41.14 342.38 TH +47.14 341.76 48.22 342.38 48.22 343.63 47.14 344.26 46.07 343.63 46.07 342.38 TH +49.60 341.76 50.68 342.38 50.68 343.63 49.60 344.26 48.53 343.63 48.53 342.38 TH +74.20 341.76 75.28 342.38 75.28 343.63 74.20 344.26 73.13 343.63 73.13 342.38 TH +96.34 341.76 97.42 342.38 97.42 343.63 96.34 344.26 95.27 343.63 95.27 342.38 TH +26.23 339.62 27.31 340.25 27.31 341.50 26.23 342.12 25.16 341.50 25.16 340.25 TH +31.15 339.62 32.23 340.25 32.23 341.50 31.15 342.12 30.08 341.50 30.08 340.25 TH +36.07 339.62 37.14 340.25 37.14 341.50 36.07 342.12 35.00 341.50 35.00 340.25 TH +43.45 339.62 44.53 340.25 44.53 341.50 43.45 342.12 42.38 341.50 42.38 340.25 TH +48.37 339.62 49.45 340.25 49.45 341.50 48.37 342.12 47.30 341.50 47.30 340.25 TH +50.83 339.62 51.91 340.25 51.91 341.50 50.83 342.12 49.76 341.50 49.76 340.25 TH +75.43 339.62 76.51 340.25 76.51 341.50 75.43 342.12 74.35 341.50 74.35 340.25 TH +77.89 339.62 78.97 340.25 78.97 341.50 77.89 342.12 76.82 341.50 76.82 340.25 TH +80.35 339.62 81.43 340.25 81.43 341.50 80.35 342.12 79.27 341.50 79.27 340.25 TH +85.27 339.62 86.35 340.25 86.35 341.50 85.27 342.12 84.20 341.50 84.20 340.25 TH +90.19 339.62 91.27 340.25 91.27 341.50 90.19 342.12 89.12 341.50 89.12 340.25 TH +95.11 339.62 96.19 340.25 96.19 341.50 95.11 342.12 94.04 341.50 94.04 340.25 TH +97.57 339.62 98.65 340.25 98.65 341.50 97.57 342.12 96.50 341.50 96.50 340.25 TH +29.92 337.49 31.00 338.11 31.00 339.36 29.92 339.99 28.85 339.36 28.85 338.11 TH +34.84 337.49 35.92 338.11 35.92 339.36 34.84 339.99 33.77 339.36 33.77 338.11 TH +39.76 337.49 40.84 338.11 40.84 339.36 39.76 339.99 38.69 339.36 38.69 338.11 TH +44.68 337.49 45.75 338.11 45.75 339.36 44.68 339.99 43.61 339.36 43.61 338.11 TH +49.60 337.49 50.68 338.11 50.68 339.36 49.60 339.99 48.53 339.36 48.53 338.11 TH +69.28 337.49 70.35 338.11 70.35 339.36 69.28 339.99 68.20 339.36 68.20 338.11 TH +71.74 337.49 72.82 338.11 72.82 339.36 71.74 339.99 70.66 339.36 70.66 338.11 TH +74.20 337.49 75.28 338.11 75.28 339.36 74.20 339.99 73.13 339.36 73.13 338.11 TH +76.66 337.49 77.74 338.11 77.74 339.36 76.66 339.99 75.59 339.36 75.59 338.11 TH +79.12 337.49 80.20 338.11 80.20 339.36 79.12 339.99 78.04 339.36 78.04 338.11 TH +84.04 337.49 85.12 338.11 85.12 339.36 84.04 339.99 82.96 339.36 82.96 338.11 TH +88.96 337.49 90.04 338.11 90.04 339.36 88.96 339.99 87.88 339.36 87.88 338.11 TH +93.88 337.49 94.96 338.11 94.96 339.36 93.88 339.99 92.81 339.36 92.81 338.11 TH +45.91 335.35 46.99 335.98 46.99 337.23 45.91 337.85 44.84 337.23 44.84 335.98 TH +48.37 335.35 49.45 335.98 49.45 337.23 48.37 337.85 47.30 337.23 47.30 335.98 TH +50.83 335.35 51.91 335.98 51.91 337.23 50.83 337.85 49.76 337.23 49.76 335.98 TH +55.75 335.35 56.83 335.98 56.83 337.23 55.75 337.85 54.68 337.23 54.68 335.98 TH +65.59 335.35 66.66 335.98 66.66 337.23 65.59 337.85 64.51 337.23 64.51 335.98 TH +68.05 335.35 69.13 335.98 69.13 337.23 68.05 337.85 66.98 337.23 66.98 335.98 TH +70.51 335.35 71.59 335.98 71.59 337.23 70.51 337.85 69.44 337.23 69.44 335.98 TH +75.43 335.35 76.51 335.98 76.51 337.23 75.43 337.85 74.35 337.23 74.35 335.98 TH +95.11 335.35 96.19 335.98 96.19 337.23 95.11 337.85 94.04 337.23 94.04 335.98 TH +97.57 335.35 98.65 335.98 98.65 337.23 97.57 337.85 96.50 337.23 96.50 335.98 TH +27.46 333.22 28.54 333.84 28.54 335.09 27.46 335.72 26.39 335.09 26.39 333.84 TH +32.38 333.22 33.46 333.84 33.46 335.09 32.38 335.72 31.31 335.09 31.31 333.84 TH +37.30 333.22 38.38 333.84 38.38 335.09 37.30 335.72 36.23 335.09 36.23 333.84 TH +42.22 333.22 43.30 333.84 43.30 335.09 42.22 335.72 41.14 335.09 41.14 333.84 TH +47.14 333.22 48.22 333.84 48.22 335.09 47.14 335.72 46.07 335.09 46.07 333.84 TH +52.06 333.22 53.14 333.84 53.14 335.09 52.06 335.72 50.99 335.09 50.99 333.84 TH +54.52 333.22 55.60 333.84 55.60 335.09 54.52 335.72 53.45 335.09 53.45 333.84 TH +56.98 333.22 58.06 333.84 58.06 335.09 56.98 335.72 55.91 335.09 55.91 333.84 TH +59.44 333.22 60.52 333.84 60.52 335.09 59.44 335.72 58.36 335.09 58.36 333.84 TH +61.90 333.22 62.97 333.84 62.97 335.09 61.90 335.72 60.82 335.09 60.82 333.84 TH +64.36 333.22 65.44 333.84 65.44 335.09 64.36 335.72 63.28 335.09 63.28 333.84 TH +69.28 333.22 70.35 333.84 70.35 335.09 69.28 335.72 68.20 335.09 68.20 333.84 TH +71.74 333.22 72.82 333.84 72.82 335.09 71.74 335.72 70.66 335.09 70.66 333.84 TH +81.58 333.22 82.66 333.84 82.66 335.09 81.58 335.72 80.51 335.09 80.51 333.84 TH +86.50 333.22 87.57 333.84 87.57 335.09 86.50 335.72 85.42 335.09 85.42 333.84 TH +91.42 333.22 92.50 333.84 92.50 335.09 91.42 335.72 90.35 335.09 90.35 333.84 TH +96.34 333.22 97.42 333.84 97.42 335.09 96.34 335.72 95.27 335.09 95.27 333.84 TH +28.69 331.08 29.77 331.71 29.77 332.96 28.69 333.58 27.62 332.96 27.62 331.71 TH +33.61 331.08 34.69 331.71 34.69 332.96 33.61 333.58 32.54 332.96 32.54 331.71 TH +38.53 331.08 39.61 331.71 39.61 332.96 38.53 333.58 37.46 332.96 37.46 331.71 TH +43.45 331.08 44.53 331.71 44.53 332.96 43.45 333.58 42.38 332.96 42.38 331.71 TH +48.37 331.08 49.45 331.71 49.45 332.96 48.37 333.58 47.30 332.96 47.30 331.71 TH +53.29 331.08 54.36 331.71 54.36 332.96 53.29 333.58 52.22 332.96 52.22 331.71 TH +58.21 331.08 59.29 331.71 59.29 332.96 58.21 333.58 57.14 332.96 57.14 331.71 TH +63.13 331.08 64.21 331.71 64.21 332.96 63.13 333.58 62.05 332.96 62.05 331.71 TH +68.05 331.08 69.13 331.71 69.13 332.96 68.05 333.58 66.98 332.96 66.98 331.71 TH +72.97 331.08 74.05 331.71 74.05 332.96 72.97 333.58 71.90 332.96 71.90 331.71 TH +75.43 331.08 76.51 331.71 76.51 332.96 75.43 333.58 74.35 332.96 74.35 331.71 TH +77.89 331.08 78.97 331.71 78.97 332.96 77.89 333.58 76.82 332.96 76.82 331.71 TH +80.35 331.08 81.43 331.71 81.43 332.96 80.35 333.58 79.27 332.96 79.27 331.71 TH +82.81 331.08 83.88 331.71 83.88 332.96 82.81 333.58 81.73 332.96 81.73 331.71 TH +87.73 331.08 88.81 331.71 88.81 332.96 87.73 333.58 86.66 332.96 86.66 331.71 TH +92.65 331.08 93.73 331.71 93.73 332.96 92.65 333.58 91.58 332.96 91.58 331.71 TH +95.11 331.08 96.19 331.71 96.19 332.96 95.11 333.58 94.04 332.96 94.04 331.71 TH +97.57 331.08 98.65 331.71 98.65 332.96 97.57 333.58 96.50 332.96 96.50 331.71 TH +76.66 328.95 77.74 329.57 77.74 330.82 76.66 331.45 75.59 330.82 75.59 329.57 TH +79.12 328.95 80.20 329.57 80.20 330.82 79.12 331.45 78.04 330.82 78.04 329.57 TH +81.58 328.95 82.66 329.57 82.66 330.82 81.58 331.45 80.51 330.82 80.51 329.57 TH +84.04 328.95 85.12 329.57 85.12 330.82 84.04 331.45 82.96 330.82 82.96 329.57 TH +88.96 328.95 90.04 329.57 90.04 330.82 88.96 331.45 87.88 330.82 87.88 329.57 TH +93.88 328.95 94.96 329.57 94.96 330.82 93.88 331.45 92.81 330.82 92.81 329.57 TH +96.34 328.95 97.42 329.57 97.42 330.82 96.34 331.45 95.27 330.82 95.27 329.57 TH +26.23 326.81 27.31 327.44 27.31 328.69 26.23 329.31 25.16 328.69 25.16 327.44 TH +31.15 326.81 32.23 327.44 32.23 328.69 31.15 329.31 30.08 328.69 30.08 327.44 TH +36.07 326.81 37.14 327.44 37.14 328.69 36.07 329.31 35.00 328.69 35.00 327.44 TH +40.99 326.81 42.07 327.44 42.07 328.69 40.99 329.31 39.92 328.69 39.92 327.44 TH +45.91 326.81 46.99 327.44 46.99 328.69 45.91 329.31 44.84 328.69 44.84 327.44 TH +50.83 326.81 51.91 327.44 51.91 328.69 50.83 329.31 49.76 328.69 49.76 327.44 TH +55.75 326.81 56.83 327.44 56.83 328.69 55.75 329.31 54.68 328.69 54.68 327.44 TH +60.67 326.81 61.75 327.44 61.75 328.69 60.67 329.31 59.59 328.69 59.59 327.44 TH +65.59 326.81 66.66 327.44 66.66 328.69 65.59 329.31 64.51 328.69 64.51 327.44 TH +70.51 326.81 71.59 327.44 71.59 328.69 70.51 329.31 69.44 328.69 69.44 327.44 TH +95.11 326.81 96.19 327.44 96.19 328.69 95.11 329.31 94.04 328.69 94.04 327.44 TH +27.46 324.68 28.54 325.30 28.54 326.55 27.46 327.18 26.39 326.55 26.39 325.30 TH +29.92 324.68 31.00 325.30 31.00 326.55 29.92 327.18 28.85 326.55 28.85 325.30 TH +32.38 324.68 33.46 325.30 33.46 326.55 32.38 327.18 31.31 326.55 31.31 325.30 TH +34.84 324.68 35.92 325.30 35.92 326.55 34.84 327.18 33.77 326.55 33.77 325.30 TH +37.30 324.68 38.38 325.30 38.38 326.55 37.30 327.18 36.23 326.55 36.23 325.30 TH +39.76 324.68 40.84 325.30 40.84 326.55 39.76 327.18 38.69 326.55 38.69 325.30 TH +42.22 324.68 43.30 325.30 43.30 326.55 42.22 327.18 41.14 326.55 41.14 325.30 TH +44.68 324.68 45.75 325.30 45.75 326.55 44.68 327.18 43.61 326.55 43.61 325.30 TH +56.98 324.68 58.06 325.30 58.06 326.55 56.98 327.18 55.91 326.55 55.91 325.30 TH +59.44 324.68 60.52 325.30 60.52 326.55 59.44 327.18 58.36 326.55 58.36 325.30 TH +61.90 324.68 62.97 325.30 62.97 326.55 61.90 327.18 60.82 326.55 60.82 325.30 TH +64.36 324.68 65.44 325.30 65.44 326.55 64.36 327.18 63.28 326.55 63.28 325.30 TH +79.12 324.68 80.20 325.30 80.20 326.55 79.12 327.18 78.04 326.55 78.04 325.30 TH +84.04 324.68 85.12 325.30 85.12 326.55 84.04 327.18 82.96 326.55 82.96 325.30 TH +28.69 322.54 29.77 323.17 29.77 324.42 28.69 325.04 27.62 324.42 27.62 323.17 TH +33.61 322.54 34.69 323.17 34.69 324.42 33.61 325.04 32.54 324.42 32.54 323.17 TH +36.07 322.54 37.14 323.17 37.14 324.42 36.07 325.04 35.00 324.42 35.00 323.17 TH +40.99 322.54 42.07 323.17 42.07 324.42 40.99 325.04 39.92 324.42 39.92 323.17 TH +45.91 322.54 46.99 323.17 46.99 324.42 45.91 325.04 44.84 324.42 44.84 323.17 TH +48.37 322.54 49.45 323.17 49.45 324.42 48.37 325.04 47.30 324.42 47.30 323.17 TH +50.83 322.54 51.91 323.17 51.91 324.42 50.83 325.04 49.76 324.42 49.76 323.17 TH +53.29 322.54 54.36 323.17 54.36 324.42 53.29 325.04 52.22 324.42 52.22 323.17 TH +65.59 322.54 66.66 323.17 66.66 324.42 65.59 325.04 64.51 324.42 64.51 323.17 TH +68.05 322.54 69.13 323.17 69.13 324.42 68.05 325.04 66.98 324.42 66.98 323.17 TH +70.51 322.54 71.59 323.17 71.59 324.42 70.51 325.04 69.44 324.42 69.44 323.17 TH +72.97 322.54 74.05 323.17 74.05 324.42 72.97 325.04 71.90 324.42 71.90 323.17 TH +77.89 322.54 78.97 323.17 78.97 324.42 77.89 325.04 76.82 324.42 76.82 323.17 TH +82.81 322.54 83.88 323.17 83.88 324.42 82.81 325.04 81.73 324.42 81.73 323.17 TH +87.73 322.54 88.81 323.17 88.81 324.42 87.73 325.04 86.66 324.42 86.66 323.17 TH +92.65 322.54 93.73 323.17 93.73 324.42 92.65 325.04 91.58 324.42 91.58 323.17 TH +27.46 320.41 28.54 321.03 28.54 322.28 27.46 322.91 26.39 322.28 26.39 321.03 TH +32.38 320.41 33.46 321.03 33.46 322.28 32.38 322.91 31.31 322.28 31.31 321.03 TH +37.30 320.41 38.38 321.03 38.38 322.28 37.30 322.91 36.23 322.28 36.23 321.03 TH +42.22 320.41 43.30 321.03 43.30 322.28 42.22 322.91 41.14 322.28 41.14 321.03 TH +59.44 320.41 60.52 321.03 60.52 322.28 59.44 322.91 58.36 322.28 58.36 321.03 TH +64.36 320.41 65.44 321.03 65.44 322.28 64.36 322.91 63.28 322.28 63.28 321.03 TH +69.28 320.41 70.35 321.03 70.35 322.28 69.28 322.91 68.20 322.28 68.20 321.03 TH +74.20 320.41 75.28 321.03 75.28 322.28 74.20 322.91 73.13 322.28 73.13 321.03 TH +76.66 320.41 77.74 321.03 77.74 322.28 76.66 322.91 75.59 322.28 75.59 321.03 TH +79.12 320.41 80.20 321.03 80.20 322.28 79.12 322.91 78.04 322.28 78.04 321.03 TH +81.58 320.41 82.66 321.03 82.66 322.28 81.58 322.91 80.51 322.28 80.51 321.03 TH +84.04 320.41 85.12 321.03 85.12 322.28 84.04 322.91 82.96 322.28 82.96 321.03 TH +86.50 320.41 87.57 321.03 87.57 322.28 86.50 322.91 85.42 322.28 85.42 321.03 TH +91.42 320.41 92.50 321.03 92.50 322.28 91.42 322.91 90.35 322.28 90.35 321.03 TH +96.34 320.41 97.42 321.03 97.42 322.28 96.34 322.91 95.27 322.28 95.27 321.03 TH +26.23 318.27 27.31 318.90 27.31 320.15 26.23 320.77 25.16 320.15 25.16 318.90 TH +28.69 318.27 29.77 318.90 29.77 320.15 28.69 320.77 27.62 320.15 27.62 318.90 TH +31.15 318.27 32.23 318.90 32.23 320.15 31.15 320.77 30.08 320.15 30.08 318.90 TH +33.61 318.27 34.69 318.90 34.69 320.15 33.61 320.77 32.54 320.15 32.54 318.90 TH +38.53 318.27 39.61 318.90 39.61 320.15 38.53 320.77 37.46 320.15 37.46 318.90 TH +43.45 318.27 44.53 318.90 44.53 320.15 43.45 320.77 42.38 320.15 42.38 318.90 TH +45.91 318.27 46.99 318.90 46.99 320.15 45.91 320.77 44.84 320.15 44.84 318.90 TH +50.83 318.27 51.91 318.90 51.91 320.15 50.83 320.77 49.76 320.15 49.76 318.90 TH +65.59 318.27 66.66 318.90 66.66 320.15 65.59 320.77 64.51 320.15 64.51 318.90 TH +68.05 318.27 69.13 318.90 69.13 320.15 68.05 320.77 66.98 320.15 66.98 318.90 TH +70.51 318.27 71.59 318.90 71.59 320.15 70.51 320.77 69.44 320.15 69.44 318.90 TH +72.97 318.27 74.05 318.90 74.05 320.15 72.97 320.77 71.90 320.15 71.90 318.90 TH +77.89 318.27 78.97 318.90 78.97 320.15 77.89 320.77 76.82 320.15 76.82 318.90 TH +82.81 318.27 83.88 318.90 83.88 320.15 82.81 320.77 81.73 320.15 81.73 318.90 TH +85.27 318.27 86.35 318.90 86.35 320.15 85.27 320.77 84.20 320.15 84.20 318.90 TH +90.19 318.27 91.27 318.90 91.27 320.15 90.19 320.77 89.12 320.15 89.12 318.90 TH +95.11 318.27 96.19 318.90 96.19 320.15 95.11 320.77 94.04 320.15 94.04 318.90 TH +29.92 316.14 31.00 316.76 31.00 318.01 29.92 318.64 28.85 318.01 28.85 316.76 TH +34.84 316.14 35.92 316.76 35.92 318.01 34.84 318.64 33.77 318.01 33.77 316.76 TH +37.30 316.14 38.38 316.76 38.38 318.01 37.30 318.64 36.23 318.01 36.23 316.76 TH +42.22 316.14 43.30 316.76 43.30 318.01 42.22 318.64 41.14 318.01 41.14 316.76 TH +47.14 316.14 48.22 316.76 48.22 318.01 47.14 318.64 46.07 318.01 46.07 316.76 TH +49.60 316.14 50.68 316.76 50.68 318.01 49.60 318.64 48.53 318.01 48.53 316.76 TH +52.06 316.14 53.14 316.76 53.14 318.01 52.06 318.64 50.99 318.01 50.99 316.76 TH +54.52 316.14 55.60 316.76 55.60 318.01 54.52 318.64 53.45 318.01 53.45 316.76 TH +59.44 316.14 60.52 316.76 60.52 318.01 59.44 318.64 58.36 318.01 58.36 316.76 TH +64.36 316.14 65.44 316.76 65.44 318.01 64.36 318.64 63.28 318.01 63.28 316.76 TH +66.82 316.14 67.90 316.76 67.90 318.01 66.82 318.64 65.74 318.01 65.74 316.76 TH +71.74 316.14 72.82 316.76 72.82 318.01 71.74 318.64 70.66 318.01 70.66 316.76 TH +76.66 316.14 77.74 316.76 77.74 318.01 76.66 318.64 75.59 318.01 75.59 316.76 TH +81.58 316.14 82.66 316.76 82.66 318.01 81.58 318.64 80.51 318.01 80.51 316.76 TH +86.50 316.14 87.57 316.76 87.57 318.01 86.50 318.64 85.42 318.01 85.42 316.76 TH +88.96 316.14 90.04 316.76 90.04 318.01 88.96 318.64 87.88 318.01 87.88 316.76 TH +91.42 316.14 92.50 316.76 92.50 318.01 91.42 318.64 90.35 318.01 90.35 316.76 TH +93.88 316.14 94.96 316.76 94.96 318.01 93.88 318.64 92.81 318.01 92.81 316.76 TH +28.69 314.00 29.77 314.63 29.77 315.88 28.69 316.50 27.62 315.88 27.62 314.63 TH +33.61 314.00 34.69 314.63 34.69 315.88 33.61 316.50 32.54 315.88 32.54 314.63 TH +58.21 314.00 59.29 314.63 59.29 315.88 58.21 316.50 57.14 315.88 57.14 314.63 TH +63.13 314.00 64.21 314.63 64.21 315.88 63.13 316.50 62.05 315.88 62.05 314.63 TH +65.59 314.00 66.66 314.63 66.66 315.88 65.59 316.50 64.51 315.88 64.51 314.63 TH +70.51 314.00 71.59 314.63 71.59 315.88 70.51 316.50 69.44 315.88 69.44 314.63 TH +75.43 314.00 76.51 314.63 76.51 315.88 75.43 316.50 74.35 315.88 74.35 314.63 TH +80.35 314.00 81.43 314.63 81.43 315.88 80.35 316.50 79.27 315.88 79.27 314.63 TH +87.73 314.00 88.81 314.63 88.81 315.88 87.73 316.50 86.66 315.88 86.66 314.63 TH +92.65 314.00 93.73 314.63 93.73 315.88 92.65 316.50 91.58 315.88 91.58 314.63 TH + +showpage diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/output/maxicode-basic.svg b/barcode/src/test/resources/org/xbib/graphics/barcode/output/maxicode-basic.svg new file mode 100755 index 0000000..40be49c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/output/maxicode-basic.svg @@ -0,0 +1,384 @@ + + + + 123456789 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.codewords new file mode 100755 index 0000000..869cf40 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.codewords @@ -0,0 +1,11 @@ +22131114232212341113313221111 +31131142111413113122614121111 +31221121231143143211234211111 +22221143211132241113233311111 +21321131112612431313112411111 +21411121421124244211212321111 +22311114232212122133232312111 +31311143141112511212233212111 +32211121311531326111124112111 +41211121211316121221264111211 +42111111521133131153214111121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.png new file mode 100644 index 0000000..6c7137c Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.properties new file mode 100755 index 0000000..d67a107 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-2-columns.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=2 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.codewords new file mode 100755 index 0000000..9dca55e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.codewords @@ -0,0 +1,6 @@ +221311142322123411133111223112122423231511222213111 +311311221151321414411112123151322112222243113113111 +312211611132212233123112213121421124244211213122111 +222211112141342122242213113121231611212232322222111 +213211331421121213522113122111411342312242212132111 +214111123212241116124113212115411311215311132141111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.png new file mode 100644 index 0000000..8a60ca8 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.properties new file mode 100755 index 0000000..bd91f93 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic-ecc-level-7-ignored.properties @@ -0,0 +1,3 @@ +mode=MICRO +preferredEccLevel=7 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.codewords new file mode 100755 index 0000000..9dca55e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.codewords @@ -0,0 +1,6 @@ +221311142322123411133111223112122423231511222213111 +311311221151321414411112123151322112222243113113111 +312211611132212233123112213121421124244211213122111 +222211112141342122242213113121231611212232322222111 +213211331421121213522113122111411342312242212132111 +214111123212241116124113212115411311215311132141111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.png new file mode 100644 index 0000000..8a60ca8 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.properties new file mode 100755 index 0000000..aaaa744 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-basic.properties @@ -0,0 +1,2 @@ +mode=MICRO +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.codewords new file mode 100755 index 0000000..4165dd4 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.codewords @@ -0,0 +1,17 @@ +22211223521211223312312221121 +21311232241221312222322131121 +21221231162112116112232122121 +21222151214211113113252122211 +21213111252312112323142121311 +21212261211222223151122121221 +21211311212343235212112121131 +21121325111223213332212112131 +21112312221162221143222111231 +21113252123211111422512111321 +21114114222114111611152111411 +21123133132113112312612112311 +21122222211144132312322112221 +21131221143141241221322113121 +21132143111115512213122113211 +21141112241331221113522114111 +21231112112325114243112123111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.png new file mode 100644 index 0000000..6bb628a Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.properties new file mode 100755 index 0000000..818cc95 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-data-variety.properties @@ -0,0 +1,2 @@ +mode=MICRO +content=TESTING 12345678901234567890 testing@TESTING diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.codewords new file mode 100755 index 0000000..87ecbae --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.codewords @@ -0,0 +1,15 @@ +2131121423221211231214212214121224232131121 +2122121131226111232112621221211244212122121 +2122211113235111142121151151235212112122211 +2121311423221211133114232212142322122121311 +2121221161112411132211611124116111242121221 +2121132352121111123223521211235212112121131 +2112131423221211122314232212142322122112131 +2111231161112411113311611124116111242111231 +2111321112134411112431121261211111552111321 +2111411212222511121412222116214233112111411 +2112312214412111211422234211321222142112311 +2112223231114212111411421413314113312112221 +2113123134131112112322122134113134132113121 +2113211122521312113211114216511212232113211 +2114111163311111213214121242611223112114111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.png new file mode 100644 index 0000000..4a18fda Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.properties new file mode 100755 index 0000000..30bf693 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-1-of-3.properties @@ -0,0 +1,6 @@ +mode=MICRO +variant=18 +structuredAppendFileId=123 +structuredAppendPosition=1 +structuredAppendTotal=3 +content=this diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.codewords new file mode 100755 index 0000000..1543e8d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.codewords @@ -0,0 +1,15 @@ +2131121423221211231224211214231511222131121 +2122123111261211232112621221211244212122121 +2122215113311211142121151151235212112122211 +2121311423221211133114232212142322122121311 +2121221161112411132211611124116111242121221 +2121132352121111123223521211235212112121131 +2112131423221211122314232212142322122112131 +2111231161112411113311611124116111242111231 +2111323262111111112412421412235112122111321 +2111413131242111121421211415233222122111411 +2112311241215111211423234111211331152112311 +2112221213124312111463122111123413212112221 +2113121231422212112311213423111115162113121 +2113214112212412113231162112512311132113211 +2114111324113211213213222241335111212114111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.png new file mode 100644 index 0000000..3a77743 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.properties new file mode 100755 index 0000000..4b81899 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-2-of-3.properties @@ -0,0 +1,6 @@ +mode=MICRO +variant=18 +structuredAppendFileId=123 +structuredAppendPosition=2 +structuredAppendTotal=3 +content=is a diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.codewords new file mode 100755 index 0000000..52cf6cb --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.codewords @@ -0,0 +1,15 @@ +2131121423221211231214212214111611152131121 +2122122313412111232112621221211244212122121 +2122211113316111142121151151245111122122211 +2121311423221211133114232212142322122121311 +2121221161112411132211611124116111242121221 +2121132352121111123223521211235212112121131 +2112131423221211122314232212142322122112131 +2111231161112411113311611124116111242111231 +2111322141152111112411423132511431112111321 +2111412213213311121412324122141232312111411 +2112313212332111211411235113611113312112311 +2112221123143212111423331131121112452112221 +2113123312133112112315122123212213152113121 +2113213214231112113211322161623112112113211 +2114116111322111213221331232132123412114111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.png new file mode 100644 index 0000000..da1bed4 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.properties new file mode 100755 index 0000000..8c88464 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-structured-append-3-of-3.properties @@ -0,0 +1,6 @@ +mode=MICRO +variant=18 +structuredAppendFileId=123 +structuredAppendPosition=3 +structuredAppendTotal=3 +content=test diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.codewords new file mode 100755 index 0000000..4328727 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.codewords @@ -0,0 +1,26 @@ +2213112333221114211131113251124141134112111 +3113111112451213311121611132121244124111211 +3122116111241113221141215112112323414111121 +2222113324112113131112151412212122253211121 +2132111312522112231126113211612112223121121 +2141112122124312321111161241121212443112121 +2231111423221212411115123131151115123112211 +3131111221541111511121143411116112233111311 +3221114121116111421123116112115131233111221 +4121112111213611412114232212151115123111131 +4211111221541112312121143312121142152211131 +3311113111116312311221131153111123532211221 +2411112113314212221233311411112321162211311 +2321115111223212222141123231411113152212211 +2312111434112112132123521211235212112221211 +3212111423221212141111215142211123343121211 +4112115422111111241152211114211152323211211 +4111211122143311331131231151162111322311211 +4111121142512111322113141412413311222311121 +3211126131111311321232112314451212112221121 +3121121231213411312213411511541131112131121 +3112125221114112212221423113111116152122121 +3112214123211313112221214115322131142122211 +3111312422114113111312221531111232612121311 +3111224232112212211312131315231131422121221 +3111135112223111311331223411512311132121131 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.png new file mode 100644 index 0000000..e6a3e6e Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.properties new file mode 100755 index 0000000..8b139ee --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-utf-8.properties @@ -0,0 +1,2 @@ +mode=MICRO +content=12üƒœ˜Ë±‹3asdf23456789012asdf89012asdf89asdfaf2 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.codewords new file mode 100755 index 0000000..6dd72b5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.codewords @@ -0,0 +1,11 @@ +221311142322123221111 +311311611111334121111 +312211111222534211111 +222211211511243311111 +213211421231312411111 +214111114311152321111 +223111321412312312111 +313111412132223212111 +322111245111124112111 +412111221222334111211 +421111211231164111121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.png new file mode 100644 index 0000000..af7cb2e Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.properties new file mode 100755 index 0000000..8ae7d73 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-01.properties @@ -0,0 +1,16 @@ +mode=MICRO +dataColumns=1 +content=ABCDEF + +mode=MICRO +rows=11 +content=ABCDEF + +mode=MICRO +dataColumns=1 +rows=11 +content=ABCDEF + +mode=MICRO +variant=1 +content=ABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.codewords new file mode 100755 index 0000000..f6b90a0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.codewords @@ -0,0 +1,14 @@ +313111116111243131111 +322111311111633221111 +412111111232344121111 +421111511312224211111 +331111311111633311111 +241111111232342411111 +232111511312222321111 +231211311351122312111 +321211232215113212111 +411211215211414112111 +411121125322114111211 +411112222411144111121 +321112121344113211121 +312112163112213121121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.png new file mode 100644 index 0000000..f5060c3 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.properties new file mode 100755 index 0000000..7dd4840 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-02.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=1 +content=ABCDEFABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.codewords new file mode 100755 index 0000000..abc7f2e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.codewords @@ -0,0 +1,17 @@ +222112235212112221121 +213112411111442131121 +212212211161412122121 +212221111513412122211 +212131411111442121311 +212122211161412121221 +212113111513412121131 +211213411111442112131 +211123211161412111231 +211132111513412111321 +211141111133342111411 +211231411213142112311 +211222114132322112221 +211312221611222113121 +211321121251232113211 +211411316111132114111 +212311223125112123111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.png new file mode 100644 index 0000000..cf1e179 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.properties new file mode 100755 index 0000000..a646f44 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-03.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=1 +content=ABCDEFABCDEFABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.codewords new file mode 100755 index 0000000..63e2297 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.codewords @@ -0,0 +1,20 @@ +411112142322124111121 +321112611111333211121 +312112111222533121121 +311212211511243112121 +311221611111333112211 +311131111222533111311 +311122211511243111221 +311113611111333111131 +221113111222532211131 +221122211511242211221 +221131611111332211311 +221221111222532212211 +222121242313112221211 +312121212241143121211 +321121223213313211211 +231121424121212311211 +231112331231222311121 +222112115331212221121 +213112132121162131121 +212212141251212122121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.png new file mode 100644 index 0000000..c468ea0 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.properties new file mode 100755 index 0000000..d0e533a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-04.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=1 +content=ABCDEFABCDEFABCDEFABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.codewords new file mode 100755 index 0000000..34ed307 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.codewords @@ -0,0 +1,24 @@ +322111235212114112111 +412111411111444111211 +421111211161414111121 +331111111513413211121 +241111412114313121121 +232111121142153112121 +231211311111633112211 +321211111232343111311 +411211511312223111221 +411121112122443111131 +411112221312242211131 +321112611111332211221 +312112111222532211311 +311212211511242212211 +311221111452212221211 +311131112232513121211 +311122112332233211211 +311113331122142311211 +221113312211522311121 +221122314321122221121 +221131421123222131121 +221221114115132122121 +222121131213152122211 +312121141242122121311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.png new file mode 100644 index 0000000..580113a Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.properties new file mode 100755 index 0000000..743ccf4 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-05.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=1 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.codewords new file mode 100755 index 0000000..846384f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.codewords @@ -0,0 +1,28 @@ +311122142322123211211 +311113611111332311211 +221113111222532311121 +221122211511242221121 +221131111452212131121 +221221112232512122121 +222121411111442122211 +312121211161412121311 +321121111513412121221 +231121412114312121131 +231112121142152112131 +222112311111632111231 +213112111232342111321 +212212511312222111411 +212221112122442112311 +212131221312242112221 +212122611111332113121 +212113111222532113211 +211213211511242114111 +211123111452212123111 +211132124215112213111 +211141141331313113111 +211231412124123122111 +211222135222112222111 +211312121224232132111 +211321511114222141111 +211411142211332231111 +212311333112133131111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.png new file mode 100644 index 0000000..dbfe35f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.properties new file mode 100755 index 0000000..b9ecc98 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-06.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=1 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGH diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.codewords new file mode 100755 index 0000000..cb0fffb --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.codewords @@ -0,0 +1,8 @@ +22131114232212411111442213111 +31131121116141511312223113111 +31221111212244112232513122111 +22221141111144111232342222111 +21321113114214532113112132111 +21411111321621132113332141111 +22311111343113111231352231111 +31311123143112512212133131111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.png new file mode 100644 index 0000000..0593a09 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.properties new file mode 100755 index 0000000..8342fe7 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-07.properties @@ -0,0 +1,16 @@ +mode=MICRO +dataColumns=2 +content=ABCDEFGHIJABCD + +mode=MICRO +rows=8 +content=ABCDEFGHIJABCD + +mode=MICRO +dataColumns=2 +rows=8 +content=ABCDEFGHIJABCD + +mode=MICRO +variant=7 +content=ABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.codewords new file mode 100755 index 0000000..600684e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.codewords @@ -0,0 +1,11 @@ +22131114232212411111443221111 +31131121116141511312224121111 +31221111212244112232514211111 +22221141111144111232343311111 +21321151131222111452212411111 +21411111223251311111632321111 +22311111123234111433312312111 +31311121115331114111443212111 +32211113231331216222114112111 +41211112142223322111254111211 +42111141141213351321114111121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.png new file mode 100644 index 0000000..6ce5622 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.properties new file mode 100755 index 0000000..85963b5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-08.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=2 +content=ABCDEFGHIJABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.codewords new file mode 100755 index 0000000..7ae1834 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.codewords @@ -0,0 +1,14 @@ +31311111611124611111333131111 +32211111122253111513413221111 +41211141211431221312244121111 +42111161111133211161414211111 +33111111151341112122443311111 +24111122131224411111442411111 +23211121116141511312222321111 +23121111212244112232512312111 +32121141111144111232343212111 +41121151131222226111314112111 +41112111421611231261114111211 +41111242411311211241514111121 +32111221511241134112413211121 +31211233125111113133413121121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.png new file mode 100644 index 0000000..ea430d5 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.properties new file mode 100755 index 0000000..b5f2718 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-09.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=2 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.codewords new file mode 100755 index 0000000..d9c05d7 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.codewords @@ -0,0 +1,17 @@ +22211223521211311111632221121 +21311211123234211511242131121 +21221211145221121142152122121 +21222131111163111222532122211 +21213121151124412114312121311 +21212212114215611111332121221 +21211311122253111513412121131 +21121341211431221312242112131 +21112361111133211161412111231 +21113211151341112122442111321 +21114122131224411111442111411 +21123121116141511312222112311 +21122211351123115131232112221 +21131213112423211311262113121 +21132132122115112212622113211 +21141121111452113113252114111 +21231123212421132431212123111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.png new file mode 100644 index 0000000..78d4cec Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.properties new file mode 100755 index 0000000..7549948 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-10.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=2 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.codewords new file mode 100755 index 0000000..bbf4856 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.codewords @@ -0,0 +1,20 @@ +41111214232212411111444111121 +32111221116141511312223211121 +31211211212244112232513121121 +31121241111144111232343112121 +31122151131222111452213112211 +31113111223251311111633111311 +31112211123234211511243111221 +31111311145221121142153111131 +22111331111163111222532211131 +22112221151124412114312211221 +22113112114215611111332211311 +22122111122253111513412212211 +22212141211431221312242221211 +31212161111133211161413121211 +32112111151341111121553211211 +23112131131134123132142311211 +23111211134115311126122311121 +22211231621211231161122221121 +21311221241511113242222131121 +21221222133411142111612122121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.png new file mode 100644 index 0000000..5607dba Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.properties new file mode 100755 index 0000000..c17db9e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-11.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=2 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.codewords new file mode 100755 index 0000000..4fa1300 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.codewords @@ -0,0 +1,23 @@ +32211123521211311111634112111 +41211111123234211511244111211 +42111111145221121142154111121 +33111131111163111222533211121 +24111121151124412114313121121 +23211112114215611111333112121 +23121111122253111513413112211 +32121141211431221312243111311 +41121161111133211161413111221 +41112111151341112122443111131 +41111222131224411111442211131 +32111221116141511312222211221 +31211211212244112232512211311 +31121241111144111232342212211 +31122151131222111452212221211 +31113111223251311111633121211 +31112211123234112224143211211 +31111321315311331421122311211 +22111312512411212211442311121 +22112214121215321313312221121 +22113121144122113122612131121 +22122115421211222112432122121 +22212121312215211513222122211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.png new file mode 100644 index 0000000..d21de5a Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.properties new file mode 100755 index 0000000..0f77b9f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-12.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=2 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.codewords new file mode 100755 index 0000000..de35405 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.codewords @@ -0,0 +1,26 @@ +22111323521211311111632311121 +22112211123234211511242221121 +22113111145221121142152131121 +22122131111163111222532122121 +22212121151124412114312122211 +31212112114215611111332121311 +32112111122253111513412121221 +23112141211431221312242121131 +23111261111133211161412112131 +22211211151341112122442111231 +21311222131224411111442111321 +21221221116141511312222111411 +21222111212244112232512112311 +21213141111144111232342112221 +21212251131222111452212113121 +21211311223251311111632113211 +21121311123234211511242114111 +21112311145221121142152123111 +21113231111163112124422213111 +21114131513112131423213113111 +21123111231162411211163122111 +21122215311222131121532222111 +21131241422121151111162132111 +21132151221312215211412141111 +21141112512411114412132231111 +21231114151311313111163131111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.png new file mode 100644 index 0000000..c2f3d81 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.properties new file mode 100755 index 0000000..27f3f66 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-13.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=2 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.codewords new file mode 100755 index 0000000..75481f5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.codewords @@ -0,0 +1,6 @@ +2213111423221211223141111144111232342213111 +3113115113122212123111145221121142153113111 +3122112113135112213124111251121312433122111 +2222114121123313113112133133114243112222111 +2132113112423113122131324211311232232132111 +2141111441141113212112211631221262112141111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.png new file mode 100644 index 0000000..2cac563 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.properties new file mode 100755 index 0000000..2f30cd2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-14.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.codewords new file mode 100755 index 0000000..2c37a97 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.codewords @@ -0,0 +1,8 @@ +2231111423221214112141111144111232342231111 +3131115113122214121111145221121142153131111 +3221113111116314211111122253111513413221111 +4121114121143113311123112332123142224121111 +4211114112121513221143112321411414114211111 +3311112311216113131124511211114224213311111 +2411112311112612231112414212221131432411111 +2321111111542212321132113421411216112321111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.png new file mode 100644 index 0000000..7302423 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.properties new file mode 100755 index 0000000..2e65cf1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-15.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGH diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.codewords new file mode 100755 index 0000000..c868e6b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.codewords @@ -0,0 +1,10 @@ +2312112352121112411131111163111222532312111 +3212112115112411511141211431221312243212111 +4112116111113311421121116141511312224112111 +4111211121224411412111223251311111634111211 +4111121112323412312121151124214142214111121 +3211121111522412311212211361211251323211121 +3121121131223412221222112162115323113121121 +3112123131323112222112222116111512153112121 +3112213113322212132142111314321522113112211 +3111311252211312141113231331132113333111311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.png new file mode 100644 index 0000000..25696cb Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.properties new file mode 100755 index 0000000..8d67ecd --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-16.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.codewords new file mode 100755 index 0000000..d1a6982 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.codewords @@ -0,0 +1,12 @@ +3111221423221211241141111144111232343111221 +3111135113122211331111145221121142153111131 +2211133111116311322111122253111513412211131 +2211224121143111321222131224411111442211221 +2211312111614111312251131222111452212211311 +2212211122325112212231111163111222532212211 +2221214231112313112231231223211214242221211 +3121212212412313111351211511122153123121211 +3211211112134412211312441212611123123211211 +2311212411223211311321312116142133212311211 +2311122124411211221352131122331222132311121 +2221123361111111222222421123122132512221121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.png new file mode 100644 index 0000000..6ade8cf Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.properties new file mode 100755 index 0000000..c5da84c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-17.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.codewords new file mode 100755 index 0000000..8c93351 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.codewords @@ -0,0 +1,15 @@ +2131121423221211231241111144111232342131121 +2122125113122211232111145221121142152122121 +2122213111116311142111122253111513412122211 +2121314121143111133122131224411111442121311 +2121222111614111132251131222111452212121221 +2121131122325111123231111163111222532121131 +2112132115112411122341211431221312242112131 +2111236111113311113321116141511312222111231 +2111323261111211112442124211214311232111321 +2111413133222111121411142323113233132111411 +2112312123431111211424123113124212412112311 +2112222142223112111411451212123421312112221 +2113122231251112112321122234221114242113121 +2113213116221112113221153212441112132113211 +2114113131124211213211212343142312312114111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.png new file mode 100644 index 0000000..2c6ea3c Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.properties new file mode 100755 index 0000000..00d0274 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-18.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.codewords new file mode 100755 index 0000000..63478c5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.codewords @@ -0,0 +1,20 @@ +2213111423221211421141111144111232343211211 +3113115113122211412111145221121142152311211 +3122113111116312312111122253111513412311121 +2222114121143112311222131224411111442221121 +2132112111614112221251131222111452212131121 +2141111122325112222131111163111222532122121 +2231112115112412132141211431221312242122211 +3131116111113312141121116141511312222121311 +3221111121224411241111223251311111632121221 +4121111112323411331121151124412114312121131 +4211111211421511322161111133211161412112131 +3311111115134111321214312231121421512111231 +2411113312123211312221131423132124132111321 +2321114214131112212241232212112253122111411 +2312111113215313112221121451221113522112311 +3212112142331113111312112325241112242112221 +4112111212451112211336112112321126112113121 +4111216112221211311311122154232113412113211 +4111121141411411221322223231413311222114111 +3211122313321211222233112115121154212123111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.png new file mode 100644 index 0000000..2348999 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.properties new file mode 100755 index 0000000..2d5eb6f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-19.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.codewords new file mode 100755 index 0000000..a3f544d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.codewords @@ -0,0 +1,26 @@ +2213111423221214211141111144111232344112111 +3113115113122213311111145221121142154111211 +3122113111116313221111122253111513414111121 +2222114121143113131122131224411111443211121 +2132112111614112231151131222111452213121121 +2141111122325112321131111163111222533112121 +2231112115112412411141211431221312243112211 +3131116111113311511121116141511312223111311 +3221111121224411421111223251311111633111221 +4121111112323411412121151124412114313111131 +4211111211421512312161111133211161412211131 +3311111115134112311211212244112232512211221 +2411114111114412221211123234211511242211311 +2321111114522112222112114215611111332212211 +2312111112225312132111151341112122442221211 +3212112213122412141123132132424113113121211 +4112112331511111241111211164522122213211211 +4111211313114311331122121153213113332311211 +4111122111152411322131321214411211432311121 +3211123115221211321242313211512222212221121 +3121122233123111312221121451223311322131121 +3112121514212112212212232115333211132122121 +3112216311113113112253121122112253122122211 +3111311221234213111311532311142121422121311 +3111223116113112211323232221311512312121221 +3111135214112111311324133112311131252121131 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.png new file mode 100644 index 0000000..b0ec757 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.properties new file mode 100755 index 0000000..de7515a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-20.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.codewords new file mode 100755 index 0000000..ae08d50 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.codewords @@ -0,0 +1,32 @@ +3121122352121111312231111163111222532131121 +3112122115112412212241211431221312242122121 +3112216111113313112221116141511312222122211 +3111311121224413111311223251311111632121311 +3111221112323412211321151124412114312121221 +3111131211421511311361111133211161412121131 +2211131115134111221311212244112232512112131 +2211224111114411222211123234211511242111231 +2211311114522111231212114215611111332111321 +2212211112225311232111151341112122442111411 +2221212213122411142141111144111232342112311 +3121215113122211133111145221121142152112221 +3211213111116311132211122253111513412113121 +2311214121143111123222131224411111442113211 +2311122111614111122351131222111452212114111 +2221121122325111113331111163111222532123111 +2131122115112411112441211431221312242213111 +2122126111113311121421116141511312223113111 +2122211121224411211411223251311111633122111 +2121311112323412111415112421211431412222111 +2121221162213112112351221213412123132132111 +2121131121416112113212341222134114122141111 +2112132513122111213211222414122331232231111 +2111233611211211214144111114111341153131111 +2111321154211211223111333141121412423221111 +2111411513131212123114133131121313154121111 +2112311231125212213143111214142111614211111 +2112223121135113113111111543115232213311111 +2113123321213213122142111143223323112411111 +2113214412131113212123123213115113322321111 +2114111431122314112122431122123321322312111 +2123112111314414121122231412221212253212111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.png new file mode 100644 index 0000000..6aef18f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.properties new file mode 100755 index 0000000..9267baa --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-21.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.codewords new file mode 100755 index 0000000..16326e0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.codewords @@ -0,0 +1,38 @@ +2312112352121113112231111163111222532112311 +3212112115112413111341211431221312242112221 +4112116111113312211321116141511312222113121 +4111211121224411311311223251311111632113211 +4111121112323411221321151124412114312114111 +3211121211421511222261111133211161412123111 +3121121115134111231211212244112232512213111 +3112124111114411232111123234211511243113111 +3112211114522111142112114215611111333122111 +3111311112225311133111151341112122442222111 +3111222213122411132241111144111232342132111 +3111135113122211123211145221121142152141111 +2211133111116311122311122253111513412231111 +2211224121143111113322131224411111443131111 +2211312111614111112451131222111452213221111 +2212211122325111121431111163111222534121111 +2221212115112411211441211431221312244211111 +3121216111113312111421116141511312223311111 +3211211121224412112311223251311111632411111 +2311211112323412113221151124412114312321111 +2311121211421511213261111133211161412312111 +2221121115134111214111212244112232513212111 +2131124111114411223111123234211511244112111 +2122121114522112123121224213211335114111211 +2122212411115212213113222142214412214111121 +2121314221133113113111143331222414113211121 +2121223211261113122143212212131161313121121 +2121132232133113212111542112144211133112121 +2112131313161114112112112226222512123112211 +2111233112322314121112311252111353213111311 +2111322353111114211112131441112223423111221 +2111411214121513311134121132222322223111131 +2112311111532313221142132122321124132211131 +2112222121154113131111222144135321112211221 +2113121332412112231121332312112331242211311 +2113211142124212321124114221431115112212211 +2114111244111312411121412232111313432221211 +2123112111142511511141431211342311213121211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.png new file mode 100644 index 0000000..3b17df8 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.properties new file mode 100755 index 0000000..8a90341 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-22.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGH diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.codewords new file mode 100755 index 0000000..15d9667 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.codewords @@ -0,0 +1,44 @@ +2213111423221211241141111144111232342113121 +3113115113122211331111145221121142152113211 +3122113111116311322111122253111513412114111 +2222114121143111321222131224411111442123111 +2132112111614111312251131222111452212213111 +2141111122325112212231111163111222533113111 +2231112115112413112241211431221312243122111 +3131116111113313111321116141511312222222111 +3221111121224412211311223251311111632132111 +4121111112323411311321151124412114312141111 +4211111211421511221361111133211161412231111 +3311111115134111222211212244112232513131111 +2411114111114411231211123234211511243221111 +2321111114522111232112114215611111334121111 +2312111112225311142111151341112122444211111 +3212112213122411133141111144111232343311111 +4112115113122211132211145221121142152411111 +4111213111116311123211122253111513412321111 +4111124121143111122322131224411111442312111 +3211122111614111113351131222111452213212111 +3121121122325111112431111163111222534112111 +3112122115112411121441211431221312244111211 +3112216111113311211421116141511312224111121 +3111311121224412111411223251311111633211121 +3111221112323412112321151124412114313121121 +3111131211421512113261111133211161413112121 +2211131115134111213211212244112232513112211 +2211224111114411214122123241321211343111311 +2211314215121111223161321211135112313111221 +2212211232233112123111352131225212123111131 +2221213222142112213141311421313313122211131 +3121211311451113113121134222611311312211221 +3211211225123113122131421132114331312211311 +2311213121132413212132211521321311332212211 +2311123111433114112141141411213153112221211 +2221125213311114121113111145132321413121211 +2131122324121214211113141511332312213211211 +2122126232111113311143111313321331222311211 +2122211142323113221121312242113513212311121 +2121312212314213131114212313512112412221121 +2121225213112212231131133123411224212131121 +2121132311115312321115311123124211152122121 +2112133321132212411151511211421121512122211 +2111231352113111511152111124123212512121311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.png new file mode 100644 index 0000000..8d82ebc Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.properties new file mode 100755 index 0000000..07f777f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-23.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=3 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.codewords new file mode 100755 index 0000000..c44f734 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.codewords @@ -0,0 +1,4 @@ +211231116111246111113312312121116141511312222112131 +211222112122441122325112311231111163111222532111231 +211312131322231322312312221212212216231314212111321 +211321331331212111533112222123143112512212132111411 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.png new file mode 100644 index 0000000..a5c360b Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.properties new file mode 100755 index 0000000..cdfdb08 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-24.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.codewords new file mode 100755 index 0000000..bcbb88f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.codewords @@ -0,0 +1,6 @@ +221311142322124111114411223111123234211511242213111 +311311111452211211421512123161111133211161413113111 +312211111513411121224412213111223251311111633122111 +222211122324123321213213113112122423131241412222111 +213211114113422322411213122112611222122112622132111 +214111234211221434112113212123411123621122122141111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.png new file mode 100644 index 0000000..ad50566 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.properties new file mode 100755 index 0000000..f5b81f6 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-25.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJAB diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.codewords new file mode 100755 index 0000000..216395b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.codewords @@ -0,0 +1,8 @@ +223111142322124111114414112111123234211511242231111 +313111111452211211421514121161111133211161413131111 +322111111513411121224414211111223251311111633221111 +412111111232342115112413311141211431221312244121111 +421111611111332111614113221111511332125113314211111 +331111151211422112135213131121421223142112333311111 +241111321312323115123112231123332211122224132411111 +232111123212511161213212321111145122211133152321111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.png new file mode 100644 index 0000000..4f95b03 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.properties new file mode 100755 index 0000000..27a35b7 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-26.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.codewords new file mode 100755 index 0000000..902c6f2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.codewords @@ -0,0 +1,10 @@ +231211235212113111116312411111122253111513412312111 +321211412114312213122411511141111144111232343212111 +411211511312221114522111421112114215611111334112111 +411121111222531115134111412111212244112232514111211 +411112411111441112323412312121151124412114314111121 +321112121142156111113312311221116141511312223211121 +312112412151121151251112221222311233116331113121121 +311212311132512221141412222111215142333311123112121 +311221322231134215121112132154111122412224113112211 +311131114214132531113112141122231241116231123111311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.png new file mode 100644 index 0000000..ca6a625 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.properties new file mode 100755 index 0000000..8de0a13 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-27.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.codewords new file mode 100755 index 0000000..585e723 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.codewords @@ -0,0 +1,12 @@ +311122142322124111114411241111123234211511243111221 +311113111452211211421511331161111133211161413111131 +221113111513411121224411322111223251311111632211131 +221122111232342115112411321241211431221312242211221 +221131611111332111614111312251131222111452212211311 +221221112232513111116312212211122253111513412212211 +222121412114312213122413112241111144111232342221211 +312121511312221114522113111344111114221531123121211 +321121221121622353111112211322412231122113343211211 +231121121411162414122111311312151214421121512311211 +231112431122223111221611221322134122413133112311121 +222112113116222351121211222231131161411241132221121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.png new file mode 100644 index 0000000..f6d139e Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.properties new file mode 100755 index 0000000..9981d7e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-28.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGH diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.codewords new file mode 100755 index 0000000..2051705 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.codewords @@ -0,0 +1,15 @@ +213112142322124111114411231211123234211511242131121 +212212111452211211421511232161111133211161412122121 +212221111513411121224411142111223251311111632122211 +212131111232342115112411133141211431221312242121311 +212122611111332111614111132251131222111452212121221 +212113112232513111116311123211122253111513412121131 +211213412114312213122411122341111144111232342112131 +211123511312221114522111113312114215611111332111231 +211132111222531115134111112411212244112232512111321 +211141411111441112323411121421151124133132132111411 +211231441221212123421211211432233112212252212112311 +211222131412411241232212111412121244132411322112221 +211312411122513333111212112333121232212232322113121 +211321222243113112221512113233213113522411112113211 +211411521133111121244211213231611212124311142114111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.png new file mode 100644 index 0000000..0e945e2 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.properties new file mode 100755 index 0000000..69382ea --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-29.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.codewords new file mode 100755 index 0000000..906f0f0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.codewords @@ -0,0 +1,20 @@ +221311142322124111114411421111123234211511243211211 +311311111452211211421511412161111133211161412311211 +312211111513411121224412312111223251311111632311121 +222211111232342115112412311241211431221312242221121 +213211611111332111614112221251131222111452212131121 +214111112232513111116312222111122253111513412122121 +223111412114312213122412132141111144111232342122211 +313111511312221114522112141112114215611111332121311 +322111111222531115134111241111212244112232512121221 +412111411111441112323411331121151124412114312121131 +421111121142156111113311322121116141511312222112131 +331111112122441122325111321231111163111222532111231 +241111211511244121143111312222131224411111442111321 +232111211161415113122212212242151211221234122111411 +231211143211233111531213112211314151122421412112311 +321211213221152131211613111321112235111116152112221 +411211511121331312512212211312311252511114222113121 +411121123116213123115111311323126111411242122113211 +411112422212311316111311221321231413322313212114111 +321112411122242123431111222231152311512112142123111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.png new file mode 100644 index 0000000..74e5681 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.properties new file mode 100755 index 0000000..87be2bd --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-30.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.codewords new file mode 100755 index 0000000..4a51535 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.codewords @@ -0,0 +1,26 @@ +221311142322124111114414211111123234211511244112111 +311311111452211211421513311161111133211161414111211 +312211111513411121224413221111223251311111634111121 +222211111232342115112413131141211431221312243211121 +213211611111332111614112231151131222111452213121121 +214111112232513111116312321111122253111513413112121 +223111412114312213122412411141111144111232343112211 +313111511312221114522111511112114215611111333111311 +322111111222531115134111421111212244112232513111221 +412111411111441112323411412121151124412114313111131 +421111121142156111113312312121116141511312222211131 +331111112122441122325112311231111163111222532211221 +241111211511244121143112221222131224411111442211311 +232111211161415113122212222111145221121142152212211 +231211311111631112225312132111151341112122442221211 +321211221312244111114412141111123234211511243121211 +411211111452211211421511241161111133211161413211211 +411121111513411121224411331111223251311111632311211 +411112141131331322312311322141121242222113152311121 +321112341223112151124111321211144411121161322221121 +312112611121141132324111312263112211111512422131121 +311212411411412411213312212223131421151421212122121 +311221213251122323411113112241114141421314112122211 +311131113521312231143113111311222243122215312121311 +311122151223211314212312211312222413424211122121221 +311113431511111131135211311311125421622111222121131 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.png new file mode 100644 index 0000000..08177e4 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.properties new file mode 100755 index 0000000..b6aa007 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-31.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.codewords new file mode 100755 index 0000000..fdd6cb0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.codewords @@ -0,0 +1,32 @@ +312112235212113111116311312211122253111513412131121 +311212412114312213122412212241111144111232342122121 +311221511312221114522113112212114215611111332122211 +311131111222531115134113111311212244112232512121311 +311122411111441112323412211321151124412114312121221 +311113121142156111113311311321116141511312222121131 +221113112122441122325111221331111163111222532112131 +221122211511244121143111222222131224411111442111231 +221131211161415113122211231211145221121142152111321 +221221311111631112225311232111151341112122442111411 +222121221312244111114411142111123234211511242112311 +312121111452211211421511133161111133211161412112221 +321121111513411121224411132211223251311111632113121 +231121111232342115112411123241211431221312242113211 +231112611111332111614111122351131222111452212114111 +222112112232513111116311113311122253111513412123111 +213112412114312213122411112441111144111232342213111 +212212511312221114522111121412114215611111333113111 +212221111222531115134111211411212244112232513122111 +212131411111441112323412111421151124412114312222111 +212122121142156111113312112321116141511312222132111 +212113112122441122325112113231111163111222532141111 +211213211511244121143111213211242313151411132231111 +211123322231132131511311214131112414131351213131111 +211132313111432141152111223112242141142212323221111 +211141141124224151211212123113324121112423134121111 +211231221234124121251112213113225112441114114211111 +211222113132421114134213113121112361214411223311111 +211312133241214131142113122111113136212213152411111 +211321411232314212212313212124123113521111242321111 +211411312112521122214414112114212142121111462312111 +212311321214313222142114121114132222141414113212111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.png new file mode 100644 index 0000000..7d5c608 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.properties new file mode 100755 index 0000000..0f93657 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-32.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGH diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.codewords new file mode 100755 index 0000000..c29b0d7 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.codewords @@ -0,0 +1,38 @@ +231211235212113111116313112211122253111513412112311 +321211412114312213122413111341111144111232342112221 +411211511312221114522112211312114215611111332113121 +411121111222531115134111311311212244112232512113211 +411112411111441112323411221321151124412114312114111 +321112121142156111113311222221116141511312222123111 +312112112122441122325111231231111163111222532213111 +311212211511244121143111232122131224411111443113111 +311221211161415113122211142111145221121142153122111 +311131311111631112225311133111151341112122442222111 +311122221312244111114411132211123234211511242132111 +311113111452211211421511123261111133211161412141111 +221113111513411121224411122311223251311111632231111 +221122111232342115112411113341211431221312243131111 +221131611111332111614111112451131222111452213221111 +221221112232513111116311121411122253111513414121111 +222121412114312213122411211441111144111232344211111 +312121511312221114522112111412114215611111333311111 +321121111222531115134112112311212244112232512411111 +231121411111441112323412113221151124412114312321111 +231112121142156111113311213221116141511312222312111 +222112112122441122325111214131111163111222533212111 +213112211511244121143111223122131224411111444112111 +212212211161415113122212123111145221121142154111211 +212221311111631112225312213111151341112122444111121 +212131221312244111114413113111123234211511243211121 +212122111452211211421513122161111133211161413121121 +212113125123123131124213212112412124121113443112121 +211213311611312113223314112133311411211321343112211 +211123116111243212312314121111211164431214113111311 +211132221261122252111314211122341131111131633111221 +211141142124124141222113311121161222123232133111131 +211231111441146211123113221125133111231131152211131 +211222315311211441222113131111221136134313112211221 +211312122232231141522112231114123132221421322211311 +211321612211222211422312321142132122412221142212211 +211411111212451132243112411113231232134511112221211 +212311213221152213314111511132112242142133213121211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.png new file mode 100644 index 0000000..2fc64eb Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.properties new file mode 100755 index 0000000..13d9338 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-33.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.codewords new file mode 100755 index 0000000..835c6e8 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.codewords @@ -0,0 +1,44 @@ +221311142322124111114411241111123234211511242113121 +311311111452211211421511331161111133211161412113211 +312211111513411121224411322111223251311111632114111 +222211111232342115112411321241211431221312242123111 +213211611111332111614111312251131222111452212213111 +214111112232513111116312212211122253111513413113111 +223111412114312213122413112241111144111232343122111 +313111511312221114522113111312114215611111332222111 +322111111222531115134112211311212244112232512132111 +412111411111441112323411311321151124412114312141111 +421111121142156111113311221321116141511312222231111 +331111112122441122325111222231111163111222533131111 +241111211511244121143111231222131224411111443221111 +232111211161415113122211232111145221121142154121111 +231211311111631112225311142111151341112122444211111 +321211221312244111114411133111123234211511243311111 +411211111452211211421511132261111133211161412411111 +411121111513411121224411123211223251311111632321111 +411112111232342115112411122341211431221312242312111 +321112611111332111614111113351131222111452213212111 +312112112232513111116311112411122253111513414112111 +311212412114312213122411121441111144111232344111211 +311221511312221114522111211412114215611111334111121 +311131111222531115134112111411212244112232513211121 +311122411111441112323412112321151124412114313121121 +311113121142156111113312113221116141511312223112121 +221113112122441122325111213231111163111222533112211 +221122211511244121143111214122131224411111443111311 +221131211161415113122211223111145221121142153111221 +221221311111631112225312123111151341112122443111131 +222121221312244111114412213111123234211511242211131 +312121111452211211421513113152111223421411132211221 +321121311261211314114213122111441411612131122211311 +231121113133144321113213212115121511142131232212211 +231112112211633221341114112112125123612311212221211 +222112123312232131133314121111111246612132113121211 +213112231111261133412214211112333311112431233211211 +212212251331113411312213311114134112321422122311211 +212221212115412154111213221121441122314212312311121 +212131133231131222322313131141331221131421232221121 +212122141143121111633112231112316112331124122131121 +212113111512421521123212321111322431242112412122121 +211213112243312214213212411133112142122521132122211 +211123212142144231321111511134132211321332212121311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.png new file mode 100644 index 0000000..0b4cb27 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.properties new file mode 100755 index 0000000..3436584 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/micro-variant-34.properties @@ -0,0 +1,3 @@ +mode=MICRO +dataColumns=4 +content=ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.codewords new file mode 100755 index 0000000..85b16cf --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.codewords @@ -0,0 +1,10 @@ +8111111331111235211121363411133141111144711311121 +8111111351111323421114131131226151111224711311121 +8111111331111163212311431432112321111353711311121 +8111111311115152432111322411132311114243711311121 +8111111321113414311126124313131121113315711311121 +8111111341114114214211242442112141114411711311121 +8111111311123234121124242314112321123143711311121 +8111111341121116222143124111242221116141711311121 +8111111321122162134222212241142111122352711311121 +8111111311132522121332321322322221132332711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.png new file mode 100644 index 0000000..55aff2e Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.properties new file mode 100755 index 0000000..14c3974 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-10-rows.properties @@ -0,0 +1,3 @@ +mode=NORMAL +rows=10 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-30-columns-90-rows.error b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-30-columns-90-rows.error new file mode 100755 index 0000000..400405b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-30-columns-90-rows.error @@ -0,0 +1 @@ +Too many codewords required (2700, but max is 929) diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-30-columns-90-rows.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-30-columns-90-rows.properties new file mode 100755 index 0000000..8bbae3e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-30-columns-90-rows.properties @@ -0,0 +1,4 @@ +mode=NORMAL +dataColumns=30 +rows=90 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-31-columns.error b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-31-columns.error new file mode 100755 index 0000000..fb8779f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-31-columns.error @@ -0,0 +1 @@ +Too many columns (31) diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-31-columns.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-31-columns.properties new file mode 100755 index 0000000..b7fd6d9 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-31-columns.properties @@ -0,0 +1,3 @@ +mode=NORMAL +dataColumns=31 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.codewords new file mode 100755 index 0000000..498473f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.codewords @@ -0,0 +1,4 @@ +8111111341111144211121363411133112122423231511221122241441111243711311121 +8111111351111323141441115132211222224311311126124313131161111133711311121 +8111111331111262214211242442112122116113114113152232113321111353711311121 +8111111311114243311131523421122223221115121332321322322251116111711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.png new file mode 100644 index 0000000..f02b2dc Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.properties new file mode 100755 index 0000000..439dc05 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-5-columns.properties @@ -0,0 +1,3 @@ +mode=NORMAL +dataColumns=5 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.codewords new file mode 100755 index 0000000..4bfb3c9 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.codewords @@ -0,0 +1,6 @@ +811111134111114421113342341113311212242323151122112224141324231151111251711311121 +811111134111141451322112222243113111261243131311122211622612311161111133711311121 +811111131111134523521211235212112352121123521211235212112352121111111444711311121 +811111131111424314232212142322121423221214232212142322121423221231121135711311121 +811111132111351311611124116111241161112411611124311142321121612331113224711311121 +811111134111431212341222124215111521133112313241241111521521113341115122711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.png new file mode 100644 index 0000000..cb1245e Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.properties new file mode 100755 index 0000000..e4a3941 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-6-rows-6-columns.properties @@ -0,0 +1,4 @@ +mode=NORMAL +dataColumns=6 +rows=6 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.codewords new file mode 100755 index 0000000..0d4a9a2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.codewords @@ -0,0 +1,7 @@ +8111111351111152211121363411133141111144711311121 +8111111351111125421114131131226141111216711311121 +8111111331111163212311431432112321111155711311121 +8111111321114251432111322411132311114243711311121 +8111111321113216311126124313131141113232711311121 +8111111341114114214211242442112151113411711311121 +8111111331123151322511212321131421123143711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.png new file mode 100644 index 0000000..260a6a0 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.properties new file mode 100755 index 0000000..14c709a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-0.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=0 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.codewords new file mode 100755 index 0000000..9fd5987 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.codewords @@ -0,0 +1,8 @@ +8111111351111152211121363411133141111144711311121 +8111111361111232421114131131226141111216711311121 +8111111331111163212311431432112331111262711311121 +8111111321114251432111322411132311114243711311121 +8111111331113323311126124313131141113232711311121 +8111111341114114214211242442112151114221711311121 +8111111331123151151131322233231121123143711311121 +8111111311116232542111121112542111116133711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.png new file mode 100644 index 0000000..52aa836 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.properties new file mode 100755 index 0000000..17e8082 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-1.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=1 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.codewords new file mode 100755 index 0000000..7f552e2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.codewords @@ -0,0 +1,7 @@ +811111135111115231112144341113311212242351111152711311121 +811111135111132311312261221151321414411141111216711311121 +811111131111124611451311223411316111322121111353711311121 +811111132111425121333221322412212332211321114251711311121 +811111132111341411611124331141313511231141113232711311121 +811111135111412212232241133114222224114141114411711311121 +811111133112315123112332133142212331241131123151711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.png new file mode 100644 index 0000000..3e20ce2 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.properties new file mode 100755 index 0000000..80c2ab1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-2.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=2 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.codewords new file mode 100755 index 0000000..5fc1060 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.codewords @@ -0,0 +1,10 @@ +811111133111123541112152341113311212242351111152711311121 +811111135111142211312261221151321414411151111224711311121 +811111131111124611451311223411316111322121111452711311121 +811111131111515221333221322412212332211321114251711311121 +811111133111352111611124116111243121341221113315711311121 +811111135111412211211533314111336311211231115213711311121 +811111131112323415121511131423212414122131123151711311121 +811111134112121511124116333141111314421121116141711311121 +811111136112221211411216121211451211134461123121711311121 +811111131113252223212421111214163115123111132423711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.png new file mode 100644 index 0000000..fc614e6 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.properties new file mode 100755 index 0000000..754178a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-3.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=3 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.codewords new file mode 100755 index 0000000..a2f9d85 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.codewords @@ -0,0 +1,11 @@ +81111113311112352111213634111331121224232315112231111235711311121 +81111113411121252211513214144111513221122222431151111224711311121 +81111113211112546111322122331231214211242442112121112163711311121 +81111113111151522115132233121331112321161324231111115152711311121 +81111113411141411411411413154111522213111122521321113315711311121 +81111113411142132142132211332331112213341332142121116114711311121 +81111113111232341412313211323214211214241225231111123234711311121 +81111113511213223411231244222111521411211152214121116141711311121 +81111113111222532311125224211241531231111122214451123311711311121 +81111113111325221311252211142521331212324142121211132522711311121 +81111113211241243121422213154111221144212311331321123215711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.png new file mode 100644 index 0000000..0ce9078 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.properties new file mode 100755 index 0000000..bb5ee89 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-4.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=4 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.codewords new file mode 100755 index 0000000..45cbc12 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.codewords @@ -0,0 +1,16 @@ +8111111351111251311122433411133112122423231511221122241441111243711311121 +8111111361112141141441115132211222224311311126124313131141111315711311121 +8111111331111262214211242442112123521211235212112352121111112254711311121 +8111111331121135142322124132132113124141151314113314113151116111711311121 +8111111321114224521312211222612124214211112111642212351141113331711311121 +8111111351114221144114112321134121136211412111611324123121116213711311121 +8111111311123333132221152231251111313413331113321224241121123242711311121 +8111111351121421211442213313211311124413121451211123116211116331711311121 +8111111321122261421241121423113221331133612231111122214451124121711311121 +8111111321133142211215232215112315131213312512214232122111133134711311121 +8111111311124215332231121131135222163111131151233414211141123231711311121 +8111111311131541113124323312511111132351132123411333122241134211711311121 +8111111321151124412112331211515121231314423111232114142211151116711311121 +8111111331132313132161214221221351131123331141312222411351131222711311121 +8111111311151242611123124112411323511311112132523112116212111344711311121 +8111111322112135313215112421151111142521132421133411214112111515711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.png new file mode 100644 index 0000000..2cc69e8 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.properties new file mode 100755 index 0000000..1b760b0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-5.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=5 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.codewords new file mode 100755 index 0000000..d7c9a49 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.codewords @@ -0,0 +1,20 @@ +81111113211113262111213634111331121224232315112211222414132423114321113221111326711311121 +81111113311123152222431131112612431313111222116226123111422322115122111451111323711311121 +81111113211113531312144122411322112215321145212115331121241211511223214221112361711311121 +81111113411211431421211522111424232215111112424214113331332114213322213141121143711311121 +81111113311143312611321122325111112262214321211314215211221152311511421221113414711311121 +81111113411144111151231352133111521131132541112113432121145121121542121121121154711311121 +81111113211233413112224215112223332411211514111321141521314122131114333121123341711311121 +81111113411221242512321131132412124112421222511342111512412132221113613141121116711311121 +81111113111223521232142211132153224113221341111511111246131412412211611341125121711311121 +81111113111332333112234113151114151113141121312641221232211121361211252311133233711311121 +81111113211243226231111232112215251131134212212322325111522211134112313221123314711311121 +81111113111321535213311151214112211113531123153132621111213122421422123221136211711311121 +81111113311511321413222251511211112332233113214223141222112424122142321231151132711311121 +81111113211331153114312221153311514131111211542141131115412321135115112141131313711311121 +81111113111521511222143212142151152211321334112214512112115133213211531112111542711311121 +81111113321121432222323121123341512112411232321342321221131131341312131532112143711311121 +81111113411421221113532144212211121253214113121461412111422223111512411211134511711311121 +81111113521141216113211214311223123322311231314221311135114311153131134141211161711311121 +81111113312114233114123242111341413213211316131111132324333121222122242231211423711311121 +81111113211533113111352132213114211241241122612232123321112261225214112111145122711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.png new file mode 100644 index 0000000..bb2a8a7 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.properties new file mode 100755 index 0000000..45701ab --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-6.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=6 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.codewords new file mode 100755 index 0000000..6bd2dea --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.codewords @@ -0,0 +1,30 @@ +811111131111151641112152341113311212242323151122112224141324231143211132241113233111325121111425711311121 +811111134111242243131311122211622612311111611124116111244212313112531131412221143323311151111422711311121 +811111131111144412111443115322121341232122126112114232311112124512512411235311116111322161113122711311121 +811111133112123423322113121521232411233124211214214134113212143121413411322211241112161421121226711311121 +811111132111452131143122411231324122231231132115311333211321511311611223342231114212313131113521711311121 +811111134111512215412121331111611131253133115112122411331312114411341223631121122231133231121261711311121 +811111131112424224112331212216121123322323312411222311151314151121123143141422212121212621124151711311121 +811111135112223153312111611113311154113112226121513321115112223141112521521122314111414141121215711311121 +811111135112311323116211213212332114115223126111131111451121153313312133144411112122225131126121711311121 +811111131113414221333221112424122311233222122134133133122114314114112125121221264123113211133332711311121 +811111131112512412611321211236113112322342323111161241113123412131142114111345111133611131123421711311121 +811111136113221141134211421241121211316213111343111122542161221262213111223411312311135121141251711311121 +811111133115123133131132311132512332231121423113114151221124312321141521322411223231121421151223711311121 +811111133113322262311211411331313215221142111215211531131311421453211311311126121241114341131511711311121 +811111131116124123431121114412131312124312211334223411312151121431111163112212353121115362112212711311121 +811111132211223424241211233222123225112111252411413113222112243213114241221131431112141612112226711311121 +811111133114241152121123512313114312212212225113121352211121511511116133211233144121241211135222711311121 +811111134211431111332232252111411351211311613212111215424211411321126113122114331151211511211335711311121 +811111133121152221111425221121351115111621131126113332132121323331221323331113322332221221211514711311121 +811111131115431161221122421421212161113232113223341322112151124123113214221143226112113251151121711311121 +811111132121225211312333122214322142122352123112113411241211154213231331114413125113321111213252711311121 +811111131121342325131221313111162115142113142321131224221223241222151321321214312231221412122423711311121 +811111133211261135122112313143114512121133123221221331142113321415134111221235113214221252111421711311121 +811111133212521114121143113114241322224151114122611124112332113212111146111313432342112211221631711311121 +811111132213122413242212331111343132121421423311341212313423112151211241132222141211252321222125711311121 +811111131211613221416111114221514111313341333111341223111112542122113314332131132161113212114215711311121 +811111131213225162132111314112321531132114231132511332111211124533111161124411131133223221231341711311121 +811111131213333133221222323112142211142414151113331311321221231511114243324123112214213211224232711311121 +811111133121311562121131311122164211323141122124113112531231216123113214341221133311221441212115711311121 +811111132124124111412125233111331131152332115212134113133121621114311223145122111242212313111145711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.png new file mode 100644 index 0000000..4c674c7 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.properties new file mode 100755 index 0000000..40bf5d6 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-7.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=7 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.codewords new file mode 100755 index 0000000..15bd547 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.codewords @@ -0,0 +1,41 @@ +81111113311121441111313634111331121224232315112211222414132423114321113224111323311132512133322132241221233221131423221221112136711311121 +81111113411125211161112411611124116111241161112411611124116111241161112411611124442221112115311312215312442121121121116441112125711311121 +81111113111121553161121211414141331251111113225221141152215412113112126112523121143122311211326161223111113122341126123151113213711311121 +81111113111214161212222523212421211511242222141314222411112222161222342123212223121512144122133113313312123132143332131131121333711311121 +81111113211151331112451212412151221342211421116111412251131351214112242111311352421111165212132122124123121451211134115141114141711311121 +81111113311154115111331211421116111421521113215312613112122122431313215116121141111411442232123221231242141312413241113221121352711311121 +81111113411311422324131114151113121331333231131314112323221422311114222413212413142131232132251133112142131412143231151131131134711311121 +81111113411223225121131353121221131341135312122112312161114411414422211111311451411414111161112441252111422221131262112251121322711311121 +81111113111232613251112223111252254111212111621312242141121115421451221111433131123111262212621111141441221312512232113321126311711311121 +81111113111412161411121622221215314122132222141323211314424112122114142242111143512112411125221341221232222422212323222141141141711311121 +81111113111252231241114322113116111542121211512412145121221144213315211131114133361121123211221541142122241133121111613321124124711311121 +81111113111331611422133132421131133112241112215422441121134122221132223311151143113132423111531213232141144113121232213311141441711311121 +81111113211514211214212413232213212115141132422215113231121411163221152131141133111131361112323411151116132232221423211311151413711311121 +81111113311333211115411312114314313242114213121332114231111351234311151114411141442221112131116221125132411112166131111331132214711311121 +81111113121112451321133311331422223311323311511212321224411144113114511131216112116231121241161122311134231111532132143122112261711311121 +81111113121124244141111432422112112331241212212632321411132132231115141331311314333312111115232212132125231411231211151522112333711311121 +81111113311431223412312151312311121442122211523141222213421111165322111232122511421212143111322411411243331521114121211551141122711311121 +81111113321153114112512112221234114231321143131315211331123115223411511111633111152311311432213122311233112112362162221131211351711311121 +81111113212121261413212323131223232113142122161211113235223125113333121132131331113331142122141412224231242211141223312342121142711311121 +81111113411612114115131112231161211532125122141152141121113111541321612111631122511321311222521223124221111143151113512341152121711311121 +81111113112124421441121311432222335111211312124314512211221162122353111132421131116132121211326114212241115141311422214111213351711311121 +81111113121232331321241314122124121134323131242111243321312211252512213112314222222115131133422121241511122232232321242111214134711311121 +81111113321131243111342252211213332132121212441232233112521221311161122326123111411412131212522241212412511122321142215142112124711311121 +81111113112211364122511122126211124411132351212121211343211261133222115132321141221111542341122221116312224114211163311122131152711311121 +81111113312222324131132222112333341211323221112514112422111611151215111524231113111333322222161124232121112432221321211612131315711311121 +81111113512111151151123321135131331131231112542111531132124211424211141311521133231131154321221214311151311424113315211122114322711311121 +81111113421341111423123142114311142213311212215313422122243211312142122332121161123311241621113231331141163211211223214211231531711311121 +81111113312311242112223423151122311322413141221342412121151215113125122122123241212314131312242251211241122222151323221321231116711311121 +81111113421222223114211413411241322133121152123242212114451311113132421131122611111245124323211121123413412421122411311451212222711311121 +81111113112421424311411212112451214412212112621221521312121122531113154111211236414111412111621312422222125121142431123133111161711311121 +81111113121422231241411312131612131113162214122321112136221311255121124123231114132221151324312114121116211122351142431111233124711311121 +81111113212152223114221334132112411224211213522131133321215112411122511424123311312333112512321152322111112111643111231521214313711311121 +81111113431142114311411211342231234211222124124111442122311162212351212111313143112214332132143111441411531131123213511112211433711311121 +81111113121515114142212111115152212115141412323131241321113151323125112214151113214232121513222114111513222222232311314211242412711311121 +81111113412223124111222411411243211251323114322111125322123161122141611153121221542111124212232111134313211335114212313161221221711311121 +81111113122131521441111413212341125121141421123313431113134214111332223124511211122321421132233214211134122114333161131111311226711311121 +81111113331122413116113111252411221315211221241423213231314124112412112432121431112232242411112521514112333112131514131123112233711311121 +81111113112253125421111233113123161241113611211242112322111352221111522424113312111353214212151113216121321231231253113121224213711311121 +81111113113122342112611341124311111215421131314312413132222112431522123122622111123132411143222251123113541131111341111512222242711311121 +81111113331212321113323334231121321312323211214313131116214232123211224213213124142223121221332331412213311222423323112223121224711311121 +81111113312341216121112323134121131153214322211225113113312331131113411541121512411322221131135231224221421413114111232342141311711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.png new file mode 100644 index 0000000..d0a33a1 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.properties new file mode 100755 index 0000000..f670abe --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-8.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=8 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-9.error b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-9.error new file mode 100755 index 0000000..3aba9d2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-9.error @@ -0,0 +1 @@ +ECC level must be between 0 and 8. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-9.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-9.properties new file mode 100755 index 0000000..c5bf86a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-ecc-level-9.properties @@ -0,0 +1,3 @@ +mode=NORMAL +preferredEccLevel=9 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.codewords new file mode 100755 index 0000000..7f552e2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.codewords @@ -0,0 +1,7 @@ +811111135111115231112144341113311212242351111152711311121 +811111135111132311312261221151321414411141111216711311121 +811111131111124611451311223411316111322121111353711311121 +811111132111425121333221322412212332211321114251711311121 +811111132111341411611124331141313511231141113232711311121 +811111135111412212232241133114222224114141114411711311121 +811111133112315123112332133142212331241131123151711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.png new file mode 100644 index 0000000..26c0290 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.properties new file mode 100755 index 0000000..e74dd56 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic-row-height-10.properties @@ -0,0 +1,3 @@ +mode=NORMAL +barHeight=10 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.codewords new file mode 100755 index 0000000..7f552e2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.codewords @@ -0,0 +1,7 @@ +811111135111115231112144341113311212242351111152711311121 +811111135111132311312261221151321414411141111216711311121 +811111131111124611451311223411316111322121111353711311121 +811111132111425121333221322412212332211321114251711311121 +811111132111341411611124331141313511231141113232711311121 +811111135111412212232241133114222224114141114411711311121 +811111133112315123112332133142212331241131123151711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.png new file mode 100644 index 0000000..3e20ce2 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.properties new file mode 100755 index 0000000..358dc74 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-basic.properties @@ -0,0 +1,2 @@ +mode=NORMAL +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.codewords new file mode 100755 index 0000000..8320923 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.codewords @@ -0,0 +1,11 @@ +811111133111123521113243213332213224122151111152711311121 +811111136111133122114322311621121161122351111224711311121 +811111131111124651214211113113254131115131111361711311121 +811111131111515211232314212232321414222121114251711311121 +811111133111342241151212116111243511231121113315711311121 +811111135111412222331231214211244122511131115114711311121 +811111131112323432122142111423231422211431123151711311121 +811111135112112431132511331321131123126121116141711311121 +811111136112221223521211133312222161221211122451711311121 +811111131113252232113151222414113332131111132423711311121 +811111133112332241131511131152222161113221123215711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.png new file mode 100644 index 0000000..db3bb8f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.properties new file mode 100755 index 0000000..08f5eb4 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-data-variety.properties @@ -0,0 +1,2 @@ +mode=NORMAL +content=TESTING 12345678901234567890 testing@TESTING diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.codewords new file mode 100755 index 0000000..e11d41e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.codewords @@ -0,0 +1,5 @@ +81111113411111441111232614212214121224232315112231111235711311121 +81111113611111331262122121124421211235126113113161111133711311121 +81111113211112542352121123521211235212112352121131111163711311121 +81111113111142431423221214232212142322121423221211115152711311121 +81111113311132241161112411611124522411113213322131113224711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.png new file mode 100644 index 0000000..7e700db Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.properties new file mode 100755 index 0000000..d993bc2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-1-of-3.properties @@ -0,0 +1,8 @@ +mode=NORMAL +rows=5 +dataColumns=4 +preferredEccLevel=0 +structuredAppendFileId=123 +structuredAppendPosition=1 +structuredAppendTotal=3 +content=this diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.codewords new file mode 100755 index 0000000..cba3df1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.codewords @@ -0,0 +1,5 @@ +81111113411111441111232624211214231511223111325131111235711311121 +81111113611111331262122121124421211236116113113161111133711311121 +81111113211112542352121123521211235212112352121131111163711311121 +81111113111142431423221214232212142322121423221211115152711311121 +81111113311132241161112411611124311622113112413231113224711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.png new file mode 100644 index 0000000..bc4bf46 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.properties new file mode 100755 index 0000000..c6cc5d0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-2-of-3.properties @@ -0,0 +1,8 @@ +mode=NORMAL +rows=5 +dataColumns=4 +preferredEccLevel=0 +structuredAppendFileId=123 +structuredAppendPosition=2 +structuredAppendTotal=3 +content=is a diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.codewords new file mode 100755 index 0000000..631eccc --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.codewords @@ -0,0 +1,5 @@ +81111113411111441111232614212214111611152411142231111235711311121 +81111113611111331262122121124421111241166113113161111133711311121 +81111113211112542451111223521211235212112352121131111163711311121 +81111113111142431423221214232212142322121423221211115152711311121 +81111113311132241161112411611124411115136131212131113224711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.png new file mode 100644 index 0000000..d1c4dcc Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.properties new file mode 100755 index 0000000..09c7239 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-structured-append-3-of-3.properties @@ -0,0 +1,8 @@ +mode=NORMAL +rows=5 +dataColumns=4 +preferredEccLevel=0 +structuredAppendFileId=123 +structuredAppendPosition=3 +structuredAppendTotal=3 +content=test diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-too-few-columns-and-rows.error b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-too-few-columns-and-rows.error new file mode 100755 index 0000000..eff26c5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-too-few-columns-and-rows.error @@ -0,0 +1 @@ +Too few rows (3) and columns (3) to hold codewords (20) diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-too-few-columns-and-rows.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-too-few-columns-and-rows.properties new file mode 100755 index 0000000..550513b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-too-few-columns-and-rows.properties @@ -0,0 +1,4 @@ +mode=NORMAL +dataColumns=3 +rows=3 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.codewords new file mode 100755 index 0000000..0453447 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.codewords @@ -0,0 +1,28 @@ +81111113111115162122212513313213421111433113224115141311211141521112161421122135111141441423221211111516711311121 +81111113411116125331211131122512222441112111523243232111211152322116311221143411216111322111432351111422711311121 +81111113211114522252121223512121144113121153231131121162111213441111336114431112211112542212216111112155711311121 +81111113311212342131322341311124232312132213314123332112211221352112223431142141221141511114252131121234711311121 +81111113311141331161122341112422121442121151224121611132211143232421411231132313311135213112221531113521711311121 +81111113311152132112115421116312211211541111336123521211116133112121114551113312122121442261221131115411711311121 +81111113111242421111414421131324111141441423221233311312312212242331241111143331211421332324212111124242711311121 +81111113411213144312222123143112116213215111314111611124412232211212613141112125511131412161113241121215711311121 +81111113611231212112115411113361132121431321113521321134111213445111341111121146111213441111336111123261711311121 +81111113111341421423221215122321211133421141512211223224311111363212214242421112111141441423221211134142711311121 +81111113111241164513111121115232114114414131311311431241311325112313412111621321511131411161112431123421711311121 +81111113111322521161331131131161143122311443111261113221111232614312411111522411154211121112114611133161711311121 +81111113311512311512151115113231212114153112234111132126311413311331422114232113223232211423221231151231711311121 +81111113511321314422211141113133432321114111222451111125216111324111313331114331211145211111512541131511711311121 +81111113121111461111336123521211134321211112154215221132234211222342112223421122234211222342112212111245711311121 +81111113221122341511121515111215151112151511121515111215151112151511121515111215151112151511121522112234711311121 +81111113411411143511221235113121216111325111314111115224511214211161122361111133123211524113313111135222711311121 +81111113321151132261221111113361235212112162211213111343215411121122234211232143111112462261221132115311711311121 +81111113312115222112152312132125311132512123111633222131311322412211213531422311133141221113141531211522711311121 +81111113311521134111313311611124231133134111141431233212221143222116321111621321411131331161112451151121711311121 +81111113221213511132162123411222111612412251212221131252123131421122214415331121143311221442212111212442711311121 +81111113112134231511121515141311211215231121322512323213332213212411142221113342124141135111115211213423711311121 +81111113321121166211113223113412132152121261132111114315512211145123212134223111541111221161112452111421711311121 +81111113312162111434112151214112143121321112154221121451111422511122224322612211511133121111336111221136711311121 +81111113221312242111415241111243142322121423221214232212142322121423221214232212111232343323122122131224711311121 +81111113121143143311241241323211411311153112211621315311126121314513111141151113113311524115212112114215711311121 +81111113112232511242232111231531231311512242122261223111116132123133114114221331221361113163111142134111711311121 +81111113121333311132412313151213222414111114121611232512321112343121324112233222123242213241211312133331711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.png new file mode 100644 index 0000000..5ce7edd Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.properties new file mode 100755 index 0000000..1e16bbc --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample-2.properties @@ -0,0 +1,2 @@ +mode=NORMAL +content=[)>0102V1Z1G5124026901066870001875FDEB90106681951/115.0LBN77 REDDY DRIVEMELIANABC0260610ZGI00611ZRTC TEST12Z901263303523ZN22ZN20Z0.0015631Z 9K50224199ZGIBI02840156CADPaintingNO EEI 30.37 (f) 010S630151821EXN26Zb1d7 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.codewords new file mode 100755 index 0000000..9355b96 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.codewords @@ -0,0 +1,27 @@ +8111111321111425412113321331321342111143311322411514131121114152111216142112213531111334711311121 +8111111351112133511131411161122341111612122162212211422321611132211143232152114141111414711311121 +8111111331111361121322516112312112232142211214511111336114431112511134111222113561112213711311121 +8111111321121226111414143121122514214131232113141514131111114144111243412112415151121151711311121 +8111111311114216411212155111314111611223411112161111623251211412122154112161113221113513711311121 +8111111331115114111211462252121215311321111312442153131121121154211163122112115431116122711311121 +8111111321124151111141441423221233311114213221152111334212151115151413111111414411124143711311121 +8111111341121413311221165111314111611124433131113111261223315111112361214214131161121132711311121 +8111111311122451124411133113521121231242521131131262311111113361235212111132162141124113711311121 +8111111311133332241115213321112411133134151413111111414421131126111233331111414421133241711311121 +8111111331124132116111241312421321133115122311613214221235112311311232233216211121123413711311121 +8111111321132161114116122131153122231241226122115111341111121146111213441111336141134112711311121 +8111111321151223142322121512232121113342123241221114232331111136321221424242111211151215711311121 +8111111341132222511131411161112445131111211152321231611225113212312242212123411341131412711311121 +8111111311161142125121141322224131231151114216111262311111113361235212111161331132111261711311121 +8111111312112226211322332142331112414113221122341413141214131115132131241514131142112151711311121 +8111111341141213511131411161112445131111211433124232311111611223611111331311411521135131711311121 +8111111342114212511233111231324122612211111211461252211312442121311162211243121332116121711311121 +8111111321211514111216141112161411114144142322121512313111122424214233111241411341211431711311121 +8111111331152212411133311111512552122131122352115421111231213511341231214122322141151113711311121 +8111111311212244325112211232122421541112511241212261221111113361235212111343212111212541711311121 +8111111312122423421112422233231151111152424211121111414414232212151231315121114211213324711311121 +8111111352112132432321116111113321611132511131412111321661111232116111241161112442111413711311121 +8111111331216112115223123211531123126111113212254221511131621112115224112323114131221152711311121 +8111111321222125222212151513141124213131311611312132231314232113112125141313111642131141711311121 +8111111312114413412124126221122161121231312232133112413251322211421231312112523132114132711311121 +8111111311223152114511132531113121126113143213211422133131135112214211245113321132135111711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.png new file mode 100644 index 0000000..ea785c9 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.properties new file mode 100755 index 0000000..6d6c2f1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-tracking-sample.properties @@ -0,0 +1,2 @@ +mode=NORMAL +content=[)>010212550840497954912206750221FDE5100871431121/15.00LBN30 JOHNNY DRIVENOUBURDEGAReturns Department0610ZED00611ZJamony Reits12Z1646888994415Z11867621520Z10031Z100231290331000125400079549122047534Z0235Z01 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.codewords new file mode 100755 index 0000000..01d5f69 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.codewords @@ -0,0 +1,15 @@ +81111113411112432112142423332211311132511241411331111235711311121 +81111113511115211112451221611132121244124111232361111232711311121 +81111113211112544121511211232341243111326221311161112114711311121 +81111113511161112121222512233321424121212122323211115152711311121 +81111113211141254211222341131412111444111161112431113323711311121 +81111113411142131343212112512213114125212121154131115312711311121 +81111113211232421241411322113242112332234141141111123234711311121 +81111113611212314111161211611124251132121221541111116232711311121 +81111113111222531121153311223251311111632113115351123212711311121 +81111113111331341111232621133142333114111123211611132522711311121 +81111113211236115111223241123231411113154422211131123223711311121 +81111113111314422142142113532111312161121123153151133112711311121 +81111113111511161313222333241121323111153322132111143331711311121 +81111113411321232322411221416111311331231221612241131214711311121 +81111113211511511432132121116213113113251121234332111162711311121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.png new file mode 100644 index 0000000..39fbc9c Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.properties new file mode 100755 index 0000000..2e24968 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/normal-utf-8.properties @@ -0,0 +1,2 @@ +mode=NORMAL +content=12üƒœ˜Ë±‹3asdf23456789012asdf89012asdf89asdfaf2 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.codewords new file mode 100755 index 0000000..6fcc10b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.codewords @@ -0,0 +1,7 @@ +811111135111115221112136341113311 +811111135111112542111413113122611 +811111133111116321231143143211231 +811111132111425143211132241113231 +811111132111321631112612431313111 +811111134111411421421124244211211 +811111133112315132251121232113141 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.png new file mode 100644 index 0000000..2d68807 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.properties new file mode 100755 index 0000000..180375e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-0.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=0 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.codewords new file mode 100755 index 0000000..9638ffb --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.codewords @@ -0,0 +1,8 @@ +811111135111115221112136341113311 +811111136111123242111413113122611 +811111133111116321231143143211231 +811111132111425143211132241113231 +811111133111332331112612431313111 +811111134111411421421124244211211 +811111133112315115113132223323111 +811111131111623254211112111254211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.png new file mode 100644 index 0000000..8ae69fe Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.properties new file mode 100755 index 0000000..e5e16ba --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-1.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=1 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.codewords new file mode 100755 index 0000000..a138e32 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.codewords @@ -0,0 +1,7 @@ +81111113511111523111214434111331121224231 +81111113511113231131226122115132141441111 +81111113111112461145131122341131611132211 +81111113211142512133322132241221233221131 +81111113211134141161112433114131351123111 +81111113511141221223224113311422222411411 +81111113311231512311233213314221233124111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.png new file mode 100644 index 0000000..034e3d5 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.properties new file mode 100755 index 0000000..9b6621e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-2.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=2 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.codewords new file mode 100755 index 0000000..990623f --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.codewords @@ -0,0 +1,10 @@ +81111113311112354111215234111331121224231 +81111113511114221131226122115132141441111 +81111113111112461145131122341131611132211 +81111113111151522133322132241221233221131 +81111113311135211161112411611124312134121 +81111113511141221121153331411133631121121 +81111113111232341512151113142321241412211 +81111113411212151112411633314111131442111 +81111113611222121141121612121145121113441 +81111113111325222321242111121416311512311 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.png new file mode 100644 index 0000000..a26ca8d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.properties new file mode 100755 index 0000000..330d750 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-3.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=3 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.codewords new file mode 100755 index 0000000..766397b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.codewords @@ -0,0 +1,11 @@ +8111111331111235211121363411133112122423231511221 +8111111341112125221151321414411151322112222243111 +8111111321111254611132212233123121421124244211211 +8111111311115152211513223312133111232116132423111 +8111111341114141141141141315411152221311112252131 +8111111341114213214213221133233111221334133214211 +8111111311123234141231321132321421121424122523111 +8111111351121322341123124422211152141121115221411 +8111111311122253231112522421124153123111112221441 +8111111311132522131125221114252133121232414212121 +8111111321124124312142221315411122114421231133131 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.png new file mode 100644 index 0000000..578b40d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.properties new file mode 100755 index 0000000..ee031fa --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-4.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=4 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.codewords new file mode 100755 index 0000000..a20be05 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.codewords @@ -0,0 +1,16 @@ +811111135111125131112243341113311212242323151122112224141 +811111136111214114144111513221122222431131112612431313111 +811111133111126221421124244211212352121123521211235212111 +811111133112113514232212413213211312414115131411331411311 +811111132111422452131221122261212421421111211164221235111 +811111135111422114411411232113412113621141211161132412311 +811111131112333313222115223125111131341333111332122424111 +811111135112142121144221331321131112441312145121112311621 +811111132112226142124112142311322133113361223111112221441 +811111132113314221121523221511231513121331251221423212211 +811111131112421533223112113113522216311113115123341421111 +811111131113154111312432331251111113235113212341133312221 +811111132115112441211233121151512123131442311123211414221 +811111133113231313216121422122135113112333114131222241131 +811111131115124261112312411241132351131111213252311211621 +811111132211213531321511242115111114252113242113341121411 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.png new file mode 100644 index 0000000..a6f089d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.properties new file mode 100755 index 0000000..0e4880c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-5.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=5 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.codewords new file mode 100755 index 0000000..864221a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.codewords @@ -0,0 +1,20 @@ +8111111321111326211121363411133112122423231511221122241413242311432111321 +8111111331112315222243113111261243131311122211622612311142232211512211141 +8111111321111353131214412241132211221532114521211533112124121151122321421 +8111111341121143142121152211142423221511111242421411333133211421332221311 +8111111331114331261132112232511111226221432121131421521122115231151142121 +8111111341114411115123135213311152113113254111211343212114512112154212111 +8111111321123341311222421511222333241121151411132114152131412213111433311 +8111111341122124251232113113241212411242122251134211151241213222111361311 +8111111311122352123214221113215322411322134111151111124613141241221161131 +8111111311133233311223411315111415111314112131264122123221112136121125231 +8111111321124322623111123211221525113113421221232232511152221113411231321 +8111111311132153521331115121411221111353112315313262111121312242142212321 +8111111331151132141322225151121111233223311321422314122211242412214232121 +8111111321133115311431222115331151413111121154214113111541232113511511211 +8111111311152151122214321214215115221132133411221451211211513321321153111 +8111111332112143222232312112334151211241123232134232122113113134131213151 +8111111341142122111353214421221112125321411312146141211142222311151241121 +8111111352114121611321121431122312332231123131422131113511431115313113411 +8111111331211423311412324211134141321321131613111113232433312122212224221 +8111111321153311311135213221311421124124112261223212332111226122521411211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.png new file mode 100644 index 0000000..eb1da21 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.properties new file mode 100755 index 0000000..d95ba6d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-6.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=6 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.codewords new file mode 100755 index 0000000..2d20b49 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.codewords @@ -0,0 +1,30 @@ +81111113111115164111215234111331121224232315112211222414132423114321113224111323311132511 +81111113411124224313131112221162261231111161112411611124421231311253113141222114332331111 +81111113111114441211144311532212134123212212611211423231111212451251241123531111611132211 +81111113311212342332211312152123241123312421121421413411321214312141341132221124111216141 +81111113211145213114312241123132412223123113211531133321132151131161122334223111421231311 +81111113411151221541212133111161113125313311511212241133131211441134122363112112223113321 +81111113111242422411233121221612112332232331241122231115131415112112314314142221212121261 +81111113511222315331211161111331115411311222612151332111511222314111252152112231411141411 +81111113511231132311621121321233211411522312611113111145112115331331213314441111212222511 +81111113111341422133322111242412231123322212213413313312211431411411212512122126412311321 +81111113111251241261132121123611311232234232311116124111312341213114211411134511113361111 +81111113611322114113421142124112121131621311134311112254216122126221311122341131231113511 +81111113311512313313113231113251233223112142311311415122112431232114152132241122323112141 +81111113311332226231121141133131321522114211121521153113131142145321131131112612124111431 +81111113111612412343112111441213131212431221133422341131215112143111116311221235312111531 +81111113221122342424121123322212322511211125241141311322211224321311424122113143111214161 +81111113311424115212112351231311431221221222511312135221112151151111613321123314412124121 +81111113421143111133223225211141135121131161321211121542421141132112611312211433115121151 +81111113312115222111142522112135111511162113112611333213212132333122132333111332233222121 +81111113111543116122112242142121216111323211322334132211215112412311321422114322611211321 +81111113212122521131233312221432214212235212311211341124121115421323133111441312511332111 +81111113112134232513122131311116211514211314232113122422122324122215132132121431223122141 +81111113321126113512211231314311451212113312322122133114211332141513411122123511321422121 +81111113321252111412114311311424132222415111412261112411233211321211114611131343234211221 +81111113221312241324221233111134313212142142331134121231342311215121124113222214121125231 +81111113121161322141611111422151411131334133311134122311111254212211331433213113216111321 +81111113121322516213211131411232153113211423113251133211121112453311116112441113113322321 +81111113121333313322122232311214221114241415111333131132122123151111424332412311221421321 +81111113312131156212113131112216421132314112212411311253123121612311321434122113331122141 +81111113212412411141212523311133113115233211521213411313312162111431122314512211124221231 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.png new file mode 100644 index 0000000..47bbb88 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.properties new file mode 100755 index 0000000..03f4f8b --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-7.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=7 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.codewords new file mode 100755 index 0000000..d82f42e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.codewords @@ -0,0 +1,41 @@ +8111111331112144111131363411133112122423231511221122241413242311432111322411132331113251213332213224122123322113142322121 +8111111341112521116111241161112411611124116111241161112411611124116111241161112444222111211531131221531244212112112111641 +8111111311112155316112121141414133125111111322522114115221541211311212611252312114312231121132616122311111312234112612311 +8111111311121416121222252321242121151124222214131422241111222216122234212321222312151214412213311331331212313214333213111 +8111111321115133111245121241215122134221142111611141225113135121411224211131135242111116521213212212412312145121113411511 +8111111331115411511133121142111611142152111321531261311212212243131321511612114111141144223212322123124214131241324111321 +8111111341131142232413111415111312133133323113131411232322142231111422241321241314213123213225113311214213141214323115111 +8111111341122322512113135312122113134113531212211231216111441141442221111131145141141411116111244125211142222113126211221 +8111111311123261325111222311125225411121211162131224214112111542145122111143313112311126221262111114144122131251223211331 +8111111311141216141112162222121531412213222214132321131442411212211414224211114351211241112522134122123222242221232322211 +8111111311125223124111432211311611154212121151241214512122114421331521113111413336112112321122154114212224113312111161331 +8111111311133161142213313242113113311224111221542244112113412222113222331115114311313242311153121323214114411312123221331 +8111111321151421121421241323221321211514113242221511323112141116322115213114113311113136111232341115111613223222142321131 +8111111331133321111541131211431431324211421312133211423111135123431115111441114144222111213111622112513241111216613111131 +8111111312111245132113331133142222331132331151121232122441114411311451113121611211623112124116112231113423111153213214311 +8111111312112424414111143242211211233124121221263232141113213223111514133131131433331211111523221213212523141123121115151 +8111111331143122341231215131231112144212221152314122221342111116532211123212251142121214311132241141124333152111412121151 +8111111332115311411251211222123411423132114313131521133112311522341151111163311115231131143221312231123311211236216222111 +8111111321212126141321232313122323211314212216121111323522312511333312113213133111333114212214141222423124221114122331231 +8111111341161211411513111223116121153212512214115214112111311154132161211163112251132131122252122312422111114315111351231 +8111111311212442144112131143222233511121131212431451221122116212235311113242113111613212121132611421224111514131142221411 +8111111312123233132124131412212412113432313124211124332131221125251221311231422222211513113342212124151112223223232124211 +8111111332113124311134225221121333213212121244123223311252122131116112232612311141141213121252224121241251112232114221511 +8111111311221136412251112212621112441113235121212121134321126113322211513232114122111154234112222111631222411421116331111 +8111111331222232413113222211233334121132322111251411242211161115121511152423111311133332222216112423212111243222132121161 +8111111351211115115112332113513133113123111254211153113212421142421114131152113323113115432122121431115131142411331521111 +8111111342134111142312314211431114221331121221531342212224321131214212233212116112331124162111323133114116321121122321421 +8111111331231124211222342315112231132241314122134241212115121511312512212212324121231413131224225121124112222215132322131 +8111111342122222311421141341124132213312115212324221211445131111313242113112261111124512432321112112341341242112241131141 +8111111311242142431141121211245121441221211262122152131212112253111315411121123641411141211162131242222212512114243112311 +8111111312142223124141131213161213111316221412232111213622131125512112412323111413222115132431211412111621112235114243111 +8111111321215222311422133413211241122421121352213113332121511241112251142412331131233311251232115232211111211164311123151 +8111111343114211431141121134223123421122212412411144212231116221235121211131314311221433213214311144141153113112321351111 +8111111312151511414221211111515221211514141232313124132111315132312511221415111321423212151322211411151322222223231131421 +8111111341222312411122241141124321125132311432211112532212316112214161115312122154211112421223211113431321133511421231311 +8111111312213152144111141321234112512114142112331343111313421411133222312451121112232142113223321421113412211433316113111 +8111111333112241311611311125241122131521122124142321323131412411241211243212143111223224241111252151411233311213151413111 +8111111311225312542111123311312316124111361121124211232211135222111152242411331211135321421215111321612132123123125311311 +8111111311312234211261134112431111121542113131431241313222211243152212312262211112313241114322225112311354113111134111151 +8111111333121232111332333423112132131232321121431313111621423212321122421321312414222312122133233141221331122242332311221 +8111111331234121612111232313412113115321432221122511311331233113111341154112151241132222113113523122422142141311411123231 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.png new file mode 100644 index 0000000..c800f58 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.properties new file mode 100755 index 0000000..cae02cc --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-8.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=8 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-9.error b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-9.error new file mode 100755 index 0000000..3aba9d2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-9.error @@ -0,0 +1 @@ +ECC level must be between 0 and 8. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-9.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-9.properties new file mode 100755 index 0000000..0bbb11c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic-ecc-level-9.properties @@ -0,0 +1,3 @@ +mode=TRUNCATED +preferredEccLevel=9 +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.codewords new file mode 100755 index 0000000..a138e32 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.codewords @@ -0,0 +1,7 @@ +81111113511111523111214434111331121224231 +81111113511113231131226122115132141441111 +81111113111112461145131122341131611132211 +81111113211142512133322132241221233221131 +81111113211134141161112433114131351123111 +81111113511141221223224113311422222411411 +81111113311231512311233213314221233124111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.png new file mode 100644 index 0000000..034e3d5 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.properties new file mode 100755 index 0000000..8ddc7b3 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-basic.properties @@ -0,0 +1,2 @@ +mode=TRUNCATED +content=This is just a test. diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.codewords new file mode 100755 index 0000000..02b95b2 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.codewords @@ -0,0 +1,11 @@ +81111113311112352111324321333221322412211 +81111113611113312211432231162112116112231 +81111113111112465121421111311325413111511 +81111113111151521123231421223232141422211 +81111113311134224115121211611124351123111 +81111113511141222233123121421124412251111 +81111113111232343212214211142323142221141 +81111113511211243113251133132113112312611 +81111113611222122352121113331222216122121 +81111113111325223211315122241411333213111 +81111113311233224113151113115222216111321 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.png b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.png new file mode 100644 index 0000000..a749e09 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.properties new file mode 100755 index 0000000..27036a6 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/pdf417/truncated-data-variety.properties @@ -0,0 +1,2 @@ +mode=TRUNCATED +content=TESTING 12345678901234567890 testing@TESTING diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.codewords new file mode 100755 index 0000000..60d5e70 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.codewords @@ -0,0 +1 @@ +LLLSSLLLSSLLSSLLLSSLLSLSLLSLSLLSLLSLSLLSLLSLSLLSLSLLLSLSLSSLLSSLLLL diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.png new file mode 100644 index 0000000..90f2f66 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.properties new file mode 100755 index 0000000..482a436 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/planet-basic.properties @@ -0,0 +1,2 @@ +mode=PLANET +content=336699885526 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.codewords new file mode 100755 index 0000000..6ff6604 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.codewords @@ -0,0 +1 @@ +LLLSSSSSSLLSSLSLSSLLSSLSSLSLSLSSLLSSLSSSLLSSLSSLSSLL diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.png new file mode 100644 index 0000000..883c586 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.properties new file mode 100755 index 0000000..390cba9 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-basic.properties @@ -0,0 +1,2 @@ +mode=POSTNET +content=012345678 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.codewords new file mode 100755 index 0000000..6ff6604 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.codewords @@ -0,0 +1 @@ +LLLSSSSSSLLSSLSLSSLLSSLSSLSLSLSSLLSSLSSSLLSSLSSLSSLL diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.png b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.png new file mode 100644 index 0000000..f4a9a12 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.properties new file mode 100755 index 0000000..0866492 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-module-width-2.properties @@ -0,0 +1,4 @@ +mode=POSTNET +moduleWidth=2 +humanReadableLocation=BOTTOM +content=012345678 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.codewords new file mode 100755 index 0000000..6ff6604 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.codewords @@ -0,0 +1 @@ +LLLSSSSSSLLSSLSLSSLLSSLSSLSLSLSSLLSSLSSSLLSSLSSLSSLL diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.png b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.png new file mode 100644 index 0000000..cbd4f0f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.properties new file mode 100755 index 0000000..2c0e188 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-above.properties @@ -0,0 +1,3 @@ +mode=POSTNET +humanReadableLocation=TOP +content=012345678 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.codewords new file mode 100755 index 0000000..6ff6604 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.codewords @@ -0,0 +1 @@ +LLLSSSSSSLLSSLSLSSLLSSLSSLSLSLSSLLSSLSSSLLSSLSSLSSLL diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.png b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.png new file mode 100644 index 0000000..6f0380c Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.properties new file mode 100755 index 0000000..b5bb3c4 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/postnet/postnet-readable-below.properties @@ -0,0 +1,3 @@ +mode=POSTNET +humanReadableLocation=BOTTOM +content=012345678 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.codewords new file mode 100755 index 0000000..374c6d5 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.codewords @@ -0,0 +1,21 @@ +711111117 +151322151 +1131124111311 +1131111511311 +1131142111311 +15122111151 +711111117 +0:29 +0211313213121 +312113112213 +02114211312111 +0113114111121111 +02111141141121 +0811132113 +72142212 +1511131233 +113111132112111 +1131144321 +1131112222131 +151222233 +72122211111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.png new file mode 100644 index 0000000..8a0b798 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.properties new file mode 100755 index 0000000..25f91eb --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-01.properties @@ -0,0 +1,2 @@ +preferredVersion=1 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.codewords new file mode 100755 index 0000000..006ee30 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.codewords @@ -0,0 +1,25 @@ +7121347 +15121521151 +11311212221111311 +1131112811311 +11311231211111311 +15122232151 +7111111111117 +094138 +02113114111113121 +012211223212111111 +021325221121111 +12111111421131121 +01222111233211111 +03111132621112 +1131211221211411 +01112211111112131121 +1112529121 +0812321323 +7612111132 +1511522323 +113111113111711 +11311322121421 +11311122111131131 +15143112133 +75112411111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.png new file mode 100644 index 0000000..7f95190 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.properties new file mode 100755 index 0000000..bec8317 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-02.properties @@ -0,0 +1,2 @@ +preferredVersion=2 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.codewords new file mode 100755 index 0000000..e354478 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.codewords @@ -0,0 +1,29 @@ +7214357 +15125134151 +11311211612211311 +113112111121111311311 +1131111211411311311 +15122263151 +71111111111111117 +0811331219 +02224611122114 +1124116121211122 +01224343622 +0211211241231221111 +01142941331 +114211122131121221 +1112413211112122111 +011212111111122614 +03121211511812 +0131113112132122112 +12123112114121312 +02419112211221 +012141322111631 +08121431131211 +7115151111113 +15141311211323 +1131152212631 +1131112221351113 +113111321231711 +15126313124 +75211132111112 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.png new file mode 100644 index 0000000..6943407 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.properties new file mode 100755 index 0000000..c570f77 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-03.properties @@ -0,0 +1,2 @@ +preferredVersion=3 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.codewords new file mode 100755 index 0000000..4c985ef --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.codewords @@ -0,0 +1,33 @@ +71343211137 +151321212224151 +1131122331321211311 +1131113215111411311 +1131121331122411311 +151521113213151 +711111111111111111117 +0;51211318 +0211314341121113121 +0155353111111212 +01144611131212131 +132124131111313212 +0141111221113111112121111 +31144341511113 +01111213221122113152 +011224612131121113 +2111423132112211311 +033231222211241121 +013251211212131421 +1321112112531121113 +0111211112111212241151 +0126184111111212 +1211251121231211231 +0112235411413212 +134121221111111151111 +0823222111332111 +723111121122111141 +151152114313212 +113111121111222291 +1131121841212221 +11311122112122111211131 +151352212312113 +7212221311113111111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.png new file mode 100644 index 0000000..226d847 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.properties new file mode 100755 index 0000000..00c42d0 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-04.properties @@ -0,0 +1,2 @@ +preferredVersion=4 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.codewords new file mode 100755 index 0000000..f1b4015 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.codewords @@ -0,0 +1,37 @@ +7211121221111221117 +1511339142151 +11311212211211241311311 +1131123136111511311 +11311211121221114411311 +15113332512111151 +7111111111111111111111117 +08132132211238 +04412114111113232311 +132142223134711 +311111313232511121121 +212231311311111113242 +112141211341121111121211 +04211112111212211121117 +11111151232423211131 +0324133311321: +1121232212312241421 +01122316121324224 +01121121113113333211212 +01113112132111421164 +0342321111224311231 +0513143123211111322 +01211121544141111321 +01131111142261132222 +02212232111212112131321 +13221421213323142 +062211211211111411121121 +0211131113232111113242 +2121221122311151621 +081421212623131 +7111111122164111131 +15111111461121235 +1131111112213321125211 +11311921112142331 +1131141132111222222121 +1512121212411231115 +723111341244121 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.png new file mode 100644 index 0000000..3a802ba Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.properties new file mode 100755 index 0000000..64b8035 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-05.properties @@ -0,0 +1,2 @@ +preferredVersion=5 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.codewords new file mode 100755 index 0000000..1b43c1e --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.codewords @@ -0,0 +1,41 @@ +716112145211117 +15111313436122151 +1131116111412111132111311 +11311;3113223111311 +11311332111331112511311 +15111112133111161111151 +71111111111111111111111111117 +08116311611< +0231111112222411121112323 +0221121111112311412112211122 +06211211311111121131111511 +0121273123111211421122 +11111112214211126211113111 +21221152251132111341 +11135223121521211121111 +6712513213235 +021311112117511242131 +0422112211111121214515 +02215211131311231211121111 +021215:111133235 +1113122133324115112111 +02155325122121211122 +1112262622113111143 +111112121521312211421122 +1211241113143123312211 +0112212322142151151211 +0523112313541111511 +03111241116212111421122 +1521231212112213111111131 +111524116221331115 +11113312132212231211121111 +1111211241111114313235 +1241211121231312136111 +0811112212511112111341 +73112223512111111111111 +15122121451123235 +113111331211212222731 +113111142153122311111211 +11311127222124422111 +151251113422121112122 +7321121112121311152111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.png new file mode 100644 index 0000000..0234605 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.properties new file mode 100755 index 0000000..6b96e12 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-06.properties @@ -0,0 +1,2 @@ +preferredVersion=6 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.codewords new file mode 100755 index 0000000..eef57ef --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.codewords @@ -0,0 +1,45 @@ +712111111:3142117 +151212154243111112151 +113113131113123132211211311 +1131112211392121122111311 +11311221112472143111311 +151811121311211224151 +711111111111111111111111111111117 +0=21532111131218 +021131121224722163121 +01154212311111213311111412 +0411221211211233112171131 +1132111221122121212122222212 +02114241325132141212111 +0112122212213124123111113121 +13112116211121111322132221 +01212121222215114312243 +11113321322243217141 +01122115111211111112111311111412 +24311211414111112171131 +014241224121122122222212 +127111141151321451111 +0413121213332112313323 +212111326111113322311132 +0112131434131233121323 +13731121712331711 +051123121211152512311112 +32213111113312312141161 +01221131332122134221121112 +11313212134432161111111 +021121212114121132212123111111 +1431:1112121121211111141 +0111132211111125211122112211112 +137212231331126241 +1132141111442124123123 +041132223324414113121 +0142231612321132211223 +122132113281131451111 +08121111312321312111132111 +7362311112221211111141 +1511112114332112121123212 +113111123111;43181 +1131171311326211111421 +113111121111114312414121131 +15134111312112111112443 +776122111223112111111 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.png new file mode 100644 index 0000000..55415b1 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.properties new file mode 100755 index 0000000..a16b69c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-07.properties @@ -0,0 +1,2 @@ +preferredVersion=7 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.codewords new file mode 100755 index 0000000..eb83f45 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.codewords @@ -0,0 +1,49 @@ +7212111631111165117 +1511421143123111121141151 +113115111141344311112111311 +11311225111122121422211211311 +11311212111792221411311 +1511112211211213111111121123151 +7111111111111111111111111111111111117 +081322121213211113112: +0448112162111221142311 +0211121221511121211131242111311 +111231232132211121511215121 +01241221221122113111152211132 +11111111131212222113111211234211 +23111123313111121131422111131 +01211121111411111121416316111 +41111161116131111127214 +01222123421111111311111211143111 +16111132331112113372311 +02123111622121413121111113211 +02211121111212517227142 +3313212411113111122211233111 +0412111221342312324211222 +02116342;1212112513 +02331115111313111112242332 +31111111212412111111321211111111221 +02332221341323134113131 +0283428241211251111 +02261511121321211124334 +71112124121231521111222111 +02242221371112111244222 +131111111331312212142111122221 +01221232211422112121243322 +0211322211112141111332111121321 +16151111325512482 +111111211323112264211112111121 +0311126415122112333142 +044111441212111511121121321 +02231235621415264 +01132231311211125121112112112111 +0134223113252114115124 +33211111311121662112531 +0811121221211314112423122 +71242211311141411221111211 +151134134336114332 +1131111114212261232112531 +11311321131121134111241111411 +1131121112111112212361127111 +15124822213411211111122 +7314212331162114151 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.png new file mode 100644 index 0000000..2c6545f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.properties new file mode 100755 index 0000000..3e67e39 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-08.properties @@ -0,0 +1,2 @@ +preferredVersion=8 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.codewords new file mode 100755 index 0000000..b8f04d1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.codewords @@ -0,0 +1,53 @@ +711131123111131111111341137 +151122282111323542151 +113111111123381223431211311 +113114111131512242121111111111311 +1131142111221111611113411311311 +1511312111134322211433151 +71111111111111111111111111111111111111117 +083121343325521: +02311141111132117113111141323 +12131231611114231161131231 +24121211112322171213411112111 +2311121121111112111443121212116 +3311134144211123111244131 +0232231211211124231521121151 +1111112135131311421111124411111 +0221133111341113218312125 +03125141213231222111212211141 +12253154112122115347 +1311212311111123291211212111112 +0511142114111114413211113214 +02131132312135112121221213111111 +11326211241112231122331331 +1111112112142121822122112312111 +0221153141114221411512125 +04511171:331111121531 +122312213713131117111314 +0411112512111131111611111141112111 +011123131111133113211234112314 +225112118151122121146111 +03312221722124212411221131 +1332121121211121322121211221161 +213113211131121131111121141212114 +1311131121712212132111131112221 +1223132123111918112215 +04414121142111122131111221131111 +01112211321121117111111214121215 +21112521152121121321111311122111 +03322221411221151116112314 +116321111221222221111111122111111111 +6112221113121111115214121314 +113115211241212621111312112111 +111113111121211111122511161155 +2151753311311111111222121111 +012411314212311111121232122115 +031211121613118222237111 +0821112311311312211222121314 +7531123211112311131231112111 +15131135122351111211112314 +113111123112211163121112127111 +11311111412116241124331151 +113111413131121441211211331121 +15123156131112123311216 +7415311112622221431211 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.png new file mode 100644 index 0000000..037d618 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.properties new file mode 100755 index 0000000..b51d4f1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-09.properties @@ -0,0 +1,2 @@ +preferredVersion=9 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.codewords new file mode 100755 index 0000000..6f5d702 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.codewords @@ -0,0 +1,57 @@ +711141311111123211111244227 +15132233131121233211121212151 +11311321212124211321111211312211311 +1131111211112122511331311211111211311 +113113332512721114321211311 +1512212331634132121213151 +711111111111111111111111111111111111111111117 +09213111311223411213211119 +0211312141312111633312112123121 +111213112113311311111122111342113112 +01121122211361155311112123241 +312325124112146111111211211212 +0114212111121221212111121231334111111 +14111113:1133111112131212111111111 +037112121212191331325141 +22211112415132121111121312111221112 +421157216212331331121131 +21111342215111422211131212113112 +21522212421421112311111133241 +02162233312222115111111211211212 +24212112322121321212313352111 +011221111782111211112131212111213 +2121411311121111141111133132632 +0112211112129112312112121311121321 +11212151321112823331311311111 +2124214224123222111213121116 +216111111144115223113131621 +021113512331211331112422111323 +221111514311411113212112132111221 +01121321121222634111143111131112 +216122251294211122731 +0112133174113211112221131111111212 +131131314312143211121121331111111 +61311221231111121221124311142111 +211231221117312512212131111231 +0221114111212211217211142233212 +24223143115212213112132161 +0111223211122172112121143111121212 +2111411111211312255121112231331 +221321721211111431211114321212 +041123122212132231115121123211111 +2131231121211123111212313411312111 +21123111112413111126412111211331 +231215212124212131111312111112212 +031214211213122112221113133361 +3112411124341111111112131211131212 +11124111113113311;1111113111331 +521111233132163111211112321212 +06411113132111521122113361111 +081312112133131112214121332111 +731111331131111124112132311141 +151117313313113115121213212 +1131111231334153112213<1 +11311221131353132112131214211111 +1131111111111316121125211111311211211 +1512113141211141512112221321112 +721313111412223122131411511 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.png b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.png new file mode 100644 index 0000000..ed8f325 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.properties new file mode 100755 index 0000000..087e9a7 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/qrcode/version-10.properties @@ -0,0 +1,2 @@ +preferredVersion=10 +content=ABC diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.codewords new file mode 100755 index 0000000..4f37112 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.codewords @@ -0,0 +1 @@ +1112221212214111132123111141111113121213311232112221212211191123211111222112212111411111132 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.png new file mode 100644 index 0000000..7910d59 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.properties new file mode 100755 index 0000000..470bc68 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-basic.properties @@ -0,0 +1,2 @@ +mode=UPCA +content=12345678901+1234 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.codewords new file mode 100755 index 0000000..4f37112 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.codewords @@ -0,0 +1 @@ +1112221212214111132123111141111113121213311232112221212211191123211111222112212111411111132 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.png b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.png new file mode 100644 index 0000000..7e99f7d Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.properties new file mode 100755 index 0000000..3a37f4d --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-a-font.properties @@ -0,0 +1,3 @@ +mode=UPCA +fontSize=4 +content=12345678901+1234 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.codewords new file mode 100755 index 0000000..5f8ccfe --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.codewords @@ -0,0 +1 @@ +11121221411113213214111213111111191123211111222112212111411111132 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.png b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.png new file mode 100644 index 0000000..27032bf Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.properties new file mode 100755 index 0000000..f0d5dcf --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/upc/upc-e-basic.properties @@ -0,0 +1,2 @@ +mode=UPCE +content=1234567+1234 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.codewords new file mode 100755 index 0000000..1534787 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.codewords @@ -0,0 +1 @@ +AFDADFAAAFTDDTDDTATADDFAADFDDFTAFTDTAFTFTDTATFTFAFDFDFFFFTFFFATDD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.png b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.png new file mode 100644 index 0000000..761f00b Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.properties new file mode 100755 index 0000000..f48958a --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-20.properties @@ -0,0 +1 @@ +content=12345678901234567890 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.codewords new file mode 100755 index 0000000..6b3fced --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.codewords @@ -0,0 +1 @@ +FTTDTTDTTDAFFTTAADDFFFAADAAAADTDFFAFFAATFADDFDDTTDADADDDATTTFDFDF diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.png b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.png new file mode 100644 index 0000000..0386710 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.properties new file mode 100755 index 0000000..0814523 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-25.properties @@ -0,0 +1 @@ +content=12345678901234567890-12345 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.codewords new file mode 100755 index 0000000..0448f41 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.codewords @@ -0,0 +1 @@ +TADDDDATDAAADFDDAFDFDDTFADAADAADFAFFFDAFAFTDTADADFTFFATFFATDFTATD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.png b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.png new file mode 100644 index 0000000..49c94f6 Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.properties new file mode 100755 index 0000000..f95b880 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-29.properties @@ -0,0 +1 @@ +content=12345678901234567890-123456789 diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.codewords b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.codewords new file mode 100755 index 0000000..5058a4c --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.codewords @@ -0,0 +1 @@ +AAFDFTDTADDTATTTDTDTDTTDTDDDADTFTTAFFTDFAFAAAFDFATDFTATTFDDFDAAFD diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.png b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.png new file mode 100644 index 0000000..19ed30f Binary files /dev/null and b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.png differ diff --git a/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.properties b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.properties new file mode 100755 index 0000000..52973d1 --- /dev/null +++ b/barcode/src/test/resources/org/xbib/graphics/barcode/uspsonecode/usps-one-code-length-31.properties @@ -0,0 +1 @@ +content=12345678901234567890-12345678901 diff --git a/build.gradle b/build.gradle index 79391f9..8379f09 100644 --- a/build.gradle +++ b/build.gradle @@ -1,51 +1,35 @@ - plugins { - id "io.codearte.nexus-staging" version "0.11.0" + id "de.marcphilipp.nexus-publish" version "0.4.0" + id "io.codearte.nexus-staging" version "0.21.1" } -apply plugin: 'java' -apply plugin: 'maven' -apply plugin: 'signing' -apply plugin: "io.codearte.nexus-staging" - -configurations { - wagon +wrapper { + gradleVersion = "${rootProject.property('gradle.wrapper.version')}" + distributionType = Wrapper.DistributionType.ALL } -dependencies { - testCompile "junit:junit:4.12" - wagon "org.apache.maven.wagon:wagon-ssh:3.0.0" +ext { + user = 'jprante' + name = 'graphics' + description = 'A collection of graphics libraries for Java' + inceptionYear = '2020' + url = 'https://github.com/' + user + '/' + name + scmUrl = 'https://github.com/' + user + '/' + name + scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git' + scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git' + issueManagementSystem = 'Github' + issueManagementUrl = ext.scmUrl + '/issues' + licenseName = 'The Apache License, Version 2.0' + licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' } -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +subprojects { + apply plugin: 'java-library' -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' -tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:all" + apply from: rootProject.file('gradle/ide/idea.gradle') + apply from: rootProject.file('gradle/compile/java.gradle') + apply from: rootProject.file('gradle/test/junit5.gradle') + apply from: rootProject.file('gradle/publishing/publication.gradle') } -task javadocJar(type: Jar, dependsOn: classes) { - from javadoc - into "build/tmp" - classifier 'javadoc' -} - -task sourcesJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - into "build/tmp" - classifier 'sources' -} - -artifacts { - archives javadocJar, sourcesJar -} - -if (project.hasProperty('signing.keyId')) { - signing { - sign configurations.archives - } -} - -apply from: 'gradle/ext.gradle' -apply from: 'gradle/publish.gradle' +apply from: rootProject.file('gradle/publishing/sonatype.gradle') diff --git a/chart/NOTICE.txt b/chart/NOTICE.txt new file mode 100644 index 0000000..e301852 --- /dev/null +++ b/chart/NOTICE.txt @@ -0,0 +1,6 @@ +This work is derived from XChart by Tim Molter + +http://knowm.org/open-source/xchart/ +https://github.com/timmolter/xchart + +Apache License 2.0 diff --git a/chart/src/main/java/module-info.java b/chart/src/main/java/module-info.java new file mode 100644 index 0000000..f256da4 --- /dev/null +++ b/chart/src/main/java/module-info.java @@ -0,0 +1,17 @@ +module org.xbib.graphics.chart { + exports org.xbib.graphics.chart; + exports org.xbib.graphics.chart.axis; + exports org.xbib.graphics.chart.bubble; + exports org.xbib.graphics.chart.category; + exports org.xbib.graphics.chart.formatter; + exports org.xbib.graphics.chart.io; + exports org.xbib.graphics.chart.legend; + exports org.xbib.graphics.chart.ohlc; + exports org.xbib.graphics.chart.pie; + exports org.xbib.graphics.chart.plot; + exports org.xbib.graphics.chart.series; + exports org.xbib.graphics.chart.style; + exports org.xbib.graphics.chart.theme; + exports org.xbib.graphics.chart.xy; + requires transitive java.desktop; +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/Chart.java b/chart/src/main/java/org/xbib/graphics/chart/Chart.java new file mode 100644 index 0000000..4f26cd2 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/Chart.java @@ -0,0 +1,391 @@ +package org.xbib.graphics.chart; + +import org.xbib.graphics.chart.axis.Direction; +import org.xbib.graphics.chart.axis.Axis; +import org.xbib.graphics.chart.axis.AxisPair; +import org.xbib.graphics.chart.io.BitmapFormat; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.legend.Legend; +import org.xbib.graphics.chart.plot.Plot; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; +import org.xbib.graphics.chart.io.vector.EPSGraphics2D; +import org.xbib.graphics.chart.io.vector.PDFGraphics2D; +import org.xbib.graphics.chart.io.vector.ProcessingPipeline; +import org.xbib.graphics.chart.io.vector.SVGGraphics2D; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.Format; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; + +public abstract class Chart { + + protected final ST styler; + + protected final ChartTitle chartTitle; + + protected final Map seriesMap; + + protected AxisPair axisPair; + + protected Plot plot; + + protected Legend legend; + + private int width; + + private int height; + + private String title = ""; + + private String xAxisTitle = ""; + + private String yAxisTitle = ""; + + private final Map yAxisGroupTitleMap; + + protected Chart(int width, int height, ST styler) { + this.styler = styler; + this.width = width; + this.height = height; + this.chartTitle = new ChartTitle<>(this); + this.seriesMap = new LinkedHashMap<>(); + this.yAxisGroupTitleMap = new HashMap<>(); + } + + public abstract void paint(Graphics2D g, int width, int height); + + protected void paintBackground(Graphics2D g) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, styler.getAntiAlias() + ? RenderingHints.VALUE_ANTIALIAS_ON + : RenderingHints.VALUE_ANTIALIAS_OFF); + g.setColor(styler.getChartBackgroundColor()); + Shape rect = new Rectangle2D.Double(0, 0, getWidth(), getHeight()); + g.fill(rect); + } + + public List listFromDoubleArray(double[] data) { + if (data == null) { + return null; + } + List dataNumber; + dataNumber = new ArrayList<>(); + for (double d : data) { + dataNumber.add(d); + } + return dataNumber; + } + + public List listFromFloatArray(float[] data) { + if (data == null) { + return null; + } + List dataNumber; + dataNumber = new ArrayList<>(); + for (float f : data) { + dataNumber.add((double) f); + } + return dataNumber; + } + + public List listFromIntArray(int[] data) { + if (data == null) { + return null; + } + List dataNumber; + dataNumber = new ArrayList<>(); + for (double d : data) { + dataNumber.add(d); + } + return dataNumber; + } + + public List getGeneratedData(int length) { + List generatedData = new ArrayList<>(); + for (int i = 1; i < length + 1; i++) { + generatedData.add((double) i); + } + return generatedData; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getXAxisTitle() { + return xAxisTitle; + } + + public void setXAxisTitle(String xAxisTitle) { + this.xAxisTitle = xAxisTitle; + } + + public String getyYAxisTitle() { + return yAxisTitle; + } + + public void setYAxisTitle(String yAxisTitle) { + this.yAxisTitle = yAxisTitle; + } + + public String getYAxisGroupTitle(int yAxisGroup) { + String title = yAxisGroupTitleMap.get(yAxisGroup); + if (title == null) { + return yAxisTitle; + } + return title; + } + + public void setYAxisGroupTitle(int yAxisGroup, String yAxisTitle) { + yAxisGroupTitleMap.put(yAxisGroup, yAxisTitle); + } + + public void setXAxisLabelOverrideMap(Map overrideMap) { + axisPair.getAxisLabelOverrideMap().put("X0", overrideMap); + } + + public void setYAxisLabelOverrideMap(Map overrideMap) { + axisPair.getAxisLabelOverrideMap().put("Y0", overrideMap); + } + + public void setYAxisLabelOverrideMap(Map overrideMap, int yAxisGroup) { + axisPair.getAxisLabelOverrideMap().put(("Y" + yAxisGroup), overrideMap); + } + + public Map getYAxisLabelOverrideMap(Direction direction, int yIndex) { + Map> axisLabelOverrideMap = axisPair.getAxisLabelOverrideMap(); + return axisLabelOverrideMap.get((direction.name() + yIndex)); + } + + public ChartTitle getChartTitle() { + return chartTitle; + } + + public Legend getLegend() { + return legend; + } + + public Plot getPlot() { + return plot; + } + + public Axis getXAxis() { + return axisPair.getXAxis(); + } + + public Axis getYAxis() { + return axisPair.getYAxis(); + } + + public Axis getYAxis(int yIndex) { + return axisPair.getYAxis(yIndex); + } + + public AxisPair getAxisPair() { + return axisPair; + } + + public Map getSeriesMap() { + return seriesMap; + } + + public S removeSeries(String seriesName) { + return seriesMap.remove(seriesName); + } + + public ST getStyler() { + return styler; + } + + public Format getXAxisFormat() { + return axisPair.getXAxis().getAxisTickCalculator().getAxisFormat(); + } + + public Format getYAxisFormat() { + return axisPair.getYAxis().getAxisTickCalculator().getAxisFormat(); + } + + /** + * Save chart as an image file. + * + * @param outputStream output stream + * @param bitmapFormat bitmap format + * @throws IOException if save fails + */ + public void saveBitmap(OutputStream outputStream, BitmapFormat bitmapFormat) throws IOException { + BufferedImage bufferedImage = getBufferedImage(); + ImageIO.write(bufferedImage, bitmapFormat.toString().toLowerCase(), outputStream); + outputStream.close(); + } + + /** + * Save a chart as a PNG with a custom DPI. The default DPI is 72, which is fine for displaying charts on a + * computer + * monitor, but for printing + * charts, a DPI of around 300 is much better. + * + * @param outputStream output stream + * @param dpi dot sper inch + * @throws IOException if save fails + */ + public void saveBitmapWithDPI(OutputStream outputStream, BitmapFormat bitmapFormat, int dpi) throws IOException { + double scaleFactor = dpi / 72.0; + BufferedImage bufferedImage = new BufferedImage((int) (getWidth() * scaleFactor), (int) (getHeight() * scaleFactor), BufferedImage.TYPE_INT_RGB); + Graphics2D graphics2D = bufferedImage.createGraphics(); + AffineTransform at = graphics2D.getTransform(); + at.scale(scaleFactor, scaleFactor); + graphics2D.setTransform(at); + paint(graphics2D, getWidth(), getHeight()); + Iterator writers = ImageIO.getImageWritersByFormatName(bitmapFormat.toString().toLowerCase()); + if (writers.hasNext()) { + ImageWriter writer = writers.next(); + // instantiate an ImageWriteParam object with default compression options + ImageWriteParam iwp = writer.getDefaultWriteParam(); + ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB); + IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, iwp); + if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) { + throw new IllegalArgumentException("It is not possible to set the DPI on a bitmap with " + bitmapFormat + " format!! Try another format."); + } + setDPI(metadata, dpi); + try { + writer.setOutput(outputStream); + IIOImage image = new IIOImage(bufferedImage, null, metadata); + writer.write(null, image, iwp); + } finally { + writer.dispose(); + } + } + } + + /** + * Sets the metadata. + * + * @param metadata metadata + * @param DPI dots per inch + * @throws IIOInvalidTreeException if setting fails + */ + private static void setDPI(IIOMetadata metadata, int DPI) throws IIOInvalidTreeException { + // for PNG, it's dots per millimeter + double dotsPerMilli = 1.0 * DPI / 10 / 2.54; + IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize"); + horiz.setAttribute("value", Double.toString(dotsPerMilli)); + IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize"); + vert.setAttribute("value", Double.toString(dotsPerMilli)); + IIOMetadataNode dim = new IIOMetadataNode("Dimension"); + dim.appendChild(horiz); + dim.appendChild(vert); + IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0"); + root.appendChild(dim); + metadata.mergeTree("javax_imageio_1.0", root); + } + + /** + * Save a Chart as a JPEG file + * + * @param outputStream output stream + * @param quality - a float between 0 and 1 (1 = maximum quality) + * @throws IOException if save fails + */ + public void saveJPGWithQuality(OutputStream outputStream, float quality) throws IOException { + BufferedImage bufferedImage = getBufferedImage(); + Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); + ImageWriter writer = iter.next(); + ImageWriteParam iwp = writer.getDefaultWriteParam(); + iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + iwp.setCompressionQuality(quality); + try (outputStream) { + writer.setOutput(outputStream); + IIOImage image = new IIOImage(bufferedImage, null, null); + writer.write(null, image, iwp); + writer.dispose(); + } + } + + /** + * Generates a byte[] for a given chart. + * + * @param bitmapFormat bitmap format + * @return a byte[] for a given chart + * @throws IOException if byte array fails + */ + public byte[] getBitmapBytes(BitmapFormat bitmapFormat) throws IOException { + BufferedImage bufferedImage = getBufferedImage(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(bufferedImage, bitmapFormat.toString().toLowerCase(), baos); + baos.flush(); + byte[] imageInBytes = baos.toByteArray(); + baos.close(); + return imageInBytes; + } + + public BufferedImage getBufferedImage() { + BufferedImage bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D graphics2D = bufferedImage.createGraphics(); + paint(graphics2D, getWidth(), getHeight()); + return bufferedImage; + } + + public void write(OutputStream outputStream, VectorGraphicsFormat vectorGraphicsFormat) + throws IOException { + ProcessingPipeline g = null; + switch (vectorGraphicsFormat) { + case EPS: + g = new EPSGraphics2D(0.0, 0.0, getWidth(), getHeight()); + break; + case PDF: + g = new PDFGraphics2D(0.0, 0.0, getWidth(), getHeight()); + break; + case SVG: + g = new SVGGraphics2D(0.0, 0.0, getWidth(), getHeight()); + break; + + default: + break; + } + paint(g, getWidth(), getHeight()); + if (outputStream != null) { + outputStream.write(g.getBytes()); + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ChartBuilder.java b/chart/src/main/java/org/xbib/graphics/chart/ChartBuilder.java new file mode 100644 index 0000000..3626c56 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ChartBuilder.java @@ -0,0 +1,66 @@ +package org.xbib.graphics.chart; + +import org.xbib.graphics.chart.theme.DefaultTheme; +import org.xbib.graphics.chart.theme.Theme; + +public abstract class ChartBuilder, C extends Chart> { + + private int width; + + private int height; + + private String title; + + private Theme theme; + + public ChartBuilder() { + this.width = 800; + this.height = 600; + this.title = ""; + this.theme = new DefaultTheme(); + } + + @SuppressWarnings("unchecked") + public T width(int width) { + this.width = width; + return (T) this; + } + + public int getWidth() { + return width; + } + + @SuppressWarnings("unchecked") + public T height(int height) { + this.height = height; + return (T) this; + } + + public int getHeight() { + return height; + } + + @SuppressWarnings("unchecked") + public T title(String title) { + this.title = title; + return (T) this; + } + + public String getTitle() { + return title; + } + + @SuppressWarnings("unchecked") + public T theme(Theme theme) { + if (theme != null) { + this.theme = theme; + } + return (T) this; + } + + public Theme getTheme() { + return theme; + } + + public abstract C build(); +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ChartComponent.java b/chart/src/main/java/org/xbib/graphics/chart/ChartComponent.java new file mode 100644 index 0000000..e52f790 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ChartComponent.java @@ -0,0 +1,14 @@ +package org.xbib.graphics.chart; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +/** + * A components of a chart that need to be painted. + */ +public interface ChartComponent { + + Rectangle2D getBounds(); + + void paint(Graphics2D g); +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ChartTitle.java b/chart/src/main/java/org/xbib/graphics/chart/ChartTitle.java new file mode 100644 index 0000000..2789e68 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ChartTitle.java @@ -0,0 +1,87 @@ +package org.xbib.graphics.chart; + +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + +/** + * Chart Title. + */ +public class ChartTitle implements ChartComponent { + + private final Chart chart; + private Rectangle2D bounds; + + public ChartTitle(Chart chart) { + this.chart = chart; + } + + @Override + public Rectangle2D getBounds() { + if (bounds == null) { + bounds = getBoundsHint(); + } + return bounds; + } + + @Override + public void paint(Graphics2D g) { + g.setFont(chart.getStyler().getChartTitleFont()); + if (!chart.getStyler().isChartTitleVisible() || chart.getTitle().length() == 0) { + return; + } + Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + FontRenderContext frc = g.getFontRenderContext(); + TextLayout textLayout = new TextLayout(chart.getTitle(), chart.getStyler().getChartTitleFont(), frc); + Rectangle2D textBounds = textLayout.getBounds(); + double xOffset = chart.getPlot().getBounds().getX(); + double yOffset = chart.getStyler().getChartPadding(); + if (chart.getStyler().isChartTitleBoxVisible()) { + double chartTitleBoxWidth = chart.getPlot().getBounds().getWidth(); + double chartTitleBoxHeight = textBounds.getHeight() + 2 * chart.getStyler().getChartTitlePadding(); + g.setStroke(Theme.Strokes.TITLE); + Shape rect = new Rectangle2D.Double(xOffset, yOffset, chartTitleBoxWidth, chartTitleBoxHeight); + g.setColor(chart.getStyler().getChartTitleBoxBackgroundColor()); + g.fill(rect); + g.setColor(chart.getStyler().getChartTitleBoxBorderColor()); + g.draw(rect); + } + xOffset = chart.getPlot().getBounds().getX() + (chart.getPlot().getBounds().getWidth() - textBounds.getWidth()) / 2.0; + yOffset = chart.getStyler().getChartPadding() + textBounds.getHeight() + chart.getStyler().getChartTitlePadding(); + g.setColor(chart.getStyler().getChartFontColor()); + Shape shape = textLayout.getOutline(null); + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + at.translate(xOffset, yOffset); + g.transform(at); + g.fill(shape); + g.setTransform(orig); + double width = 2 * chart.getStyler().getChartTitlePadding() + textBounds.getWidth(); + double height = 2 * chart.getStyler().getChartTitlePadding() + textBounds.getHeight(); + bounds = new Rectangle2D.Double(xOffset - chart.getStyler().getChartTitlePadding(), + yOffset - textBounds.getHeight() - chart.getStyler().getChartTitlePadding(), width, height); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint); + } + + private Rectangle2D getBoundsHint() { + if (chart.getStyler().isChartTitleVisible() && chart.getTitle().length() > 0) { + TextLayout textLayout = new TextLayout(chart.getTitle(), chart.getStyler().getChartTitleFont(), + new FontRenderContext(null, true, false)); + Rectangle2D rectangle = textLayout.getBounds(); + double width = 2 * chart.getStyler().getChartTitlePadding() + rectangle.getWidth(); + double height = 2 * chart.getStyler().getChartTitlePadding() + rectangle.getHeight(); + return new Rectangle2D.Double(Double.NaN, Double.NaN, width, height); + } else { + return new Rectangle2D.Double(); + } + } +} \ No newline at end of file diff --git a/chart/src/main/java/org/xbib/graphics/chart/Histogram.java b/chart/src/main/java/org/xbib/graphics/chart/Histogram.java new file mode 100644 index 0000000..2a4912e --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/Histogram.java @@ -0,0 +1,96 @@ +package org.xbib.graphics.chart; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Histogram { + + private final Collection originalData; + + private final int numBins; + + private final double min; + + private final double max; + + private List xAxisData; + + private List yAxisData; + + public Histogram(Collection data, int numBins) { + this.numBins = numBins; + this.originalData = data; + double tempMax = -Double.MAX_VALUE; + double tempMin = Double.MAX_VALUE; + for (Number number : data) { + double value = number.doubleValue(); + if (value > tempMax) { + tempMax = value; + } + if (value < tempMin) { + tempMin = value; + } + } + max = tempMax; + min = tempMin; + init(); + } + + public Histogram(Collection data, int numBins, double min, double max) { + this.numBins = numBins; + this.originalData = data; + this.min = min; + this.max = max; + init(); + } + + private void init() { + double[] tempYAxisData = new double[numBins]; + final double binSize = (max - min) / numBins; + for (Number anOriginalData : originalData) { + double doubleValue = (anOriginalData).doubleValue(); + int bin = (int) ((doubleValue - min) / binSize); + if (bin >= 0) { + if (doubleValue == max) { + tempYAxisData[bin - 1] += 1; + } else if (bin <= numBins && bin != numBins) { + tempYAxisData[bin] += 1; + } + } + } + yAxisData = new ArrayList<>(numBins); + for (double d : tempYAxisData) { + yAxisData.add(d); + } + xAxisData = new ArrayList<>(numBins); + for (int i = 0; i < numBins; i++) { + xAxisData.add(((i * (max - min)) / numBins + min) + binSize / 2); + } + } + + public List getxAxisData() { + return xAxisData; + } + + public List getyAxisData() { + return yAxisData; + } + + public Collection getOriginalData() { + return originalData; + } + + public int getNumBins() { + return numBins; + } + + public double getMin() { + return min; + } + + public double getMax() { + return max; + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/QuickChart.java b/chart/src/main/java/org/xbib/graphics/chart/QuickChart.java new file mode 100644 index 0000000..bc2ca8d --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/QuickChart.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.chart; + +import org.xbib.graphics.chart.theme.Theme; +import org.xbib.graphics.chart.xy.XYChart; +import org.xbib.graphics.chart.xy.XYSeries; + +import java.util.List; + +/** + * A convenience class for making Charts with one line of code. + */ +public final class QuickChart { + + private final static int WIDTH = 600; + private final static int HEIGHT = 400; + + private QuickChart() { + } + + public static XYChart getChart(String chartTitle, String xTitle, String yTitle, + String seriesName, double[] xData, double[] yData) { + double[][] yData2d = {yData}; + if (seriesName == null) { + return getChart(chartTitle, xTitle, yTitle, null, xData, yData2d); + } else { + return getChart(chartTitle, xTitle, yTitle, new String[]{seriesName}, xData, yData2d); + } + } + + public static XYChart getChart(String chartTitle, String xTitle, String yTitle, + String[] seriesNames, double[] xData, double[][] yData) { + XYChart chart = new XYChart(WIDTH, HEIGHT); + chart.setTitle(chartTitle); + chart.setXAxisTitle(xTitle); + chart.setYAxisTitle(yTitle); + for (int i = 0; i < yData.length; i++) { + XYSeries series; + if (seriesNames != null) { + series = chart.addSeries(seriesNames[i], xData, yData[i]); + } else { + chart.getStyler().setLegendVisible(false); + series = chart.addSeries(" " + i, xData, yData[i]); + } + series.setMarker(Theme.Series.NONE_MARKER); + } + return chart; + } + + public static XYChart getChart(String chartTitle, String xTitle, String yTitle, + String seriesName, List xData, List yData) { + XYChart chart = new XYChart(WIDTH, HEIGHT); + chart.setTitle(chartTitle); + chart.setXAxisTitle(xTitle); + chart.setYAxisTitle(yTitle); + XYSeries series = chart.addSeries(seriesName, xData, yData); + series.setMarker(Theme.Series.NONE_MARKER); + return chart; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/Axis.java b/chart/src/main/java/org/xbib/graphics/chart/axis/Axis.java new file mode 100644 index 0000000..cec0821 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/Axis.java @@ -0,0 +1,293 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.category.CategoryStyler; +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.AxesChartSeries; +import org.xbib.graphics.chart.series.AxesChartSeriesCategory; +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.legend.LegendPosition; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.List; +import java.util.Map; + +public class Axis implements ChartComponent { + + private final Chart chart; + private final Rectangle2D.Double bounds; + private final ST axesChartStyler; + private final AxisTitle axisTitle; + private final AxisTick axisTick; + private final Direction direction; + private final int yIndex; + private DataType dataType; + private AxisTickCalculator axisTickCalculator; + private double min; + private double max; + + public Axis(Chart chart, Direction direction, int yIndex) { + this.chart = chart; + this.axesChartStyler = chart.getStyler(); + this.direction = direction; + this.yIndex = yIndex; + bounds = new Rectangle2D.Double(); + axisTitle = new AxisTitle<>(chart, direction, direction == Direction.Y ? this : null, yIndex); + axisTick = new AxisTick<>(chart, direction, direction == Direction.Y ? this : null); + } + + @Override + public Rectangle2D getBounds() { + return bounds; + } + + public void resetMinMax() { + min = Double.MAX_VALUE; + max = -Double.MAX_VALUE; + } + + public void addMinMax(double min, double max) { + if (Double.isNaN(this.min) || min < this.min) { + this.min = min; + } + if (max > this.max) { + this.max = max; + } + } + + public void preparePaint() { + if (direction == Direction.Y) { + double xOffset = 0; + double yOffset = chart.getChartTitle().getBounds().getHeight() + axesChartStyler.getChartPadding(); + int i = 1; + double width = 60; + double height; + do { + double approximateXAxisWidth = chart.getWidth() - width + - (axesChartStyler.getLegendPosition() == LegendPosition.OutsideE + ? chart.getLegend().getBounds().getWidth() + : 0) + - 2 * axesChartStyler.getChartPadding() + - (axesChartStyler.isYAxisTicksVisible() ? (axesChartStyler.getPlotMargin()) : 0) + - (axesChartStyler.getLegendPosition() == LegendPosition.OutsideE + && axesChartStyler.isLegendVisible() + ? axesChartStyler.getChartPadding() + : 0); + + height = chart.getHeight() - yOffset + - chart.getXAxis().getXAxisHeightHint(approximateXAxisWidth) + - axesChartStyler.getPlotMargin() + - axesChartStyler.getChartPadding() + - (axesChartStyler.getLegendPosition() == LegendPosition.OutsideS + ? chart.getLegend().getBounds().getHeight() + : 0); + + width = getYAxisWidthHint(height); + } while (i-- > 0); + bounds.setRect(xOffset, yOffset, width, height); + } else { + Rectangle2D leftYAxisBounds = chart.getAxisPair().getLeftYAxisBounds(); + Rectangle2D rightYAxisBounds = chart.getAxisPair().getRightYAxisBounds(); + double maxYAxisY = Math.max(leftYAxisBounds.getY() + leftYAxisBounds.getHeight(), + rightYAxisBounds.getY() + rightYAxisBounds.getHeight()); + double xOffset = leftYAxisBounds.getWidth() + axesChartStyler.getChartPadding(); + double yOffset = maxYAxisY + axesChartStyler.getPlotMargin() + - (axesChartStyler.getLegendPosition() == LegendPosition.OutsideS + ? chart.getLegend().getBounds().getHeight() + : 0); + double legendWidth = 0; + if (axesChartStyler.getLegendPosition() == LegendPosition.OutsideE + && axesChartStyler.isLegendVisible()) { + legendWidth = chart.getLegend().getBounds().getWidth() + axesChartStyler.getChartPadding(); + } + double width = chart.getWidth() - leftYAxisBounds.getWidth() + - rightYAxisBounds.getWidth() + - 2 * axesChartStyler.getChartPadding() + - legendWidth; + double height = chart.getHeight() - maxYAxisY + - axesChartStyler.getChartPadding() + - axesChartStyler.getPlotMargin(); + bounds.setRect(xOffset, yOffset, width, height); + } + } + + @Override + public void paint(Graphics2D g) { + Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + if (direction == Direction.Y) { + boolean onRight = axesChartStyler.getYAxisGroupPosistion(yIndex) == YAxisPosition.Right; + if (onRight) { + axisTick.paint(g); + axisTitle.paint(g); + } else { + axisTitle.paint(g); + axisTick.paint(g); + } + bounds.width = (axesChartStyler.isYAxisTitleVisible() ? axisTitle.getBounds().getWidth() : 0) + + axisTick.getBounds().getWidth(); + + } else { + this.axisTickCalculator = getAxisTickCalculator(bounds.getWidth()); + axisTitle.paint(g); + axisTick.paint(g); + } + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint); + } + + private double getXAxisHeightHint(double workingSpace) { + double titleHeight = 0.0; + if (chart.getXAxisTitle() != null && + !chart.getXAxisTitle().trim().equalsIgnoreCase("") && + axesChartStyler.isXAxisTitleVisible()) { + TextLayout textLayout = new TextLayout(chart.getXAxisTitle(), + axesChartStyler.getAxisTitleFont(), new FontRenderContext(null, true, false)); + Rectangle2D rectangle = textLayout.getBounds(); + titleHeight = rectangle.getHeight() + chart.getStyler().getAxisTitlePadding(); + } + this.axisTickCalculator = getAxisTickCalculator(workingSpace); + double axisTickLabelsHeight = 0.0; + if (chart.getStyler().isXAxisTicksVisible()) { + String sampleLabel = ""; + for (int i = 0; i < axisTickCalculator.getTickLabels().size(); i++) { + if (axisTickCalculator.getTickLabels().get(i) != null && + axisTickCalculator.getTickLabels().get(i).length() > sampleLabel.length()) { + sampleLabel = axisTickCalculator.getTickLabels().get(i); + } + } + TextLayout textLayout = new TextLayout(sampleLabel.length() == 0 ? " " : sampleLabel, + axesChartStyler.getAxisTickLabelsFont(), + new FontRenderContext(null, true, false)); + AffineTransform rot = axesChartStyler.getXAxisLabelRotation() == 0 ? null : + AffineTransform.getRotateInstance(-1 * Math.toRadians(axesChartStyler.getXAxisLabelRotation())); + Shape shape = textLayout.getOutline(rot); + Rectangle2D rectangle = shape.getBounds(); + axisTickLabelsHeight = rectangle.getHeight() + axesChartStyler.getAxisTickPadding() + axesChartStyler.getAxisTickMarkLength(); + } + return titleHeight + axisTickLabelsHeight; + } + + private double getYAxisWidthHint(double workingSpace) { + double titleHeight = 0.0; + if (chart.getyYAxisTitle() != null && + !chart.getyYAxisTitle().trim().equalsIgnoreCase("") && + axesChartStyler.isYAxisTitleVisible()) { + TextLayout textLayout = new TextLayout(chart.getyYAxisTitle(), + axesChartStyler.getAxisTitleFont(), new FontRenderContext(null, true, false)); + Rectangle2D rectangle = textLayout.getBounds(); + titleHeight = rectangle.getHeight() + axesChartStyler.getAxisTitlePadding(); + } + double axisTickLabelsHeight = 0.0; + if (axesChartStyler.isYAxisTicksVisible()) { + this.axisTickCalculator = getAxisTickCalculator(workingSpace); + String sampleLabel = ""; + for (int i = 0; i < axisTickCalculator.getTickLabels().size(); i++) { + if (axisTickCalculator.getTickLabels().get(i) != null + && axisTickCalculator.getTickLabels().get(i).length() > sampleLabel.length()) { + sampleLabel = axisTickCalculator.getTickLabels().get(i); + } + } + TextLayout textLayout = new TextLayout(sampleLabel.length() == 0 ? " " : sampleLabel, + axesChartStyler.getAxisTickLabelsFont(), new FontRenderContext(null, true, false)); + Rectangle2D rectangle = textLayout.getBounds(); + axisTickLabelsHeight = rectangle.getWidth() + axesChartStyler.getAxisTickPadding() + axesChartStyler.getAxisTickMarkLength(); + } + return titleHeight + axisTickLabelsHeight; + } + + private AxisTickCalculator getAxisTickCalculator(double workingSpace) { + Map labelOverrideMap = chart.getYAxisLabelOverrideMap(getDirection(), yIndex); + if (labelOverrideMap != null) { + if (getDirection() == Direction.X && axesChartStyler instanceof CategoryStyler) { + AxesChartSeriesCategory axesChartSeries = + (AxesChartSeriesCategory) chart.getSeriesMap().values().iterator().next(); + List categories = (List) axesChartSeries.getXData(); + DataType axisType = chart.getAxisPair().getXAxis().getDataType(); + return new AxisTickCalculatorOverride(getDirection(), + workingSpace, + axesChartStyler, + labelOverrideMap, + axisType, + categories.size()); + } + return new AxisTickCalculatorOverride(getDirection(), workingSpace, min, max, axesChartStyler, labelOverrideMap); + } + if (getDirection() == Direction.X) { + if (axesChartStyler instanceof CategoryStyler) { + AxesChartSeriesCategory axesChartSeries = + (AxesChartSeriesCategory) chart.getSeriesMap().values().iterator().next(); + List categories = (List) axesChartSeries.getXData(); + DataType axisType = chart.getAxisPair().getXAxis().getDataType(); + return new AxisTickCalculatorCategory( + getDirection(), workingSpace, categories, axisType, axesChartStyler); + } else if (getDataType() == DataType.Instant) { + return new AxisTickCalculatorInstant(getDirection(), workingSpace, min, max, axesChartStyler); + } else if (axesChartStyler.isXAxisLogarithmic()) { + return new AxisTickCalculatorLogarithmic( + getDirection(), workingSpace, min, max, axesChartStyler); + } else { + return new AxisTickCalculatorNumber(getDirection(), workingSpace, min, max, axesChartStyler); + } + } else { + if (axesChartStyler.isYAxisLogarithmic() && getDataType() != DataType.Instant) { + return new AxisTickCalculatorLogarithmic(getDirection(), workingSpace, min, max, axesChartStyler); + } else { + return new AxisTickCalculatorNumber(getDirection(), workingSpace, min, max, axesChartStyler); + } + } + } + + public DataType getDataType() { + return dataType; + } + + public void setDataType(DataType dataType) { + + if (dataType != null && this.dataType != null && this.dataType != dataType) { + throw new IllegalArgumentException("Different Axes (e.g. Date, Number, String) cannot be mixed on the same chart!!"); + } + this.dataType = dataType; + } + + public double getMin() { + return min; + } + + public void setMin(double min) { + this.min = min; + } + + public double getMax() { + return max; + } + + public void setMax(double max) { + this.max = max; + } + + public AxisTick getAxisTick() { + return axisTick; + } + + public Direction getDirection() { + return direction; + } + + public AxisTitle getAxisTitle() { + return axisTitle; + } + + public AxisTickCalculator getAxisTickCalculator() { + return this.axisTickCalculator; + } + + public int getYIndex() { + return yIndex; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisPair.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisPair.java new file mode 100644 index 0000000..20a2a84 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisPair.java @@ -0,0 +1,363 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.category.CategorySeriesRenderStyle; +import org.xbib.graphics.chart.category.CategoryStyler; +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.AxesChartSeries; +import org.xbib.graphics.chart.series.AxesChartSeriesCategory; +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.legend.LegendPosition; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class AxisPair implements ChartComponent { + + private final Chart chart; + + private final Axis xAxis; + + private final Axis yAxis; + + private final TreeMap> yAxisMap; + + private final Rectangle2D.Double leftYAxisBounds; + + private final Rectangle2D.Double rightYAxisBounds; + + private Axis leftMainYAxis; + + private Axis rightMainYAxis; + + private final Map> axisLabelOverrideMap; + + public AxisPair(Chart chart) { + this.chart = chart; + this.xAxis = new Axis<>(chart, Direction.X, 0); + this.yAxis = new Axis<>(chart, Direction.Y, 0); + this.yAxisMap = new TreeMap<>(); + yAxisMap.put(0, yAxis); + leftYAxisBounds = new Rectangle2D.Double(); + rightYAxisBounds = new Rectangle2D.Double(); + axisLabelOverrideMap = new HashMap<>(); + } + + @Override + public Rectangle2D getBounds() { + return null; // should never be called + } + + @Override + public void paint(Graphics2D g) { + prepareForPaint(); + leftMainYAxis = null; + rightMainYAxis = null; + ST styler = chart.getStyler(); + final int chartPadding = styler.getChartPadding(); + int tickMargin = (styler.isYAxisTicksVisible() ? (styler.getPlotMargin()) : 0); + leftYAxisBounds.width = 0; + int leftCount = 0; + double leftStart = chartPadding; + for (Map.Entry> e : yAxisMap.entrySet()) { + Axis ya = e.getValue(); + if (styler.getYAxisGroupPosistion(e.getKey()) == YAxisPosition.Right) { + continue; + } + if (e.getKey() == 0) { + continue; + } + ya.preparePaint(); + Rectangle2D.Double bounds = (Rectangle2D.Double) ya.getBounds(); + bounds.x = leftStart; + ya.paint(g); + double width = bounds.getWidth(); + leftStart += chartPadding + width + tickMargin; + leftYAxisBounds.width += width; + leftCount++; + leftMainYAxis = ya; + } + if (styler.getYAxisGroupPosistion(0) != YAxisPosition.Right) { + yAxis.preparePaint(); + Rectangle2D.Double bounds = (Rectangle2D.Double) yAxis.getBounds(); + bounds.x = leftStart; + yAxis.paint(g); + double width = bounds.getWidth(); + //leftStart += chartPadding + width + tickMargin; + leftYAxisBounds.width += width; + leftCount++; + leftMainYAxis = yAxis; + } + + if (leftCount > 1) { + leftYAxisBounds.width += (leftCount - 1) * chartPadding; + } + leftYAxisBounds.width += leftCount * tickMargin; + rightYAxisBounds.width = 0; + double legendWidth = 0; + if (styler.getLegendPosition() == LegendPosition.OutsideE && styler.isLegendVisible()) { + legendWidth = chart.getLegend().getBounds().getWidth() + styler.getChartPadding(); + } + double rightEnd = chart.getWidth() - legendWidth - chartPadding; + rightYAxisBounds.x = rightEnd; + int rightCount = 0; + for (Map.Entry> e : yAxisMap.descendingMap().entrySet()) { + Axis ya = e.getValue(); + if (styler.getYAxisGroupPosistion(e.getKey()) != YAxisPosition.Right) { + continue; + } + if (e.getKey() == 0) { + continue; + } + ya.preparePaint(); + Rectangle2D.Double bounds = (Rectangle2D.Double) ya.getBounds(); + double aproxWidth = bounds.getWidth(); + double xOffset = rightEnd - aproxWidth; + bounds.x = xOffset; + rightYAxisBounds.x = xOffset; + ya.paint(g); + rightYAxisBounds.width += aproxWidth; + rightEnd -= chartPadding + aproxWidth + tickMargin; + rightCount++; + rightMainYAxis = ya; + } + if (styler.getYAxisGroupPosistion(0) == YAxisPosition.Right) { + yAxis.preparePaint(); + Rectangle2D.Double bounds = (Rectangle2D.Double) yAxis.getBounds(); + double aproxWidth = bounds.getWidth(); + double xOffset = rightEnd - aproxWidth; + bounds.x = xOffset; + rightYAxisBounds.x = xOffset; + yAxis.paint(g); + rightYAxisBounds.width += aproxWidth; + //rightEnd -= chartPadding + aproxWidth + tickMargin; + rightCount++; + rightMainYAxis = yAxis; + } + if (leftMainYAxis == null) { + leftMainYAxis = yAxis; + } + if (rightMainYAxis == null) { + rightMainYAxis = yAxis; + } + + if (rightCount > 1) { + rightYAxisBounds.width += (rightCount - 1) * chartPadding; + } + rightYAxisBounds.width += rightCount * tickMargin; + Rectangle2D.Double bounds = (java.awt.geom.Rectangle2D.Double) yAxis.getBounds(); + leftYAxisBounds.x = chartPadding; + leftYAxisBounds.y = bounds.y; + leftYAxisBounds.height = bounds.height; + rightYAxisBounds.y = bounds.y; + rightYAxisBounds.height = bounds.height; + xAxis.preparePaint(); + xAxis.paint(g); + } + + public Axis getYAxis(int yIndex) { + return yAxisMap.get(yIndex); + } + + public Axis getXAxis() { + return xAxis; + } + + public Axis getYAxis() { + return yAxis; + } + + public Rectangle2D.Double getLeftYAxisBounds() { + return leftYAxisBounds; + } + + public Rectangle2D.Double getRightYAxisBounds() { + return rightYAxisBounds; + } + + public Axis getLeftMainYAxis() { + return leftMainYAxis; + } + + public Axis getRightMainYAxis() { + return rightMainYAxis; + } + + public Map> getAxisLabelOverrideMap() { + return axisLabelOverrideMap; + } + + private void prepareForPaint() { + boolean mainYAxisUsed = false; + if (chart.getSeriesMap() != null) { + for (S series : chart.getSeriesMap().values()) { + int yIndex = series.getYAxisGroup(); + if (!mainYAxisUsed && yIndex == 0) { + mainYAxisUsed = true; + } + if (yAxisMap.containsKey(yIndex)) { + continue; + } + yAxisMap.put(yIndex, new Axis<>(chart, Direction.Y, yIndex)); + } + } + xAxis.setDataType(null); + yAxis.setDataType(null); + for (S series : chart.getSeriesMap().values()) { + xAxis.setDataType(series.getxAxisDataType()); + yAxis.setDataType(series.getyAxisDataType()); + getYAxis(series.getYAxisGroup()).setDataType(series.getyAxisDataType()); + if (!mainYAxisUsed) { + yAxis.setDataType(series.getyAxisDataType()); + } + } + xAxis.resetMinMax(); + for (Axis ya : yAxisMap.values()) { + ya.resetMinMax(); + } + if (chart.getSeriesMap() == null || chart.getSeriesMap().size() < 1) { + xAxis.addMinMax(-1, 1); + for (Axis ya : yAxisMap.values()) { + ya.addMinMax(-1, 1); + } + } else { + int disabledCount = 0; + for (S series : chart.getSeriesMap().values()) { + if (!series.isEnabled()) { + disabledCount++; + continue; + } + xAxis.addMinMax(series.getXMin(), series.getXMax()); + getYAxis(series.getYAxisGroup()).addMinMax(series.getYMin(), series.getYMax()); + if (!mainYAxisUsed) { + yAxis.addMinMax(series.getYMin(), series.getYMax()); + } + } + if (disabledCount == chart.getSeriesMap().values().size()) { + xAxis.addMinMax(-1, 1); + for (Axis ya : yAxisMap.values()) { + ya.addMinMax(-1, 1); + } + } + } + overrideMinMaxForXAxis(); + for (Axis ya : yAxisMap.values()) { + overrideMinMaxForYAxis(ya); + } + if (chart.getStyler().isXAxisLogarithmic() && xAxis.getMin() <= 0.0) { + throw new IllegalArgumentException("Series data (accounting for error bars too) cannot be less or equal to zero for a logarithmic X-Axis"); + } + if (chart.getStyler().isYAxisLogarithmic() && yAxis.getMin() <= 0.0) { + for (Axis ya : yAxisMap.values()) { + if (ya.getMin() <= 0.0) { + throw new IllegalArgumentException("Series data (accounting for error bars too) cannot be less or equal to zero for a logarithmic Y-Axis"); + } + } + } + if (xAxis.getMin() == Double.POSITIVE_INFINITY || xAxis.getMax() == Double.POSITIVE_INFINITY) { + throw new IllegalArgumentException( + "Series data (accounting for error bars too) cannot be equal to Double.POSITIVE_INFINITY!!!"); + } + for (Axis ya : yAxisMap.values()) { + if (ya.getMin() == Double.POSITIVE_INFINITY || ya.getMax() == Double.POSITIVE_INFINITY) { + throw new IllegalArgumentException( + "Series data (accounting for error bars too) cannot be equal to Double.POSITIVE_INFINITY!!!"); + } + if (ya.getMin() == Double.NEGATIVE_INFINITY || ya.getMax() == Double.NEGATIVE_INFINITY) { + throw new IllegalArgumentException( + "Series data (accounting for error bars too) cannot be equal to Double.NEGATIVE_INFINITY!!!"); + } + } + + if (xAxis.getMin() == Double.NEGATIVE_INFINITY || xAxis.getMax() == Double.NEGATIVE_INFINITY) { + throw new IllegalArgumentException( + "Series data (accounting for error bars too) cannot be equal to Double.NEGATIVE_INFINITY!!!"); + } + } + + private void overrideMinMaxForXAxis() { + double overrideXAxisMinValue = xAxis.getMin(); + double overrideXAxisMaxValue = xAxis.getMax(); + if (chart.getStyler().getXAxisMin() != null) { + overrideXAxisMinValue = chart.getStyler().getXAxisMin(); + } + if (chart.getStyler().getXAxisMax() != null) { + overrideXAxisMaxValue = chart.getStyler().getXAxisMax(); + } + xAxis.setMin(overrideXAxisMinValue); + xAxis.setMax(overrideXAxisMaxValue); + } + + private void overrideMinMaxForYAxis(Axis yAxis) { + double overrideYAxisMinValue = yAxis.getMin(); + double overrideYAxisMaxValue = yAxis.getMax(); + if (chart.getStyler() instanceof CategoryStyler) { + CategoryStyler categoryStyler = (CategoryStyler) chart.getStyler(); + if (categoryStyler.getDefaultSeriesRenderStyle() == CategorySeriesRenderStyle.Bar) { + if (categoryStyler.isStacked()) { + AxesChartSeriesCategory axesChartSeries = + (AxesChartSeriesCategory) chart.getSeriesMap().values().iterator().next(); + List categories = (List) axesChartSeries.getXData(); + int numCategories = categories.size(); + double[] accumulatedStackOffsetPos = new double[numCategories]; + double[] accumulatedStackOffsetNeg = new double[numCategories]; + for (S series : chart.getSeriesMap().values()) { + AxesChartSeriesCategory axesChartSeriesCategory = (AxesChartSeriesCategory) series; + if (!series.isEnabled()) { + continue; + } + int categoryCounter = 0; + for (Number next : axesChartSeriesCategory.getYData()) { + if (next == null) { + categoryCounter++; + continue; + } + if (next.doubleValue() > 0) { + accumulatedStackOffsetPos[categoryCounter] += next.doubleValue(); + } else if (next.doubleValue() < 0) { + accumulatedStackOffsetNeg[categoryCounter] += next.doubleValue(); + } + categoryCounter++; + } + } + double max = accumulatedStackOffsetPos[0]; + for (int i = 1; i < accumulatedStackOffsetPos.length; i++) { + if (accumulatedStackOffsetPos[i] > max) { + max = accumulatedStackOffsetPos[i]; + } + } + double min = accumulatedStackOffsetNeg[0]; + for (int i = 1; i < accumulatedStackOffsetNeg.length; i++) { + if (accumulatedStackOffsetNeg[i] < min) { + min = accumulatedStackOffsetNeg[i]; + } + } + overrideYAxisMaxValue = max; + overrideYAxisMinValue = min; + } + if (yAxis.getMin() > 0.0) { + overrideYAxisMinValue = 0.0; + } + if (yAxis.getMax() < 0.0) { + overrideYAxisMaxValue = 0.0; + } + } + } + if (chart.getStyler().getYAxisMin(yAxis.getYIndex()) != null) { + overrideYAxisMinValue = chart.getStyler().getYAxisMin(yAxis.getYIndex()); + } else if (chart.getStyler().getYAxisMin() != null) { + overrideYAxisMinValue = chart.getStyler().getYAxisMin(); + } + if (chart.getStyler().getYAxisMax(yAxis.getYIndex()) != null) { + overrideYAxisMaxValue = chart.getStyler().getYAxisMax(yAxis.getYIndex()); + } else if (chart.getStyler().getYAxisMax() != null) { + overrideYAxisMaxValue = chart.getStyler().getYAxisMax(); + } + yAxis.setMin(overrideYAxisMinValue); + yAxis.setMax(overrideYAxisMaxValue); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTick.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTick.java new file mode 100644 index 0000000..ac19962 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTick.java @@ -0,0 +1,62 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.AxesChartSeries; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +public class AxisTick implements ChartComponent { + + private final Chart chart; + + private final Direction direction; + + private Rectangle2D bounds; + + private final AxisTickLabels axisTickLabels; + + private final AxisTickMarks axisTickMarks; + + protected AxisTick(Chart chart, Direction direction, Axis yAxis) { + this.chart = chart; + this.direction = direction; + axisTickLabels = new AxisTickLabels<>(chart, direction, yAxis); + axisTickMarks = new AxisTickMarks<>(chart, direction, yAxis); + } + + @Override + public Rectangle2D getBounds() { + return bounds; + } + + @Override + public void paint(Graphics2D g) { + if (direction == Direction.Y && chart.getStyler().isYAxisTicksVisible()) { + axisTickLabels.paint(g); + axisTickMarks.paint(g); + bounds = new Rectangle2D.Double(axisTickLabels.getBounds().getX(), + axisTickLabels.getBounds().getY(), + axisTickLabels.getBounds().getWidth() + chart.getStyler().getAxisTickPadding() + axisTickMarks.getBounds().getWidth(), + axisTickMarks.getBounds().getHeight() + ); + } else if (direction == Direction.X && chart.getStyler().isXAxisTicksVisible()) { + axisTickLabels.paint(g); + axisTickMarks.paint(g); + bounds = new Rectangle2D.Double(axisTickMarks.getBounds().getX(), + axisTickMarks.getBounds().getY(), + axisTickLabels.getBounds().getWidth(), + axisTickMarks.getBounds().getHeight() + chart.getStyler().getAxisTickPadding() + axisTickLabels.getBounds().getHeight() + ); + } else { + bounds = new Rectangle2D.Double(); + } + } + + protected AxisTickLabels getAxisTickLabels() { + return axisTickLabels; + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculator.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculator.java new file mode 100644 index 0000000..16a2701 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculator.java @@ -0,0 +1,87 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.text.Format; +import java.util.LinkedList; +import java.util.List; + +public abstract class AxisTickCalculator { + + protected final Direction axisDirection; + + protected final double workingSpace; + + protected final double minValue; + + protected final double maxValue; + + protected final AxesChartStyler styler; + + protected Format axisFormat; + + protected List tickLocations = new LinkedList<>(); + + protected List tickLabels = new LinkedList<>(); + + public AxisTickCalculator(Direction axisDirection, double workingSpace, double minValue, double maxValue, AxesChartStyler styler) { + this.axisDirection = axisDirection; + this.workingSpace = workingSpace; + this.minValue = minValue; + this.maxValue = maxValue; + this.styler = styler; + } + + /** + * Gets the first position + * + * @param gridStep grid step + * @return first posiition + */ + public double getFirstPosition(double gridStep) { + return minValue - (minValue % gridStep) - gridStep; + } + + public List getTickLocations() { + return tickLocations; + } + + public List getTickLabels() { + return tickLabels; + } + + /** + * Given the generated tickLabels, will they fit side-by-side without overlapping each other and looking bad? + * Sometimes the given tickSpacingHint is simply too small. + * + * @param tickLabels tick lables + * @param tickSpacingHint tick psacing hint + * @return true if it fits + */ + public boolean willLabelsFitInTickSpaceHint(List tickLabels, int tickSpacingHint) { + if (this.axisDirection == Direction.Y) { + return true; + } + String sampleLabel = " "; + for (String tickLabel : tickLabels) { + if (tickLabel != null && tickLabel.length() > sampleLabel.length()) { + sampleLabel = tickLabel; + } + } + TextLayout textLayout = new TextLayout(sampleLabel, styler.getAxisTickLabelsFont(), new FontRenderContext(null, true, false)); + AffineTransform rot = styler.getXAxisLabelRotation() == 0 ? null : AffineTransform.getRotateInstance(-1 * Math.toRadians(styler.getXAxisLabelRotation())); + Shape shape = textLayout.getOutline(rot); + Rectangle2D rectangle = shape.getBounds(); + double largestLabelWidth = rectangle.getWidth(); + return (largestLabelWidth * 1.1 < tickSpacingHint); + } + + public Format getAxisFormat() { + return axisFormat; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorCategory.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorCategory.java new file mode 100644 index 0000000..92ad620 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorCategory.java @@ -0,0 +1,51 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.formatter.DateFormatter; +import org.xbib.graphics.chart.formatter.NumberFormatter; +import org.xbib.graphics.chart.formatter.StringFormatter; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.math.BigDecimal; +import java.util.List; + +/** + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis + * ticks for String axes + */ +public class AxisTickCalculatorCategory extends AxisTickCalculator { + + public AxisTickCalculatorCategory(Direction axisDirection, double workingSpace, List categories, DataType axisType, AxesChartStyler styler) { + super(axisDirection, workingSpace, Double.NaN, Double.NaN, styler); + calculate(categories, axisType); + } + + private void calculate(List categories, DataType axisType) { + // tick space - a percentage of the working space available for ticks + double tickSpace = styler.getPlotContentSize() * workingSpace; // in plot space + // where the tick should begin in the working space in pixels + double margin = (workingSpace - tickSpace) / 2.0; + // generate all tickLabels and tickLocations from the first to last position + double gridStep = (tickSpace / (double) categories.size()); + double firstPosition = gridStep / 2.0; + // set up String formatters that may be encountered + if (axisType == DataType.String) { + axisFormat = new StringFormatter(); + } else if (axisType == DataType.Number) { + axisFormat = new NumberFormatter(styler, axisDirection, minValue, maxValue); + } else if (axisType == DataType.Instant) { + axisFormat = new DateFormatter(styler.getDatePattern(), styler.getLocale()); + } + int counter = 0; + for (Object category : categories) { + if (axisType == DataType.String) { + tickLabels.add(category.toString()); + } else if (axisType == DataType.Number) { + tickLabels.add(axisFormat.format(new BigDecimal(category.toString()).doubleValue())); + } else if (axisType == DataType.Instant) { + tickLabels.add(axisFormat.format(category)); + } + double tickLabelPosition = (int) (margin + firstPosition + gridStep * counter++); + tickLocations.add(tickLabelPosition); + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorInstant.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorInstant.java new file mode 100644 index 0000000..8b749e2 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorInstant.java @@ -0,0 +1,161 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis + * ticks for date axes + */ +public class AxisTickCalculatorInstant extends AxisTickCalculator { + + private static final long MILLIS_SCALE = TimeUnit.MILLISECONDS.toMillis(1L); + private static final long SEC_SCALE = TimeUnit.SECONDS.toMillis(1L); + private static final long MIN_SCALE = TimeUnit.MINUTES.toMillis(1L); + private static final long HOUR_SCALE = TimeUnit.HOURS.toMillis(1L); + private static final long DAY_SCALE = TimeUnit.DAYS.toMillis(1L); + private static final long MONTH_SCALE = TimeUnit.DAYS.toMillis(1L) * 30; + private static final long YEAR_SCALE = TimeUnit.DAYS.toMillis(1L) * 365; + + private static final List timeSpans = new ArrayList<>(); + + static { + timeSpans.add(new TimeSpan(MILLIS_SCALE, 1, "ss.SSS")); + timeSpans.add(new TimeSpan(MILLIS_SCALE, 2, "ss.SSS")); + timeSpans.add(new TimeSpan(MILLIS_SCALE, 5, "ss.SSS")); + timeSpans.add(new TimeSpan(MILLIS_SCALE, 10, "ss.SSS")); + timeSpans.add(new TimeSpan(MILLIS_SCALE, 50, "ss.SS")); + timeSpans.add(new TimeSpan(MILLIS_SCALE, 100, "ss.SS")); + timeSpans.add(new TimeSpan(MILLIS_SCALE, 200, "ss.SS")); + timeSpans.add(new TimeSpan(MILLIS_SCALE, 500, "ss.SS")); + + timeSpans.add(new TimeSpan(SEC_SCALE, 1, "ss.SS")); + timeSpans.add(new TimeSpan(SEC_SCALE, 2, "ss.S")); + timeSpans.add(new TimeSpan(SEC_SCALE, 5, "ss.S")); + timeSpans.add(new TimeSpan(SEC_SCALE, 10, "HH:mm:ss")); + timeSpans.add(new TimeSpan(SEC_SCALE, 15, "HH:mm:ss")); + timeSpans.add(new TimeSpan(SEC_SCALE, 20, "HH:mm:ss")); + timeSpans.add(new TimeSpan(SEC_SCALE, 30, "HH:mm:ss")); + + timeSpans.add(new TimeSpan(MIN_SCALE, 1, "HH:mm:ss")); + timeSpans.add(new TimeSpan(MIN_SCALE, 2, "HH:mm:ss")); + timeSpans.add(new TimeSpan(MIN_SCALE, 5, "HH:mm:ss")); + timeSpans.add(new TimeSpan(MIN_SCALE, 10, "HH:mm")); + timeSpans.add(new TimeSpan(MIN_SCALE, 15, "HH:mm")); + timeSpans.add(new TimeSpan(MIN_SCALE, 20, "HH:mm")); + timeSpans.add(new TimeSpan(MIN_SCALE, 30, "HH:mm")); + + timeSpans.add(new TimeSpan(HOUR_SCALE, 1, "HH:mm")); + timeSpans.add(new TimeSpan(HOUR_SCALE, 2, "HH:mm")); + timeSpans.add(new TimeSpan(HOUR_SCALE, 4, "HH:mm")); + timeSpans.add(new TimeSpan(HOUR_SCALE, 8, "HH:mm")); + timeSpans.add(new TimeSpan(HOUR_SCALE, 12, "HH:mm")); + + timeSpans.add(new TimeSpan(DAY_SCALE, 1, "EEE HH:mm")); + timeSpans.add(new TimeSpan(DAY_SCALE, 2, "EEE HH:mm")); + timeSpans.add(new TimeSpan(DAY_SCALE, 3, "EEE HH:mm")); + timeSpans.add(new TimeSpan(DAY_SCALE, 5, "MM-dd")); + timeSpans.add(new TimeSpan(DAY_SCALE, 10, "MM-dd")); + timeSpans.add(new TimeSpan(DAY_SCALE, 15, "MM-dd")); + + timeSpans.add(new TimeSpan(MONTH_SCALE, 1, "MM-dd")); + timeSpans.add(new TimeSpan(MONTH_SCALE, 2, "MM-dd")); + timeSpans.add(new TimeSpan(MONTH_SCALE, 3, "MM-dd")); + timeSpans.add(new TimeSpan(MONTH_SCALE, 4, "MM-dd")); + timeSpans.add(new TimeSpan(MONTH_SCALE, 6, "yyyy-MM")); + + timeSpans.add(new TimeSpan(YEAR_SCALE, 1, "yyyy-MM")); + timeSpans.add(new TimeSpan(YEAR_SCALE, 2, "yyyy-MM")); + timeSpans.add(new TimeSpan(YEAR_SCALE, 5, "yyyy")); + timeSpans.add(new TimeSpan(YEAR_SCALE, 10, "yyyy")); + timeSpans.add(new TimeSpan(YEAR_SCALE, 20, "yyyy")); + timeSpans.add(new TimeSpan(YEAR_SCALE, 100, "yyyy")); + timeSpans.add(new TimeSpan(YEAR_SCALE, 500, "yyyy")); + timeSpans.add(new TimeSpan(YEAR_SCALE, 1000, "yyyy")); + } + + public AxisTickCalculatorInstant(Direction axisDirection, double workingSpace, double minValue, double maxValue, AxesChartStyler styler) { + super(axisDirection, workingSpace, minValue, maxValue, styler); + calculate(); + } + + private void calculate() { + double tickSpace = styler.getPlotContentSize() * workingSpace; + if (tickSpace < styler.getXAxisTickMarkSpacingHint()) { + return; + } + double margin = (workingSpace - tickSpace) / 2.0; + long span = (long) Math.abs(maxValue - minValue); + int tickSpacingHint = styler.getXAxisTickMarkSpacingHint(); + int gridStepInChartSpace; + long gridStepHint = (long) (span / tickSpace * tickSpacingHint); + int index = 0; + for (int i = 0; i < timeSpans.size() - 1; i++) { + if (span < ((timeSpans.get(i).getUnitAmount() * timeSpans.get(i).getMagnitude() + timeSpans.get(i + 1).getUnitAmount() * timeSpans.get(i + 1).getMagnitude()) / 2.0)) { + index = i; + break; + } + } + String datePattern = timeSpans.get(index).getDatePattern(); + for (int i = index - 1; i > 0; i--) { + if (gridStepHint > timeSpans.get(i).getUnitAmount() * timeSpans.get(i).getMagnitude()) { + index = i; + break; + } + } + index--; + do { + tickLabels.clear(); + tickLocations.clear(); + double gridStep = timeSpans.get(++index).getUnitAmount() * timeSpans.get(index).getMagnitude(); // in time units (ms) + gridStepInChartSpace = (int) (gridStep / span * tickSpace); + double firstPosition = getFirstPosition(gridStep); + if (styler.getDatePattern() != null) { + datePattern = styler.getDatePattern(); + } + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(datePattern) + .withZone(styler.getZoneId()); + for (double value = firstPosition; value <= maxValue + 2 * gridStep; value = value + gridStep) { + long l = Math.round(value); + tickLabels.add(dateTimeFormatter.format(Instant.ofEpochMilli(l))); + double tickLabelPosition = margin + ((value - minValue) / (maxValue - minValue) * tickSpace); + tickLocations.add(tickLabelPosition); + } + } while (!willLabelsFitInTickSpaceHint(tickLabels, gridStepInChartSpace)); + } + + static class TimeSpan { + + private final long unitAmount; + private final int magnitude; + private final String datePattern; + + TimeSpan(long unitAmount, int magnitude, String datePattern) { + this.unitAmount = unitAmount; + this.magnitude = magnitude; + this.datePattern = datePattern; + } + + long getUnitAmount() { + return unitAmount; + } + + int getMagnitude() { + return magnitude; + } + + String getDatePattern() { + return datePattern; + } + + @Override + public String toString() { + return "TimeSpan [unitAmount=" + unitAmount + ", magnitude=" + magnitude + ", datePattern=" + datePattern + "]"; + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorLogarithmic.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorLogarithmic.java new file mode 100644 index 0000000..ae97bf4 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorLogarithmic.java @@ -0,0 +1,62 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.formatter.NumberLogFormatter; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.math.BigDecimal; + +/** + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis + * ticks for logarithmic axes + */ +public class AxisTickCalculatorLogarithmic extends AxisTickCalculator { + + private final NumberLogFormatter numberLogFormatter; + + public AxisTickCalculatorLogarithmic(Direction axisDirection, double workingSpace, double minValue, double maxValue, AxesChartStyler styler) { + super(axisDirection, workingSpace, minValue, maxValue, styler); + numberLogFormatter = new NumberLogFormatter(styler, axisDirection); + axisFormat = numberLogFormatter; + calculate(); + } + + private void calculate() { + if (minValue == maxValue) { + tickLabels.add(numberLogFormatter.format(BigDecimal.valueOf(maxValue))); + tickLocations.add(workingSpace / 2.0); + return; + } + double tickSpace = styler.getPlotContentSize() * workingSpace; + if (tickSpace < styler.getXAxisTickMarkSpacingHint()) { + return; + } + double margin = (workingSpace - tickSpace) / 2.0; + int logMin = (int) Math.floor(Math.log10(minValue)); + int logMax = (int) Math.ceil(Math.log10(maxValue)); + double firstPosition = pow(logMin); + double tickStep = pow(logMin - 1); + for (int i = logMin; i <= logMax; i++) { + for (double j = firstPosition; j <= pow(i) + .00000001; j = j + tickStep) { + if (j < minValue - tickStep) { + continue; + } + if (j > maxValue + tickStep) { + break; + } + if (Math.abs(Math.log10(j) % 1) < 0.00000001) { + tickLabels.add(numberLogFormatter.format(j)); + } else { + tickLabels.add(null); + } + double tickLabelPosition = (int) (margin + (Math.log10(j) - Math.log10(minValue)) / (Math.log10(maxValue) - Math.log10(minValue)) * tickSpace); + tickLocations.add(tickLabelPosition); + } + tickStep = tickStep * pow(1); + firstPosition = tickStep + pow(i); + } + } + + private static double pow(int exponent) { + return exponent > 0 ? Math.pow(10, exponent) : 1.0 / Math.pow(10, -1 * exponent); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorNumber.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorNumber.java new file mode 100644 index 0000000..dbd95b3 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorNumber.java @@ -0,0 +1,89 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.formatter.NumberFormatter; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for rendering the axis + * ticks for decimal axes + */ +public class AxisTickCalculatorNumber extends AxisTickCalculator { + + private final NumberFormatter numberFormatter; + + public AxisTickCalculatorNumber(Direction axisDirection, double workingSpace, double minValue, double maxValue, AxesChartStyler styler) { + super(axisDirection, workingSpace, minValue, maxValue, styler); + numberFormatter = new NumberFormatter(styler, axisDirection, minValue, maxValue); + axisFormat = numberFormatter; + calculate(); + } + + private void calculate() { + if (minValue == maxValue) { + tickLabels.add(numberFormatter.format(BigDecimal.valueOf(maxValue))); + tickLocations.add(workingSpace / 2.0); + return; + } + double tickSpace = styler.getPlotContentSize() * workingSpace; + if (tickSpace < styler.getXAxisTickMarkSpacingHint()) { + return; + } + double margin = (workingSpace - tickSpace) / 2.0; + double span = Math.abs(Math.min((maxValue - minValue), Double.MAX_VALUE - 1)); + int tickSpacingHint = (axisDirection == Direction.X ? styler.getXAxisTickMarkSpacingHint() : styler.getYAxisTickMarkSpacingHint()) - 5; + if (axisDirection == Direction.Y && tickSpace < 160) { + tickSpacingHint = 25 - 5; + } + int gridStepInChartSpace; + do { + tickLabels.clear(); + tickLocations.clear(); + tickSpacingHint += 5; + double significand = span / tickSpace * tickSpacingHint; + int exponent = 0; + if (significand == 0) { + exponent = 1; + } else if (significand < 1) { + while (significand < 1) { + significand *= 10.0; + exponent--; + } + } else { + while (significand >= 10 || significand == Double.NEGATIVE_INFINITY) { + significand /= 10.0; + exponent++; + } + } + double gridStep; + if (significand > 7.5) { + gridStep = 10.0 * pow(exponent); + } else if (significand > 3.5) { + gridStep = 5.0 * pow(exponent); + } else if (significand > 1.5) { + gridStep = 2.0 * pow(exponent); + } else { + gridStep = pow(exponent); + } + gridStepInChartSpace = (int) (gridStep / span * tickSpace); + BigDecimal gridStepBigDecimal = BigDecimal.valueOf(gridStep); + BigDecimal cleanedGridStep = gridStepBigDecimal.setScale(10, RoundingMode.HALF_UP).stripTrailingZeros(); + BigDecimal firstPosition = BigDecimal.valueOf(getFirstPosition(cleanedGridStep.doubleValue())); + BigDecimal cleanedFirstPosition = firstPosition.setScale(10, RoundingMode.HALF_UP).stripTrailingZeros(); + for (BigDecimal value = cleanedFirstPosition; + value.compareTo(BigDecimal.valueOf(maxValue + 2 * cleanedGridStep.doubleValue())) < 0; + value = value.add(cleanedGridStep)) { + String tickLabel = numberFormatter.format(value); + tickLabels.add(tickLabel); + double tickLabelPosition = margin + ((value.doubleValue() - minValue) / (maxValue - minValue) * tickSpace); + tickLocations.add(tickLabelPosition); + } + } while (!willLabelsFitInTickSpaceHint(tickLabels, gridStepInChartSpace)); + } + + private static double pow(int exponent) { + return exponent > 0 ? Math.pow(10, exponent) : 1.0 / Math.pow(10, -1 * exponent); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorOverride.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorOverride.java new file mode 100644 index 0000000..467f4f3 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickCalculatorOverride.java @@ -0,0 +1,79 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.formatter.DateFormatter; +import org.xbib.graphics.chart.formatter.NumberFormatter; +import org.xbib.graphics.chart.formatter.StringFormatter; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.util.Map; + +/** + * This class encapsulates the logic to generate the axis tick mark and axis tick label data for + * rendering the axis ticks for given values and labels. + */ +class AxisTickCalculatorOverride extends AxisTickCalculator { + + public AxisTickCalculatorOverride(Direction axisDirection, + double workingSpace, + double minValue, + double maxValue, + AxesChartStyler styler, + Map labelOverrideMap) { + super(axisDirection, workingSpace, minValue, maxValue, styler); + axisFormat = new NumberFormatter(styler, axisDirection, minValue, maxValue); + calculate(labelOverrideMap); + } + + public AxisTickCalculatorOverride(Direction axisDirection, + double workingSpace, + AxesChartStyler styler, + Map markMap, + DataType axisType, + int categoryCount) { + super(axisDirection, workingSpace, Double.NaN, Double.NaN, styler); + if (axisType == DataType.String) { + axisFormat = new StringFormatter(); + } else if (axisType == DataType.Number) { + axisFormat = new NumberFormatter(styler, axisDirection, minValue, maxValue); + } else if (axisType == DataType.Instant) { + axisFormat = new DateFormatter(styler.getDatePattern(), styler.getLocale()); + } + calculateForCategory(markMap, categoryCount); + } + + private void calculate(Map labelOverrideMap) { + if (minValue == maxValue) { + String label = labelOverrideMap.isEmpty() ? " " : labelOverrideMap.values().iterator().next().toString(); + tickLabels.add(label); + tickLocations.add(workingSpace / 2.0); + return; + } + double tickSpace = styler.getPlotContentSize() * workingSpace; + if (tickSpace < styler.getXAxisTickMarkSpacingHint()) { + return; + } + double margin = (workingSpace - tickSpace) / 2.0; + for (Map.Entry entry : labelOverrideMap.entrySet()) { + Object value = entry.getValue(); + String tickLabel = value == null ? " " : value.toString(); + tickLabels.add(tickLabel); + double tickLabelPosition = + margin + ((entry.getKey() - minValue) / (maxValue - minValue) * tickSpace); + tickLocations.add(tickLabelPosition); + } + } + + private void calculateForCategory(Map locationLabelMap, int categoryCount) { + double tickSpace = styler.getPlotContentSize() * workingSpace; + double margin = (workingSpace - tickSpace) / 2.0; + double gridStep = (tickSpace / categoryCount); + double firstPosition = gridStep / 2.0; + for (Map.Entry entry : locationLabelMap.entrySet()) { + Object value = entry.getValue(); + String tickLabel = value == null ? " " : value.toString(); + tickLabels.add(tickLabel); + double tickLabelPosition = margin + firstPosition + gridStep * entry.getKey(); + tickLocations.add(tickLabelPosition); + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickLabels.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickLabels.java new file mode 100644 index 0000000..352a49e --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickLabels.java @@ -0,0 +1,168 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.AxesChartSeries; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; +import java.util.Map; + +/** + * Axis tick labels. + */ +public class AxisTickLabels implements ChartComponent { + + private final Chart chart; + + private final Direction direction; + + private final Axis yAxis; + + private Rectangle2D bounds; + + protected AxisTickLabels(Chart chart, Direction direction, Axis yAxis) { + this.chart = chart; + this.direction = direction; + this.yAxis = yAxis; + } + + @Override + public Rectangle2D getBounds() { + return bounds; + } + + @Override + public void paint(Graphics2D g) { + ST styler = chart.getStyler(); + g.setFont(styler.getAxisTickLabelsFont()); + g.setColor(styler.getAxisTickLabelsColor()); + if (direction == Direction.Y && chart.getStyler().isYAxisTicksVisible()) { + boolean onRight = styler.getYAxisGroupPosistion(yAxis.getYIndex()) == YAxisPosition.Right; + double xOffset; + if (onRight) { + xOffset = yAxis.getBounds().getX() + (styler.isYAxisTicksVisible() ? + styler.getAxisTickMarkLength() + styler.getAxisTickPadding() : 0); + } else { + double xWidth = yAxis.getAxisTitle().getBounds().getWidth(); + xOffset = yAxis.getAxisTitle().getBounds().getX() + xWidth; + } + double yOffset = yAxis.getBounds().getY(); + double height = yAxis.getBounds().getHeight(); + double maxTickLabelWidth = 0; + Map axisLabelTextLayouts = new HashMap<>(); + for (int i = 0; i < chart.getYAxis().getAxisTickCalculator().getTickLabels().size(); i++) { + String tickLabel = chart.getYAxis().getAxisTickCalculator().getTickLabels().get(i); + double tickLocation = chart.getYAxis().getAxisTickCalculator().getTickLocations().get(i); + double flippedTickLocation = yOffset + height - tickLocation; + if (tickLabel != null && !tickLabel.isEmpty() && + flippedTickLocation > yOffset && + flippedTickLocation < yOffset + height) { + FontRenderContext frc = g.getFontRenderContext(); + TextLayout axisLabelTextLayout = new TextLayout(tickLabel, styler.getAxisTickLabelsFont(), frc); + Rectangle2D tickLabelBounds = axisLabelTextLayout.getBounds(); + double boundWidth = tickLabelBounds.getWidth(); + if (boundWidth > maxTickLabelWidth) { + maxTickLabelWidth = boundWidth; + } + axisLabelTextLayouts.put(tickLocation, axisLabelTextLayout); + } + } + for (Map.Entry entry : axisLabelTextLayouts.entrySet()) { + Double tickLocation = entry.getKey(); + TextLayout axisLabelTextLayout = axisLabelTextLayouts.get(tickLocation); + Shape shape = axisLabelTextLayout.getOutline(null); + Rectangle2D tickLabelBounds = shape.getBounds(); + double flippedTickLocation = yOffset + height - tickLocation; + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + double boundWidth = tickLabelBounds.getWidth(); + double xPos; + switch (chart.getStyler().getYAxisLabelAlignment()) { + case Right: + xPos = xOffset + maxTickLabelWidth - boundWidth; + break; + case Centre: + xPos = xOffset + (maxTickLabelWidth - boundWidth) / 2; + break; + case Left: + default: + xPos = xOffset; + } + at.translate(xPos, flippedTickLocation + tickLabelBounds.getHeight() / 2.0); + g.transform(at); + g.fill(shape); + g.setTransform(orig); + } + bounds = new Rectangle2D.Double(xOffset, yOffset, maxTickLabelWidth, height); + } else if (direction == Direction.X && chart.getStyler().isXAxisTicksVisible()) { + double xOffset = chart.getXAxis().getBounds().getX(); + double yOffset = chart.getXAxis().getAxisTitle().getBounds().getY(); + double width = chart.getXAxis().getBounds().getWidth(); + double maxTickLabelHeight = 0; + int maxTickLabelY = 0; + for (int i = 0; i < chart.getXAxis().getAxisTickCalculator().getTickLabels().size(); i++) { + String tickLabel = chart.getXAxis().getAxisTickCalculator().getTickLabels().get(i); + double tickLocation = chart.getXAxis().getAxisTickCalculator().getTickLocations().get(i); + double shiftedTickLocation = xOffset + tickLocation; + if (tickLabel != null && !tickLabel.isEmpty() && + shiftedTickLocation > xOffset && + shiftedTickLocation < xOffset + width) { + FontRenderContext frc = g.getFontRenderContext(); + TextLayout textLayout = new TextLayout(tickLabel, chart.getStyler().getAxisTickLabelsFont(), frc); + AffineTransform rot = AffineTransform.getRotateInstance(-1 * Math.toRadians(chart.getStyler().getXAxisLabelRotation()), 0, 0); + Shape shape = textLayout.getOutline(rot); + Rectangle2D tickLabelBounds = shape.getBounds2D(); + if (tickLabelBounds.getBounds().height > maxTickLabelY) { + maxTickLabelY = tickLabelBounds.getBounds().height; + } + } + } + for (int i = 0; i < chart.getXAxis().getAxisTickCalculator().getTickLabels().size(); i++) { + String tickLabel = chart.getXAxis().getAxisTickCalculator().getTickLabels().get(i); + double tickLocation = chart.getXAxis().getAxisTickCalculator().getTickLocations().get(i); + double shiftedTickLocation = xOffset + tickLocation; + if (tickLabel != null && shiftedTickLocation > xOffset && shiftedTickLocation < xOffset + width) { + FontRenderContext frc = g.getFontRenderContext(); + TextLayout textLayout = new TextLayout(tickLabel, styler.getAxisTickLabelsFont(), frc); + AffineTransform rot = AffineTransform.getRotateInstance( + -1 * Math.toRadians(styler.getXAxisLabelRotation()), 0, 0); + Shape shape = textLayout.getOutline(rot); + Rectangle2D tickLabelBounds = shape.getBounds2D(); + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + double xPos; + switch (styler.getXAxisLabelAlignment()) { + case Left: + xPos = shiftedTickLocation; + break; + case Right: + xPos = shiftedTickLocation - tickLabelBounds.getWidth(); + break; + case Centre: + default: + xPos = shiftedTickLocation - tickLabelBounds.getWidth() / 2.0; + } + double shiftX = -1 * tickLabelBounds.getX() * Math.sin(Math.toRadians(chart.getStyler().getXAxisLabelRotation())); + double shiftY = -1 * (tickLabelBounds.getY() + tickLabelBounds.getHeight()); + at.translate(xPos + shiftX, yOffset + shiftY); + g.transform(at); + g.fill(shape); + g.setTransform(orig); + if (tickLabelBounds.getHeight() > maxTickLabelHeight) { + maxTickLabelHeight = tickLabelBounds.getHeight(); + } + } + } + bounds = new Rectangle2D.Double(xOffset, yOffset - maxTickLabelHeight, width, maxTickLabelHeight); + } else { + bounds = new Rectangle2D.Double(); + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickMarks.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickMarks.java new file mode 100644 index 0000000..d0ce02e --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTickMarks.java @@ -0,0 +1,101 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.AxesChartSeries; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; + +/** + * Axis tick marks. This includes the little tick marks and the line that hugs the plot area. + */ +public class AxisTickMarks implements ChartComponent { + + private final Chart chart; + + private final Direction direction; + + private final Axis yAxis; + + private Rectangle2D bounds; + + protected AxisTickMarks(Chart chart, Direction direction, Axis yAxis) { + this.chart = chart; + this.direction = direction; + this.yAxis = yAxis; + } + + @Override + public Rectangle2D getBounds() { + return bounds; + } + + @Override + public void paint(Graphics2D g) { + ST styler = chart.getStyler(); + g.setColor(styler.getAxisTickMarksColor()); + g.setStroke(styler.getAxisTickMarksStroke()); + if (direction == Direction.Y && chart.getStyler().isYAxisTicksVisible()) { + int axisTickMarkLength = styler.getAxisTickMarkLength(); + boolean onRight = styler.getYAxisGroupPosistion(yAxis.getYIndex()) == YAxisPosition.Right; + Rectangle2D yAxisBounds = yAxis.getBounds(); + Rectangle2D axisTickLabelBounds = yAxis.getAxisTick().getAxisTickLabels().getBounds(); + double xOffset; + double lineXOffset; + if (onRight) { + xOffset = axisTickLabelBounds.getX() - styler.getAxisTickPadding() - axisTickMarkLength; + lineXOffset = xOffset; + } else { + xOffset = axisTickLabelBounds.getX() + axisTickLabelBounds.getWidth() + styler.getAxisTickPadding(); + lineXOffset = xOffset + axisTickMarkLength; + } + double yOffset = yAxisBounds.getY(); + bounds = new Rectangle2D.Double(xOffset, yOffset, styler.getAxisTickMarkLength(), yAxis.getBounds().getHeight()); + if (styler.isAxisTicksMarksVisible()) { + for (int i = 0; i < chart.getYAxis().getAxisTickCalculator().getTickLabels().size(); i++) { + double tickLocation = yAxis.getAxisTickCalculator().getTickLocations().get(i); + double flippedTickLocation = yOffset + yAxisBounds.getHeight() - tickLocation; + if (flippedTickLocation > bounds.getY() && + flippedTickLocation < bounds.getY() + bounds.getHeight()) { + Shape line = new Line2D.Double(xOffset, flippedTickLocation, xOffset + axisTickMarkLength, flippedTickLocation); + g.draw(line); + } + } + } + if (styler.isAxisTicksLineVisible()) { + Shape line = new Line2D.Double(lineXOffset, yOffset, lineXOffset, yOffset + yAxisBounds.getHeight()); + g.draw(line); + } + } else if (direction == Direction.X && styler.isXAxisTicksVisible()) { + int axisTickMarkLength = styler.getAxisTickMarkLength(); + double xOffset = chart.getXAxis().getBounds().getX(); + double yOffset = chart.getXAxis().getAxisTick().getAxisTickLabels().getBounds().getY() - styler.getAxisTickPadding(); + bounds = new Rectangle2D.Double(xOffset, yOffset - axisTickMarkLength, + chart.getXAxis().getBounds().getWidth(), axisTickMarkLength); + if (styler.isAxisTicksMarksVisible()) { + for (int i = 0; i < chart.getXAxis().getAxisTickCalculator().getTickLabels().size(); i++) { + double tickLocation = chart.getXAxis().getAxisTickCalculator().getTickLocations().get(i); + double shiftedTickLocation = xOffset + tickLocation; + if (shiftedTickLocation > bounds.getX() && + shiftedTickLocation < bounds.getX() + bounds.getWidth()) { + Shape line = new Line2D.Double(shiftedTickLocation, yOffset, xOffset + tickLocation, yOffset - chart.getStyler().getAxisTickMarkLength()); + g.draw(line); + } + } + } + if (styler.isAxisTicksLineVisible()) { + g.setStroke(styler.getAxisTickMarksStroke()); + g.drawLine((int) xOffset, + (int) (yOffset - axisTickMarkLength), + (int) (xOffset + chart.getXAxis().getBounds().getWidth()), + (int) (yOffset - axisTickMarkLength)); + } else { + bounds = new Rectangle2D.Double(); + } + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTitle.java b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTitle.java new file mode 100644 index 0000000..0010256 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/AxisTitle.java @@ -0,0 +1,108 @@ +package org.xbib.graphics.chart.axis; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + +/** + * Axis title. + */ +public class AxisTitle implements ChartComponent { + + private final Chart chart; + private final Direction direction; + private final Axis yAxis; + private final int yIndex; + private Rectangle2D bounds; + + public AxisTitle(Chart chart, Direction direction, Axis yAxis, int yIndex) { + this.chart = chart; + this.direction = direction; + this.yAxis = yAxis; + this.yIndex = yIndex; + } + + @Override + public Rectangle2D getBounds() { + return bounds; + } + + @Override + public void paint(Graphics2D g) { + bounds = new Rectangle2D.Double(); + g.setColor(chart.getStyler().getChartFontColor()); + g.setFont(chart.getStyler().getAxisTitleFont()); + if (direction == Direction.Y) { + String yAxisTitle = chart.getYAxisGroupTitle(yIndex); + if (yAxisTitle != null && !yAxisTitle.trim().equalsIgnoreCase("") && + chart.getStyler().isYAxisTitleVisible()) { + FontRenderContext frc = g.getFontRenderContext(); + TextLayout nonRotatedTextLayout = new TextLayout(chart.getyYAxisTitle(), chart.getStyler().getAxisTitleFont(), frc); + Rectangle2D nonRotatedRectangle = nonRotatedTextLayout.getBounds(); + boolean onRight = chart.getStyler().getYAxisGroupPosistion(yAxis.getYIndex()) == YAxisPosition.Right; + int xOffset; + if (onRight) { + xOffset = (int) (yAxis.getAxisTick().getBounds().getX() + + yAxis.getAxisTick().getBounds().getWidth() + + nonRotatedRectangle.getHeight()); + } else { + xOffset = (int) (yAxis.getBounds().getX() + nonRotatedRectangle.getHeight()); + } + int yOffset = (int) ((yAxis.getBounds().getHeight() + nonRotatedRectangle.getWidth()) / 2.0 + + yAxis.getBounds().getY()); + AffineTransform rot = AffineTransform.getRotateInstance(-1 * Math.PI / 2, 0, 0); + Shape shape = nonRotatedTextLayout.getOutline(rot); + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + at.translate(xOffset, yOffset); + g.transform(at); + g.fill(shape); + g.setTransform(orig); + bounds = new Rectangle2D.Double(xOffset - nonRotatedRectangle.getHeight(), + yOffset - nonRotatedRectangle.getWidth(), + nonRotatedRectangle.getHeight() + chart.getStyler().getAxisTitlePadding(), + nonRotatedRectangle.getWidth()); + } else { + bounds = new Rectangle2D.Double(yAxis.getBounds().getX(), yAxis.getBounds().getY(),0, yAxis.getBounds().getHeight()); + } + } else { + + if (chart.getXAxisTitle() != null && + !chart.getXAxisTitle().trim().equalsIgnoreCase("") && + chart.getStyler().isXAxisTitleVisible()) { + + FontRenderContext frc = g.getFontRenderContext(); + TextLayout textLayout = new TextLayout(chart.getXAxisTitle(), chart.getStyler().getAxisTitleFont(), frc); + Rectangle2D rectangle = textLayout.getBounds(); + + double xOffset = chart.getXAxis().getBounds().getX() + + (chart.getXAxis().getBounds().getWidth() - rectangle.getWidth()) / 2.0; + double yOffset = chart.getXAxis().getBounds().getY() + + chart.getXAxis().getBounds().getHeight() + - rectangle.getHeight(); + + Shape shape = textLayout.getOutline(null); + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + at.translate((float) xOffset, (float) (yOffset - rectangle.getY())); + g.transform(at); + g.fill(shape); + g.setTransform(orig); + bounds = new Rectangle2D.Double(xOffset, yOffset - chart.getStyler().getAxisTitlePadding(), + rectangle.getWidth(), rectangle.getHeight() + chart.getStyler().getAxisTitlePadding()); + } else { + bounds = new Rectangle2D.Double(chart.getXAxis().getBounds().getX(), + chart.getXAxis().getBounds().getY() + chart.getXAxis().getBounds().getHeight(), + chart.getXAxis().getBounds().getWidth(), 0); + } + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/DataType.java b/chart/src/main/java/org/xbib/graphics/chart/axis/DataType.java new file mode 100644 index 0000000..e4b0bea --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/DataType.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.axis; + +public enum DataType { + Number, Instant, String; +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/Direction.java b/chart/src/main/java/org/xbib/graphics/chart/axis/Direction.java new file mode 100644 index 0000000..e886b47 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/Direction.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.axis; + +public enum Direction { + X, Y +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/TextAlignment.java b/chart/src/main/java/org/xbib/graphics/chart/axis/TextAlignment.java new file mode 100644 index 0000000..8e3b8a1 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/TextAlignment.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.axis; + +public enum TextAlignment { + Left, Centre, Right; +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/axis/YAxisPosition.java b/chart/src/main/java/org/xbib/graphics/chart/axis/YAxisPosition.java new file mode 100644 index 0000000..1e9ed0d --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/axis/YAxisPosition.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.axis; + +public enum YAxisPosition { + Left, Right +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleChart.java b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleChart.java new file mode 100644 index 0000000..0af3128 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleChart.java @@ -0,0 +1,204 @@ +package org.xbib.graphics.chart.bubble; + +import org.xbib.graphics.chart.axis.AxisPair; +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.plot.AxesChartPlot; +import org.xbib.graphics.chart.plot.ContentPlot; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyle; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyleCycler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import java.util.List; +import java.util.Map; + +public class BubbleChart extends Chart { + + public BubbleChart(int width, int height) { + super(width, height, new BubbleStyler()); + axisPair = new AxisPair<>(this); + plot = new BubblePlot<>(this); + legend = new BubbleLegend<>(this); + } + + public BubbleChart(int width, int height, Theme theme) { + this(width, height); + styler.setTheme(theme); + } + + public BubbleChart(BubbleChartBuilder chartBuilder) { + this(chartBuilder.getWidth(), chartBuilder.getHeight(), chartBuilder.getTheme()); + setTitle(chartBuilder.getTitle()); + setXAxisTitle(chartBuilder.xAxisTitle); + setYAxisTitle(chartBuilder.yAxisTitle); + } + + public BubbleSeries addSeries(String seriesName, + List xData, + List yData, + List bubbleData) { + sanityCheck(seriesName, xData, yData, bubbleData); + BubbleSeries series; + if (xData != null) { + if (xData.size() != yData.size()) { + throw new IllegalArgumentException("X and Y-Axis sizes are not the same"); + } + series = new BubbleSeries(seriesName, xData, yData, bubbleData); + } else { // generate xData + series = new BubbleSeries(seriesName, getGeneratedData(yData.size()), yData, bubbleData); + } + seriesMap.put(seriesName, series); + return series; + } + + private void sanityCheck(String seriesName, List xData, + List yData, + List bubbleData) { + if (seriesMap.containsKey(seriesName)) { + throw new IllegalArgumentException("Series name >" + + seriesName + + "< has already been used. Use unique names for each series"); + } + if (yData == null) { + throw new IllegalArgumentException("Y-Axis data cannot be null >" + seriesName); + } + if (yData.size() == 0) { + throw new IllegalArgumentException("Y-Axis data cannot be empty >" + seriesName); + } + if (bubbleData == null) { + throw new IllegalArgumentException("Bubble data cannot be null >" + seriesName); + } + if (bubbleData.size() == 0) { + throw new IllegalArgumentException("Bubble data cannot be empty >" + seriesName); + } + if (xData != null && xData.size() == 0) { + throw new IllegalArgumentException("X-Axis data cannot be empty >" + seriesName); + } + if (bubbleData.size() != yData.size()) { + throw new IllegalArgumentException("Bubble Data and Y-Axis sizes are not the same >" + seriesName); + } + } + + @Override + public void paint(Graphics2D g, int width, int height) { + setWidth(width); + setHeight(height); + for (BubbleSeries bubbleSeries : getSeriesMap().values()) { + BubbleSeriesRenderStyle seriesType = + bubbleSeries.getBubbleSeriesRenderStyle(); + if (seriesType == null) { + bubbleSeries.setBubbleSeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle()); + } + } + setSeriesStyles(); + paintBackground(g); + axisPair.paint(g); + plot.paint(g); + chartTitle.paint(g); + legend.paint(g); + } + + private void setSeriesStyles() { + SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler = + new SeriesColorMarkerLineStyleCycler( + getStyler().getSeriesColors(), + getStyler().getSeriesMarkers(), + getStyler().getSeriesLines()); + for (BubbleSeries series : getSeriesMap().values()) { + SeriesColorMarkerLineStyle seriesColorMarkerLineStyle = + seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle(); + if (series.getLineStyle() == null) { + series.setLineStyle(seriesColorMarkerLineStyle.getStroke()); + } + if (series.getLineColor() == null) { + series.setLineColor(seriesColorMarkerLineStyle.getColor()); + } + if (series.getFillColor() == null) { + series.setFillColor(seriesColorMarkerLineStyle.getColor()); + } + } + } + + private static class BubblePlot extends AxesChartPlot { + + private BubblePlot(Chart chart) { + super(chart); + this.contentPlot = new BubbleContentPlot<>(chart); + } + } + + private static class BubbleContentPlot extends ContentPlot { + + private final ST stylerBubble; + + private BubbleContentPlot(Chart chart) { + super(chart); + stylerBubble = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double xTickSpace = stylerBubble.getPlotContentSize() * getBounds().getWidth(); + double xLeftMargin = ((int) getBounds().getWidth() - xTickSpace) / 2.0; + double yTickSpace = stylerBubble.getPlotContentSize() * getBounds().getHeight(); + double yTopMargin = ((int) getBounds().getHeight() - yTickSpace) / 2.0; + double xMin = chart.getXAxis().getMin(); + double xMax = chart.getXAxis().getMax(); + if (stylerBubble.isXAxisLogarithmic()) { + xMin = Math.log10(xMin); + xMax = Math.log10(xMax); + } + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (!series.isEnabled()) { + continue; + } + double yMin = chart.getYAxis(series.getYAxisGroup()).getMin(); + double yMax = chart.getYAxis(series.getYAxisGroup()).getMax(); + if (stylerBubble.isYAxisLogarithmic()) { + yMin = Math.log10(yMin); + yMax = Math.log10(yMax); + } + for (int i = 0; i < series.getXData().size(); i++) { + Double x = (Double) series.getXData().get(i); + if (stylerBubble.isXAxisLogarithmic()) { + x = Math.log10(x); + } + if (Double.isNaN((Double)series.getYData().get(i))) { + continue; + } + Double yOrig = (Double) series.getYData().get(i); + double y; + if (stylerBubble.isYAxisLogarithmic()) { + y = Math.log10(yOrig); + } else { + y = yOrig; + } + double xTransform = xLeftMargin + ((x - xMin) / (xMax - xMin) * xTickSpace); + double yTransform = + getBounds().getHeight() - (yTopMargin + (y - yMin) / (yMax - yMin) * yTickSpace); + if (Math.abs(xMax - xMin) / 5 == 0.0) { + xTransform = getBounds().getWidth() / 2.0; + } + if (Math.abs(yMax - yMin) / 5 == 0.0) { + yTransform = getBounds().getHeight() / 2.0; + } + double xOffset = getBounds().getX() + xTransform; + double yOffset = getBounds().getY() + yTransform; + if (series.getExtraValues() != null) { + Double bubbleSize = (Double) series.getExtraValues().get(i); + Shape bubble; + bubble = new Ellipse2D.Double(xOffset - bubbleSize / 2, yOffset - bubbleSize / 2, bubbleSize, bubbleSize); + g.setColor(series.getFillColor()); + g.fill(bubble); + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + g.draw(bubble); + } + } + } + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleChartBuilder.java b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleChartBuilder.java new file mode 100644 index 0000000..0cee777 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleChartBuilder.java @@ -0,0 +1,27 @@ +package org.xbib.graphics.chart.bubble; + +import org.xbib.graphics.chart.ChartBuilder; + +public class BubbleChartBuilder extends ChartBuilder { + + String xAxisTitle = ""; + String yAxisTitle = ""; + + public BubbleChartBuilder() { + } + + public BubbleChartBuilder xAxisTitle(String xAxisTitle) { + this.xAxisTitle = xAxisTitle; + return this; + } + + public BubbleChartBuilder yAxisTitle(String yAxisTitle) { + this.yAxisTitle = yAxisTitle; + return this; + } + + @Override + public BubbleChart build() { + return new BubbleChart(this); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleLegend.java b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleLegend.java new file mode 100644 index 0000000..2074f44 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleLegend.java @@ -0,0 +1,68 @@ +package org.xbib.graphics.chart.bubble; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.legend.AbstractLegend; +import org.xbib.graphics.chart.series.AxesChartSeries; +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.legend.LegendLayout; +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +public class BubbleLegend extends AbstractLegend { + + private final ST axesChartStyler; + + public BubbleLegend(Chart chart) { + super(chart); + axesChartStyler = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double startx = xOffset + chart.getStyler().getLegendPadding(); + double starty = yOffset + chart.getStyler().getLegendPadding(); + Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (series.isNotShownInLegend()) { + continue; + } + if (!series.isEnabled()) { + continue; + } + Map seriesTextBounds = getSeriesTextBounds(series); + float legendEntryHeight = getLegendEntryHeight(seriesTextBounds, BOX_SIZE); + Shape rectSmall = new Ellipse2D.Double(startx, starty, BOX_SIZE, BOX_SIZE); + g.setColor(series.getFillColor()); + g.fill(rectSmall); + g.setStroke(series.getLineStyle()); + g.setColor(series.getLineColor()); + g.draw(rectSmall); + final double x = startx + BOX_SIZE + chart.getStyler().getLegendPadding(); + paintSeriesText(g, seriesTextBounds, BOX_SIZE, x, starty); + if (chart.getStyler().getLegendLayout() == LegendLayout.Vertical) { + starty += legendEntryHeight + chart.getStyler().getLegendPadding(); + } else { + int markerWidth = BOX_SIZE; + if (series.getLegendRenderType() == LegendRenderType.Line) { + markerWidth = chart.getStyler().getLegendSeriesLineLength(); + } + float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth); + startx += legendEntryWidth + chart.getStyler().getLegendPadding(); + } + } + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint); + } + + @Override + public double getSeriesLegendRenderGraphicHeight(S series) { + return series.getLegendRenderType() == LegendRenderType.Box ? BOX_SIZE : axesChartStyler.getMarkerSize(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleSeries.java b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleSeries.java new file mode 100644 index 0000000..808a476 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleSeries.java @@ -0,0 +1,33 @@ +package org.xbib.graphics.chart.bubble; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.series.NoMarkersSeries; +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.util.List; + +/** + * A Series containing X, Y and bubble size data to be plotted on a Chart. + */ +public class BubbleSeries extends NoMarkersSeries { + + private BubbleSeriesRenderStyle bubbleSeriesRenderStyle = null; + + public BubbleSeries(String name, List xData, List yData, List bubbleSizes) { + super(name, xData, yData, bubbleSizes, DataType.Number); + } + + public BubbleSeriesRenderStyle getBubbleSeriesRenderStyle() { + return bubbleSeriesRenderStyle; + } + + public void setBubbleSeriesRenderStyle(BubbleSeriesRenderStyle bubbleSeriesRenderStyle) { + this.bubbleSeriesRenderStyle = bubbleSeriesRenderStyle; + } + + @Override + public LegendRenderType getLegendRenderType() { + return bubbleSeriesRenderStyle.getLegendRenderType(); + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleSeriesRenderStyle.java b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleSeriesRenderStyle.java new file mode 100644 index 0000000..359d4ff --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleSeriesRenderStyle.java @@ -0,0 +1,19 @@ +package org.xbib.graphics.chart.bubble; + +import org.xbib.graphics.chart.legend.LegendRenderable; +import org.xbib.graphics.chart.legend.LegendRenderType; + +public enum BubbleSeriesRenderStyle implements LegendRenderable { + Round(LegendRenderType.Box); + + private final LegendRenderType legendRenderType; + + BubbleSeriesRenderStyle(LegendRenderType legendRenderType) { + this.legendRenderType = legendRenderType; + } + + @Override + public LegendRenderType getLegendRenderType() { + return legendRenderType; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleStyler.java b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleStyler.java new file mode 100644 index 0000000..dbd4bd3 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/bubble/BubbleStyler.java @@ -0,0 +1,44 @@ +package org.xbib.graphics.chart.bubble; + +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.theme.Theme; + +public class BubbleStyler extends AxesChartStyler { + + private BubbleSeriesRenderStyle bubbleChartSeriesRenderStyle; + + public BubbleStyler() { + this.setAllStyles(); + super.setAllStyles(); + } + + @Override + protected void setAllStyles() { + bubbleChartSeriesRenderStyle = BubbleSeriesRenderStyle.Round; // set default to Round + } + + public BubbleSeriesRenderStyle getDefaultSeriesRenderStyle() { + return bubbleChartSeriesRenderStyle; + } + + /** + * Sets the default series render style for the chart (Round is the only one for now) You can + * override the series render style individually on each Series object. + * + * @param bubbleChartSeriesRenderStyle render style + */ + public BubbleStyler setDefaultSeriesRenderStyle(BubbleSeriesRenderStyle bubbleChartSeriesRenderStyle) { + this.bubbleChartSeriesRenderStyle = bubbleChartSeriesRenderStyle; + return this; + } + + /** + * Set the theme the styler should use + * + * @param theme theme + */ + public void setTheme(Theme theme) { + this.theme = theme; + super.setAllStyles(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/category/CategoryChart.java b/chart/src/main/java/org/xbib/graphics/chart/category/CategoryChart.java new file mode 100644 index 0000000..113db2d --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/category/CategoryChart.java @@ -0,0 +1,706 @@ +package org.xbib.graphics.chart.category; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.axis.Axis; +import org.xbib.graphics.chart.axis.AxisPair; +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.legend.MarkerLegend; +import org.xbib.graphics.chart.plot.AxesChartPlot; +import org.xbib.graphics.chart.plot.ContentPlot; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyle; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyleCycler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.xbib.graphics.chart.category.CategorySeriesRenderStyle.SteppedBar; + +public class CategoryChart extends Chart { + + public CategoryChart(int width, int height) { + super(width, height, new CategoryStyler()); + axisPair = new AxisPair<>(this); + plot = new CategoryPlot<>(this); + legend = new MarkerLegend<>(this); + } + + public CategoryChart(int width, int height, Theme theme) { + this(width, height); + styler.setTheme(theme); + } + + public CategoryChart(CategoryChartBuilder chartBuilder) { + this(chartBuilder.getWidth(), chartBuilder.getHeight(), chartBuilder.getTheme()); + setTitle(chartBuilder.getTitle()); + setXAxisTitle(chartBuilder.getxAxisTitle()); + setYAxisTitle(chartBuilder.getyAxisTitle()); + } + + /** + * Add a series for a Category type chart using using double arrays + * + * @param seriesName series name + * @param xData the X-Axis data + * @param yData the Y-Axis data + * @return A Series object that you can set properties on + */ + public CategorySeries addSeries(String seriesName, double[] xData, double[] yData) { + return addSeries(seriesName, xData, yData, null); + } + + /** + * Add a series for a Category type chart using using double arrays with error bars + * + * @param seriesName series name + * @param xData the X-Axis data + * @param yData the Y-Axis data + * @param errorBars the error bar data + * @return A Series object that you can set properties on + */ + public CategorySeries addSeries(String seriesName, double[] xData, double[] yData, double[] errorBars) { + return addSeries(seriesName, listFromDoubleArray(xData), listFromDoubleArray(yData), + listFromDoubleArray(errorBars)); + } + + /** + * Add a series for a X-Y type chart using using int arrays + * + * @param seriesName series name + * @param xData the X-Axis data + * @param yData the Y-Axis data + * @return A Series object that you can set properties on + */ + public CategorySeries addSeries(String seriesName, int[] xData, int[] yData) { + return addSeries(seriesName, xData, yData, null); + } + + /** + * Add a series for a X-Y type chart using using int arrays with error bars + * + * @param seriesName series name + * @param xData the X-Axis data + * @param yData the Y-Axis data + * @param errorBars the error bar data + * @return A Series object that you can set properties on + */ + public CategorySeries addSeries(String seriesName, int[] xData, int[] yData, int[] errorBars) { + return addSeries(seriesName, listFromIntArray(xData), listFromIntArray(yData), + listFromIntArray(errorBars)); + } + + /** + * Add a series for a Category type chart using Lists + * + * @param seriesName series name + * @param xData the X-Axis data + * @param yData the Y-Axis data + * @return A Series object that you can set properties on + */ + public CategorySeries addSeries(String seriesName, List xData, List yData) { + return addSeries(seriesName, xData, yData, null); + } + + /** + * Add a series for a Category type chart using Lists with error bars + * + * @param seriesName series name + * @param xData the X-Axis data + * @param yData the Y-Axis data + * @param errorBars the error bar data + * @return A Series object that you can set properties on + */ + public CategorySeries addSeries(String seriesName, + List xData, + List yData, + List errorBars) { + sanityCheck(seriesName, xData, yData, errorBars); + CategorySeries series; + if (xData != null) { + if (xData.size() != yData.size()) { + throw new IllegalArgumentException("X and Y-Axis sizes are not the same"); + } + series = new CategorySeries(seriesName, xData, yData, errorBars, getDataType(xData)); + } else { + series = new CategorySeries(seriesName, getGeneratedData(yData.size()), yData, errorBars, DataType.String); + } + seriesMap.put(seriesName, series); + return series; + } + + private void sanityCheck(String seriesName, List xData, List yData, + List errorBars) { + if (seriesMap.containsKey(seriesName)) { + throw new IllegalArgumentException("Series name >" + seriesName + "< has already been used"); + } + if (yData == null) { + throw new IllegalArgumentException("Y-Axis data cannot be null"); + } + if (yData.size() == 0) { + throw new IllegalArgumentException("Y-Axis data cannot be empty"); + } + if (xData != null && xData.size() == 0) { + throw new IllegalArgumentException("X-Axis data cannot be empty"); + } + if (errorBars != null && errorBars.size() != yData.size()) { + throw new IllegalArgumentException("Error bars and Y-Axis sizes are not the same"); + } + } + + @Override + public void paint(Graphics2D g, int width, int height) { + setWidth(width); + setHeight(height); + for (CategorySeries categorySeries : getSeriesMap().values()) { + CategorySeriesRenderStyle seriesType = categorySeries.getCategorySeriesRenderStyle(); + if (seriesType == null) { + categorySeries.setCategorySeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle()); + } + } + setSeriesStyles(); + paintBackground(g); + axisPair.paint(g); + plot.paint(g); + chartTitle.paint(g); + legend.paint(g); + } + + public void setSeriesStyles() { + SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler = + new SeriesColorMarkerLineStyleCycler(getStyler().getSeriesColors(), + getStyler().getSeriesMarkers(), getStyler().getSeriesLines()); + for (CategorySeries series : getSeriesMap().values()) { + SeriesColorMarkerLineStyle seriesColorMarkerLineStyle = + seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle(); + if (series.getLineStyle() == null) { + series.setLineStyle(seriesColorMarkerLineStyle.getStroke()); + } + if (series.getLineColor() == null) { + series.setLineColor(seriesColorMarkerLineStyle.getColor()); + } + if (series.getFillColor() == null) { + series.setFillColor(seriesColorMarkerLineStyle.getColor()); + } + if (series.getMarker() == null) { + series.setMarker(seriesColorMarkerLineStyle.getMarker()); + } + if (series.getMarkerColor() == null) { + series.setMarkerColor(seriesColorMarkerLineStyle.getColor()); + } + } + } + + private DataType getDataType(List data) { + DataType axisType; + Iterator itr = data.iterator(); + Object dataPoint = itr.next(); + if (dataPoint instanceof Number) { + axisType = DataType.Number; + } else if (dataPoint instanceof Instant) { + axisType = DataType.Instant; + } else if (dataPoint instanceof String) { + axisType = DataType.String; + } else { + throw new IllegalArgumentException("Series data must be either Number, Instant or String type"); + } + return axisType; + } + + private static class CategoryPlot extends AxesChartPlot { + + private final ST categoryStyler; + + private CategoryPlot(Chart chart) { + super(chart); + categoryStyler = chart.getStyler(); + } + + @Override + public void paint(Graphics2D g) { + if (CategorySeriesRenderStyle.Bar.equals(categoryStyler.getDefaultSeriesRenderStyle()) || CategorySeriesRenderStyle.Stick.equals(categoryStyler.getDefaultSeriesRenderStyle())) { + this.contentPlot = new ContentPlotCategoryBar<>(chart); + } else { + this.contentPlot = new ContentPlotCategoryLineAreaScatter<>(chart); + } + super.paint(g); + } + } + + private static class ContentPlotCategoryBar extends ContentPlot { + + private final ST stylerCategory; + + ContentPlotCategoryBar(Chart chart) { + super(chart); + this.stylerCategory = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double xTickSpace = stylerCategory.getPlotContentSize() * getBounds().getWidth(); + double xLeftMargin = (getBounds().getWidth() - xTickSpace) / 2.0; + Map seriesMap = chart.getSeriesMap(); + int numCategories = seriesMap.values().iterator().next().getXData().size(); + double gridStep = xTickSpace / numCategories; + double yMin = chart.getYAxis().getMin(); + double yMax = chart.getYAxis().getMax(); + int chartForm; // 1=positive, -1=negative, 0=span + if (yMin > 0.0 && yMax > 0.0) { + chartForm = 1; // positive chart + } else if (yMin < 0.0 && yMax < 0.0) { + chartForm = -1; // negative chart + } else { + chartForm = 0;// span chart + } + double yTickSpace = stylerCategory.getPlotContentSize() * getBounds().getHeight(); + double yTopMargin = (getBounds().getHeight() - yTickSpace) / 2.0; + int seriesCounter = 0; + double[] accumulatedStackOffsetPos = new double[numCategories]; + double[] accumulatedStackOffsetNeg = new double[numCategories]; + for (S series : seriesMap.values()) { + double previousX = -Double.MAX_VALUE; + double previousY = -Double.MAX_VALUE; + Iterator yItr = series.getYData().iterator(); + Iterator ebItr = null; + Collection errorBars = series.getExtraValues(); + if (errorBars != null) { + ebItr = errorBars.iterator(); + } + List steppedPath = null; + List steppedReturnPath = null; + int categoryCounter = 0; + while (yItr.hasNext()) { + Number next = yItr.next(); + if (next == null) { + previousX = -Double.MAX_VALUE; + previousY = -Double.MAX_VALUE; + categoryCounter++; + continue; + } + double y = next.doubleValue(); + double yTop = 0.0; + double yBottom = 0.0; + switch (chartForm) { + case 1: // positive chart + // check for points off the chart draw area due to a custom yMin + if (y < yMin) { + categoryCounter++; + continue; + } + yTop = y; + yBottom = yMin; + break; + case -1: // negative chart + // check for points off the chart draw area due to a custom yMin + if (y > yMax) { + categoryCounter++; + continue; + } + yTop = yMax; + yBottom = y; + break; + case 0: // span chart + if (y >= 0.0) { // positive + yTop = y; + if (series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Bar + || series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Stick + || series.getCategorySeriesRenderStyle() + == SteppedBar) { + yBottom = 0.0; + } else { + yBottom = y; + } + if (stylerCategory.isStacked()) { + yTop += accumulatedStackOffsetPos[categoryCounter]; + yBottom += accumulatedStackOffsetPos[categoryCounter]; + accumulatedStackOffsetPos[categoryCounter] += (yTop - yBottom); + } + } else { + if (series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Bar + || series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Stick + || series.getCategorySeriesRenderStyle() + == SteppedBar) { + yTop = 0.0; + } else { + yTop = y; // yTransform uses yTop, and for non-bars and stick, it's the same as + // yBottom. + } + yBottom = y; + if (stylerCategory.isStacked()) { + yTop -= accumulatedStackOffsetNeg[categoryCounter]; + yBottom -= accumulatedStackOffsetNeg[categoryCounter]; + accumulatedStackOffsetNeg[categoryCounter] += (yTop - yBottom); + } + } + break; + default: + break; + } + + double yTransform = getBounds().getHeight() - (yTopMargin + (yTop - yMin) / (yMax - yMin) * yTickSpace); + double yOffset = getBounds().getY() + yTransform; + + double zeroTransform = getBounds().getHeight() - (yTopMargin + (yBottom - yMin) / (yMax - yMin) * yTickSpace); + double zeroOffset = getBounds().getY() + zeroTransform; + double xOffset; + double barWidth; + + { + double barWidthPercentage = stylerCategory.getAvailableSpaceFill(); + // SteppedBars can not have any space between them + if (series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.SteppedBar) + barWidthPercentage = 1; + + if (stylerCategory.isOverlapped() || stylerCategory.isStacked()) { + + barWidth = gridStep * barWidthPercentage; + double barMargin = gridStep * (1 - barWidthPercentage) / 2; + xOffset = getBounds().getX() + xLeftMargin + gridStep * categoryCounter++ + barMargin; + } else { + + barWidth = gridStep / chart.getSeriesMap().size() * barWidthPercentage; + double barMargin = gridStep * (1 - barWidthPercentage) / 2; + xOffset = + getBounds().getX() + + xLeftMargin + + gridStep * categoryCounter++ + + seriesCounter * barWidth + + barMargin; + } + } + + // SteppedBar. Partially drawn in loop, partially after loop. + if (series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.SteppedBar) { + + double yCenter = zeroOffset; + double yTip = yOffset; + double stepLength = gridStep; + + // yTip should be the value end, yCenter the center (0) end. + if (y < 0) { + + yTip = zeroOffset; + yCenter = yOffset; + } + + // Init in first iteration + if (steppedPath == null) { + steppedPath = new ArrayList<>(); + steppedReturnPath = new ArrayList<>(); + steppedPath.add(new Point2D.Double(xOffset, yCenter)); + } else if (stylerCategory.isStacked()) { + // If a section of a stacked graph has changed from positive + // to negative or vice-versa, draw what we've stored up so far + // and resume with a blank slate. + if ((previousY > 0 && y < 0) || (previousY < 0 && y > 0)) { + drawStepBar(g, series, steppedPath, steppedReturnPath); + + steppedPath.clear(); + steppedReturnPath.clear(); + steppedPath.add(new Point2D.Double(xOffset, yCenter)); + } + } + + if (!yItr.hasNext()) { + + // Shift the far point of the final bar backwards + // by the same amount its start was shifted forward. + if (!(stylerCategory.isOverlapped() || stylerCategory.isStacked())) { + + double singleBarStep = stepLength / (double) chart.getSeriesMap().size(); + stepLength -= (seriesCounter * singleBarStep); + } + } + + // Draw the vertical line to the new y position, and the horizontal flat of the bar. + steppedPath.add(new Point2D.Double(xOffset, yTip)); + steppedPath.add(new Point2D.Double(xOffset + stepLength, yTip)); + + // Add the corresponding centerline (or equivalent) to the return path + // Could be simplfied and removed for non-stacked graphs + steppedReturnPath.add(new Point2D.Double(xOffset, yCenter)); + steppedReturnPath.add(new Point2D.Double(xOffset + stepLength, yCenter)); + + previousY = y; + } + + // paint series + if (series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Bar) { + + // paint bar + Path2D.Double path = new Path2D.Double(); + path.moveTo(xOffset, yOffset); + path.lineTo(xOffset + barWidth, yOffset); + path.lineTo(xOffset + barWidth, zeroOffset); + path.lineTo(xOffset, zeroOffset); + path.closePath(); + + g.setColor(series.getFillColor()); + g.fill(path); + } else if (CategorySeriesRenderStyle.Stick.equals(series.getCategorySeriesRenderStyle())) { + if (series.getLineStyle() != Theme.Series.NONE_STROKE) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + Shape line = new Line2D.Double(xOffset, zeroOffset, xOffset, yOffset); + g.draw(line); + } + + // paint marker + if (series.getMarker() != null) { + g.setColor(series.getMarkerColor()); + + if (y <= 0) { + series.getMarker().paint(g, xOffset, zeroOffset, stylerCategory.getMarkerSize()); + } else { + series.getMarker().paint(g, xOffset, yOffset, stylerCategory.getMarkerSize()); + } + } + } else { + if (series.getCategorySeriesRenderStyle() == CategorySeriesRenderStyle.Line) { + if (series.getLineStyle() != Theme.Series.NONE_STROKE) { + if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + Shape line = new Line2D.Double(previousX, previousY, xOffset + barWidth / 2, yOffset); + g.draw(line); + } + } + } + previousX = xOffset + barWidth / 2; + previousY = yOffset; + + // paint marker + if (series.getMarker() != null) { + g.setColor(series.getMarkerColor()); + series.getMarker().paint(g, previousX, previousY, stylerCategory.getMarkerSize()); + } + + } + // paint error bars + if (errorBars != null) { + double eb = ebItr.next().doubleValue(); + // set error bar style + if (stylerCategory.isErrorBarsColorSeriesColor()) { + g.setColor(series.getLineColor()); + } else { + g.setColor(stylerCategory.getErrorBarsColor()); + } + g.setStroke(Theme.Strokes.ERROR_BARS); + + // Top value + double errorBarLength = ((eb) / (yMax - yMin) * yTickSpace); + double topEBOffset = yOffset - errorBarLength; + + // Bottom value + double bottomEBOffset = yOffset + errorBarLength; + + // Draw it + double errorBarOffset = xOffset + barWidth / 2; + Shape line = new Line2D.Double(errorBarOffset, topEBOffset, errorBarOffset, bottomEBOffset); + g.draw(line); + line = new Line2D.Double(errorBarOffset - 3, bottomEBOffset, errorBarOffset + 3, bottomEBOffset); + g.draw(line); + line = new Line2D.Double(errorBarOffset - 3, topEBOffset, errorBarOffset + 3, topEBOffset); + g.draw(line); + } + + } + // Final drawing of a steppedBar is done after the main loop, + // as it continues on null and we may end up missing the final iteration. + if (steppedPath != null && !steppedReturnPath.isEmpty()) { + drawStepBar(g, series, steppedPath, steppedReturnPath); + } + seriesCounter++; + } + } + + private void drawStepBarLine(Graphics2D g, S series, Path2D.Double path) { + if (series.getLineColor() != null) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + g.draw(path); + } + } + + private void drawStepBarFill(Graphics2D g, S series, Path2D.Double path) { + if (series.getFillColor() != null) { + g.setColor(series.getFillColor()); + g.fill(path); + } + } + + private void drawStepBar(Graphics2D g, S series, List path, List returnPath) { + Collections.reverse(returnPath); + returnPath.remove(returnPath.size() - 1); + path.addAll(returnPath); + Path2D.Double drawPath = new Path2D.Double(); + Point2D.Double startPoint = path.remove(0); + drawPath.moveTo(startPoint.getX(), startPoint.getY()); + for (Point2D.Double currentPoint : path) { + drawPath.lineTo(currentPoint.getX(), currentPoint.getY()); + } + drawStepBarFill(g, series, drawPath); + drawPath.reset(); + drawPath.moveTo(startPoint.getX(), startPoint.getY()); + List linePath = path.subList(0, path.size() - returnPath.size() + 1); + for (Point2D.Double currentPoint : linePath) { + drawPath.lineTo(currentPoint.getX(), currentPoint.getY()); + } + drawStepBarLine(g, series, drawPath); + } + } + + private static class ContentPlotCategoryLineAreaScatter extends ContentPlot { + + private final ST categoryStyler; + + protected ContentPlotCategoryLineAreaScatter(Chart chart) { + super(chart); + this.categoryStyler = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double xTickSpace = categoryStyler.getPlotContentSize() * getBounds().getWidth(); + double xLeftMargin = ((int) getBounds().getWidth() - xTickSpace) / 2.0; + double yTickSpace = categoryStyler.getPlotContentSize() * getBounds().getHeight(); + double yTopMargin =((int) getBounds().getHeight() - yTickSpace) / 2.0; + Map seriesMap = chart.getSeriesMap(); + int numCategories = seriesMap.values().iterator().next().getXData().size(); + double gridStep = xTickSpace / numCategories; + for (S series : seriesMap.values()) { + if (!series.isEnabled()) { + continue; + } + Axis yAxis = chart.getYAxis(series.getYAxisGroup()); + double yMin = yAxis.getMin(); + double yMax = yAxis.getMax(); + if (categoryStyler.isYAxisLogarithmic()) { + yMin = Math.log10(yMin); + yMax = Math.log10(yMax); + } + Collection yData = series.getYData(); + double previousX = -Double.MAX_VALUE; + double previousY = -Double.MAX_VALUE; + Iterator yItr = yData.iterator(); + Iterator ebItr = null; + Collection errorBars = series.getExtraValues(); + if (errorBars != null) { + ebItr = errorBars.iterator(); + } + Path2D.Double path = null; + int categoryCounter = 0; + while (yItr.hasNext()) { + Number next = yItr.next(); + if (next == null) { + // for area charts + closePath(g, path, previousX, yTopMargin); + path = null; + previousX = -Double.MAX_VALUE; + previousY = -Double.MAX_VALUE; + continue; + } + double yOrig = next.doubleValue(); + double y; + if (categoryStyler.isYAxisLogarithmic()) { + y = Math.log10(yOrig); + } else { + y = yOrig; + } + double yTransform = getBounds().getHeight() - (yTopMargin + (y - yMin) / (yMax - yMin) * yTickSpace); + if (Math.abs(yMax - yMin) / 5 == 0.0) { + yTransform = getBounds().getHeight() / 2.0; + } + double xOffset = getBounds().getX() + xLeftMargin + categoryCounter++ * gridStep + gridStep / 2; + double yOffset = getBounds().getY() + yTransform; + if (CategorySeriesRenderStyle.Line.equals(series.getCategorySeriesRenderStyle()) || + CategorySeriesRenderStyle.Area.equals(series.getCategorySeriesRenderStyle())) { + if (series.getLineStyle() != Theme.Series.NONE_STROKE) { + if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + Shape line = new Line2D.Double(previousX, previousY, xOffset, yOffset); + g.draw(line); + } + } + } + if (CategorySeriesRenderStyle.Area.equals(series.getCategorySeriesRenderStyle())) { + if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) { + g.setColor(series.getFillColor()); + double yBottomOfArea = getBounds().getY() + getBounds().getHeight() - yTopMargin; + if (path == null) { + path = new Path2D.Double(); + path.moveTo(previousX, yBottomOfArea); + path.lineTo(previousX, previousY); + } + path.lineTo(xOffset, yOffset); + } + if (xOffset < previousX) { + throw new RuntimeException("X-Data must be in ascending order for Area Charts"); + } + } + if (CategorySeriesRenderStyle.Stick.equals(series.getCategorySeriesRenderStyle())) { + if (series.getLineStyle() != Theme.Series.NONE_STROKE) { + double yBottomOfArea = getBounds().getY() + getBounds().getHeight() - yTopMargin; + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + Shape line = new Line2D.Double(xOffset, yBottomOfArea, xOffset, yOffset); + g.draw(line); + } + } + previousX = xOffset; + previousY = yOffset; + if (series.getMarker() != null) { + g.setColor(series.getMarkerColor()); + series.getMarker().paint(g, xOffset, yOffset, categoryStyler.getMarkerSize()); + } + if (errorBars != null) { + double eb = ebItr.next().doubleValue(); + if (categoryStyler.isErrorBarsColorSeriesColor()) { + g.setColor(series.getLineColor()); + } else { + g.setColor(categoryStyler.getErrorBarsColor()); + } + g.setStroke(Theme.Strokes.ERROR_BARS); + double topValue; + if (categoryStyler.isYAxisLogarithmic()) { + topValue = yOrig + eb; + topValue = Math.log10(topValue); + } else { + topValue = y + eb; + } + double topEBTransform = getBounds().getHeight() - (yTopMargin + (topValue - yMin) / (yMax - yMin) * yTickSpace); + double topEBOffset = getBounds().getY() + topEBTransform; + double bottomValue; + if (categoryStyler.isYAxisLogarithmic()) { + bottomValue = yOrig - eb; + bottomValue = Math.log10(bottomValue); + } else { + bottomValue = y - eb; + } + double bottomEBTransform = getBounds().getHeight() - (yTopMargin + (bottomValue - yMin) / (yMax - yMin) * yTickSpace); + double bottomEBOffset = getBounds().getY() + bottomEBTransform; + Shape line = new Line2D.Double(xOffset, topEBOffset, xOffset, bottomEBOffset); + g.draw(line); + line = new Line2D.Double(xOffset - 3, bottomEBOffset, xOffset + 3, bottomEBOffset); + g.draw(line); + line = new Line2D.Double(xOffset - 3, topEBOffset, xOffset + 3, topEBOffset); + g.draw(line); + } + } + g.setColor(series.getFillColor()); + closePath(g, path, previousX, yTopMargin); + } + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/category/CategoryChartBuilder.java b/chart/src/main/java/org/xbib/graphics/chart/category/CategoryChartBuilder.java new file mode 100644 index 0000000..f36b48c --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/category/CategoryChartBuilder.java @@ -0,0 +1,40 @@ +package org.xbib.graphics.chart.category; + +import org.xbib.graphics.chart.ChartBuilder; + +public class CategoryChartBuilder extends ChartBuilder { + + private String xAxisTitle = ""; + private String yAxisTitle = ""; + + public CategoryChartBuilder() { + } + + public CategoryChartBuilder xAxisTitle(String xAxisTitle) { + this.xAxisTitle = xAxisTitle; + return this; + } + + public CategoryChartBuilder yAxisTitle(String yAxisTitle) { + this.yAxisTitle = yAxisTitle; + return this; + } + + public String getxAxisTitle() { + return xAxisTitle; + } + + public String getyAxisTitle() { + return yAxisTitle; + } + + /** + * return fully built Chart_Category + * + * @return a Chart_Category + */ + @Override + public CategoryChart build() { + return new CategoryChart(this); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/category/CategorySeries.java b/chart/src/main/java/org/xbib/graphics/chart/category/CategorySeries.java new file mode 100644 index 0000000..71d3bb4 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/category/CategorySeries.java @@ -0,0 +1,36 @@ +package org.xbib.graphics.chart.category; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.series.AxesChartSeriesCategory; +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.util.List; + +/** + * A Series containing category data to be plotted on a Chart. + */ +public class CategorySeries extends AxesChartSeriesCategory { + + private CategorySeriesRenderStyle categorySeriesRenderStyle = null; + + public CategorySeries(String name, List xData, + List yData, + List errorBars, + DataType axisType) { + super(name, xData, yData, errorBars, axisType); + } + + public CategorySeriesRenderStyle getCategorySeriesRenderStyle() { + return categorySeriesRenderStyle; + } + + public void setCategorySeriesRenderStyle(CategorySeriesRenderStyle chartXYSeriesRenderStyle) { + this.categorySeriesRenderStyle = chartXYSeriesRenderStyle; + } + + @Override + public LegendRenderType getLegendRenderType() { + return categorySeriesRenderStyle.getLegendRenderType(); + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/category/CategorySeriesRenderStyle.java b/chart/src/main/java/org/xbib/graphics/chart/category/CategorySeriesRenderStyle.java new file mode 100644 index 0000000..4ef08f6 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/category/CategorySeriesRenderStyle.java @@ -0,0 +1,30 @@ +package org.xbib.graphics.chart.category; + +import org.xbib.graphics.chart.legend.LegendRenderable; +import org.xbib.graphics.chart.legend.LegendRenderType; + +public enum CategorySeriesRenderStyle implements LegendRenderable { + + Line(LegendRenderType.Line), + + Area(LegendRenderType.Line), + + Scatter(LegendRenderType.Scatter), + + Bar(LegendRenderType.BoxNoOutline), + + SteppedBar(LegendRenderType.Box), + + Stick(LegendRenderType.Line); + + private final LegendRenderType legendRenderType; + + CategorySeriesRenderStyle(LegendRenderType legendRenderType) { + this.legendRenderType = legendRenderType; + } + + @Override + public LegendRenderType getLegendRenderType() { + return legendRenderType; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/category/CategoryStyler.java b/chart/src/main/java/org/xbib/graphics/chart/category/CategoryStyler.java new file mode 100644 index 0000000..31183f8 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/category/CategoryStyler.java @@ -0,0 +1,96 @@ +package org.xbib.graphics.chart.category; + +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.theme.Theme; + +public class CategoryStyler extends AxesChartStyler { + + private CategorySeriesRenderStyle categorySeriesRenderStyle; + + private double availableSpaceFill; + private boolean isOverlapped; + private boolean isStacked; + + public CategoryStyler() { + + this.setAllStyles(); + super.setAllStyles(); + } + + @Override + protected void setAllStyles() { + + this.categorySeriesRenderStyle = CategorySeriesRenderStyle.Bar; // set default to bar + + availableSpaceFill = theme.getAvailableSpaceFill(); + isOverlapped = theme.isOverlapped(); + } + + /** + * Sets the available space for rendering each category as a percentage. For a bar chart with one + * series, it will be the width of the bar as a percentage of the maximum space alloted for the + * bar. If there are three series and three bars, the three bars will share the available space. + * This affects all category series render types, not only bar charts. Full width is 100%, i.e. + * 1.0 + * + * @param availableSpaceFill space fill + */ + public void setAvailableSpaceFill(double availableSpaceFill) { + this.availableSpaceFill = availableSpaceFill; + } + + public double getAvailableSpaceFill() { + + return availableSpaceFill; + } + + /** + * Sets the default series render style for the chart (bar, stick, line, scatter, area, etc.) You can override the + * series render style individually on each Series object. + * + * @param categorySeriesRenderStyle render style + */ + public void setDefaultSeriesRenderStyle(CategorySeriesRenderStyle categorySeriesRenderStyle) { + this.categorySeriesRenderStyle = categorySeriesRenderStyle; + } + + public CategorySeriesRenderStyle getDefaultSeriesRenderStyle() { + return categorySeriesRenderStyle; + } + + public boolean isOverlapped() { + return isOverlapped; + } + + /** + * set whether or no bars are overlapped. Otherwise they are places side-by-side + * + * @param isOverlapped overlapped + */ + public void setOverlapped(boolean isOverlapped) { + this.isOverlapped = isOverlapped; + } + + public boolean isStacked() { + return isStacked; + } + + /** + * Set whether or not series renderings (i.e. bars, stick, etc.) are stacked. + * + * @param isStacked stacked + */ + public void setStacked(boolean isStacked) { + this.isStacked = isStacked; + } + + /** + * Set the theme the styler should use + * + * @param theme theme + */ + protected void setTheme(Theme theme) { + this.theme = theme; + super.setAllStyles(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/formatter/DateFormatter.java b/chart/src/main/java/org/xbib/graphics/chart/formatter/DateFormatter.java new file mode 100644 index 0000000..9308f22 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/formatter/DateFormatter.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.chart.formatter; + +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +@SuppressWarnings("serial") +public class DateFormatter extends Format { + + private final String pattern; + + private final Locale locale; + + public DateFormatter(String pattern, Locale locale) { + this.pattern = pattern; + this.locale = locale; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + if (obj instanceof ZonedDateTime) { + ZonedDateTime zdt = (ZonedDateTime) obj; + DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern) + .withLocale(locale); + toAppendTo.append(zdt.format(dtf)); + } + return toAppendTo; + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return null; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/formatter/NumberFormatter.java b/chart/src/main/java/org/xbib/graphics/chart/formatter/NumberFormatter.java new file mode 100644 index 0000000..96ebf6b --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/formatter/NumberFormatter.java @@ -0,0 +1,102 @@ +package org.xbib.graphics.chart.formatter; + +import org.xbib.graphics.chart.axis.Direction; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.text.Format; +import java.text.NumberFormat; +import java.text.ParsePosition; + +@SuppressWarnings("serial") +public class NumberFormatter extends Format { + + private final AxesChartStyler styler; + private final Direction axisDirection; + private final double min; + private final double max; + private final NumberFormat numberFormat; + + public NumberFormatter(AxesChartStyler styler, Direction axisDirection, double min, double max) { + this.styler = styler; + this.axisDirection = axisDirection; + this.min = min; + this.max = max; + numberFormat = NumberFormat.getNumberInstance(styler.getLocale()); + } + + public String getFormatPattern(BigDecimal value) { + if (value.compareTo(BigDecimal.ZERO) == 0) { + return "0"; + } + double difference = max - min; + int placeOfDifference; + if (difference == 0.0) { + placeOfDifference = 0; + } else { + placeOfDifference = (int) Math.floor(Math.log(difference) / Math.log(10)); + } + int placeOfValue; + if (value.doubleValue() == 0.0) { + placeOfValue = 0; + } else { + placeOfValue = (int) Math.floor(Math.log(value.doubleValue()) / Math.log(10)); + } + if (placeOfDifference <= 4 && placeOfDifference >= -4) { + return getNormalDecimalPatternPositive(placeOfValue); + } else { + return getScientificDecimalPattern(); + } + } + + private String getNormalDecimalPatternPositive(int placeOfValue) { + int maxNumPlaces = 15; + StringBuilder sb = new StringBuilder(); + for (int i = maxNumPlaces - 1; i >= -1 * maxNumPlaces; i--) { + if (i >= 0 && (i < placeOfValue)) { + sb.append("0"); + } else if (i < 0 && (i > placeOfValue)) { + sb.append("0"); + } else { + sb.append("#"); + } + if (i % 3 == 0 && i > 0) { + sb.append(","); + } + if (i == 0) { + sb.append("."); + } + } + return sb.toString(); + } + + private String getScientificDecimalPattern() { + return "0.###############E0"; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + Number value = (Number) obj; + String decimalPattern; + if (axisDirection == Direction.X && styler.getXAxisDecimalPattern() != null) { + decimalPattern = styler.getXAxisDecimalPattern(); + } else if (axisDirection == Direction.Y && styler.getYAxisDecimalPattern() != null) { + decimalPattern = styler.getYAxisDecimalPattern(); + } else if (styler.getDecimalPattern() != null) { + decimalPattern = styler.getDecimalPattern(); + } else { + decimalPattern = getFormatPattern(BigDecimal.valueOf(value.doubleValue())); + } + DecimalFormat normalFormat = (DecimalFormat) numberFormat; + normalFormat.applyPattern(decimalPattern); + toAppendTo.append(normalFormat.format(value)); + return toAppendTo; + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return null; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/formatter/NumberLogFormatter.java b/chart/src/main/java/org/xbib/graphics/chart/formatter/NumberLogFormatter.java new file mode 100644 index 0000000..827cbd8 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/formatter/NumberLogFormatter.java @@ -0,0 +1,54 @@ +package org.xbib.graphics.chart.formatter; + +import org.xbib.graphics.chart.axis.Direction; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.text.Format; +import java.text.NumberFormat; +import java.text.ParsePosition; + +@SuppressWarnings("serial") +public class NumberLogFormatter extends Format { + + private final AxesChartStyler styler; + + private final Direction axisDirection; + + private final NumberFormat numberFormat; + + public NumberLogFormatter(AxesChartStyler styler, Direction axisDirection) { + this.styler = styler; + this.axisDirection = axisDirection; + numberFormat = NumberFormat.getNumberInstance(styler.getLocale()); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + double number = (Double) obj; + String decimalPattern; + if (axisDirection == Direction.X && styler.getXAxisDecimalPattern() != null) { + decimalPattern = styler.getXAxisDecimalPattern(); + } else if (axisDirection == Direction.Y && styler.getYAxisDecimalPattern() != null) { + decimalPattern = styler.getYAxisDecimalPattern(); + } else if (styler.getDecimalPattern() != null) { + decimalPattern = styler.getDecimalPattern(); + } else { + if (Math.abs(number) > 1000.0 || Math.abs(number) < 0.001) { + decimalPattern = "0E0"; + } else { + decimalPattern = "0.###"; + } + } + DecimalFormat normalFormat = (DecimalFormat) numberFormat; + normalFormat.applyPattern(decimalPattern); + toAppendTo.append(normalFormat.format(number)); + return toAppendTo; + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return null; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/formatter/StringFormatter.java b/chart/src/main/java/org/xbib/graphics/chart/formatter/StringFormatter.java new file mode 100644 index 0000000..91a6203 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/formatter/StringFormatter.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.chart.formatter; + +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; + +@SuppressWarnings("serial") +public class StringFormatter extends Format { + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + String string = obj.toString(); + toAppendTo.append(string); + return toAppendTo; + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return null; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/BitmapFormat.java b/chart/src/main/java/org/xbib/graphics/chart/io/BitmapFormat.java new file mode 100644 index 0000000..d4481a8 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/BitmapFormat.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.io; + +public enum BitmapFormat { + PNG, JPG, BMP, GIF +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/CSVExporter.java b/chart/src/main/java/org/xbib/graphics/chart/io/CSVExporter.java new file mode 100644 index 0000000..b2c1e06 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/CSVExporter.java @@ -0,0 +1,91 @@ +package org.xbib.graphics.chart.io; + +import org.xbib.graphics.chart.xy.XYSeries; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Iterator; + +/** + * This class is used to export Chart data to a path. + */ +public class CSVExporter { + + private final static String LF = System.getProperty("line.separator"); + + public static void writeCSVRows(XYSeries series, Path path) throws IOException { + try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path), + StandardCharsets.UTF_8))) { + String csv = join(series.getXData()) + LF; + bufferedWriter.write(csv); + csv = join(series.getYData()) + LF; + bufferedWriter.write(csv); + if (series.getExtraValues() != null) { + csv = join(series.getExtraValues()) + LF; + bufferedWriter.write(csv); + } + } + } + + public static void writeCSVColumns(XYSeries series, Path path) throws IOException { + try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path), + StandardCharsets.UTF_8))) { + Collection xData = series.getXData(); + Collection yData = series.getYData(); + Collection errorBarData = series.getExtraValues(); + Iterator itrx = xData.iterator(); + Iterator itry = yData.iterator(); + Iterator itrErrorBar = null; + if (errorBarData != null) { + itrErrorBar = errorBarData.iterator(); + } + while (itrx.hasNext()) { + Number xDataPoint = (Number) itrx.next(); + Number yDataPoint = itry.next(); + Number errorBarValue = null; + if (itrErrorBar != null) { + errorBarValue = itrErrorBar.next(); + } + StringBuilder sb = new StringBuilder(); + sb.append(xDataPoint).append(","); + sb.append(yDataPoint).append(","); + if (errorBarValue != null) { + sb.append(errorBarValue).append(","); + } + sb.append(LF); + bufferedWriter.write(sb.toString()); + } + } + } + + private static String join(Collection collection) { + if (collection == null) { + return null; + } + Iterator iterator = collection.iterator(); + if (!iterator.hasNext()) { + return ""; + } + Object first = iterator.next(); + if (!iterator.hasNext()) { + return first == null ? "" : first.toString(); + } + StringBuilder sb = new StringBuilder(); + if (first != null) { + sb.append(first); + } + while (iterator.hasNext()) { + sb.append(","); + Object obj = iterator.next(); + if (obj != null) { + sb.append(obj); + } + } + return sb.toString(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/CSVImporter.java b/chart/src/main/java/org/xbib/graphics/chart/io/CSVImporter.java new file mode 100644 index 0000000..777b899 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/CSVImporter.java @@ -0,0 +1,109 @@ +package org.xbib.graphics.chart.io; + +import org.xbib.graphics.chart.theme.Theme; +import org.xbib.graphics.chart.xy.XYChart; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +/** + * This class is used to create a Chart object from a folder containing one or more CSV files. The parent folder's name + * becomes the title of the chart. Each CSV file in the folder becomes a series on the chart. + * The CSV file's name becomes the series' name. + */ +public class CSVImporter { + + public static XYChart getChartFromCSVDir(Path path, DataOrientation dataOrientation, int width, int height) + throws IOException { + return getChartFromCSVDir(path, dataOrientation, width, height, null); + } + + public static XYChart getChartFromCSVDir(Path path, DataOrientation dataOrientation, int width, int height, + Theme theme) throws IOException { + XYChart chart = new XYChart(width, height, theme); + final PathMatcher pathMatcher = path.getFileSystem().getPathMatcher("glob:*.csv"); + Files.walkFileTree(path, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (pathMatcher.matches(file.getFileName())) { + String[] xAndYData; + if (dataOrientation == DataOrientation.Rows) { + xAndYData = getSeriesDataFromCSVRows(Files.newInputStream(file)); + } else { + xAndYData = getSeriesDataFromCSVColumns(Files.newInputStream(file)); + } + if (xAndYData[2] == null || xAndYData[2].trim().equalsIgnoreCase("")) { + chart.addSeries(file.toString().substring(0, file.toString().indexOf(".csv")), + getAxisData(xAndYData[0]), getAxisData(xAndYData[1])); + } else { + chart.addSeries(file.toString().substring(0, file.toString().indexOf(".csv")), + getAxisData(xAndYData[0]), getAxisData(xAndYData[1]), getAxisData(xAndYData[2])); + } + } + return FileVisitResult.CONTINUE; + } + }); + return chart; + } + + private static String[] getSeriesDataFromCSVRows(InputStream inputStream) throws IOException { + String[] xAndYData = new String[3]; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, + Charset.forName("UTF-8")))) { + int counter = 0; + String line = null; + while ((line = bufferedReader.readLine()) != null) { + xAndYData[counter++] = line; + } + } + return xAndYData; + } + + private static String[] getSeriesDataFromCSVColumns(InputStream inputStream) throws IOException { + String[] xAndYData = new String[3]; + xAndYData[0] = ""; + xAndYData[1] = ""; + xAndYData[2] = ""; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, + Charset.forName("UTF-8")))) { + String line = null; + while ((line = bufferedReader.readLine()) != null) { + String[] dataArray = line.split(","); + xAndYData[0] += dataArray[0] + ","; + xAndYData[1] += dataArray[1] + ","; + if (dataArray.length > 2) { + xAndYData[2] += dataArray[2] + ","; + } + } + } + return xAndYData; + } + + private static List getAxisData(String stringData) { + List axisData = new ArrayList<>(); + String[] stringDataArray = stringData.split(","); + for (String dataPoint : stringDataArray) { + axisData.add(Double.parseDouble(dataPoint)); + } + return axisData; + } + + public enum DataOrientation { + + Rows, Columns + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/VectorGraphicsFormat.java b/chart/src/main/java/org/xbib/graphics/chart/io/VectorGraphicsFormat.java new file mode 100644 index 0000000..c01323f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/VectorGraphicsFormat.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.io; + +public enum VectorGraphicsFormat { + EPS, PDF, SVG +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/Document.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/Document.java new file mode 100644 index 0000000..49f183f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/Document.java @@ -0,0 +1,13 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.intermediate.CommandHandler; + +import java.io.IOException; +import java.io.OutputStream; + +public interface Document extends CommandHandler { + void write(OutputStream out) throws IOException; + + void close(); +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/EPSGraphics2D.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/EPSGraphics2D.java new file mode 100644 index 0000000..8c0eb34 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/EPSGraphics2D.java @@ -0,0 +1,42 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.eps.EPSProcessor; + +import java.awt.BasicStroke; +import java.awt.Color; + +/** + * {@code Graphics2D} implementation that saves all operations to a string + * in the Encapsulated PostScript® (EPS) format. + */ +public class EPSGraphics2D extends ProcessingPipeline { + + private final Processor processor; + + /** + * Initializes a new VectorGraphics2D pipeline for translating Graphics2D + * commands to EPS data. The document dimensions must be specified as + * parameters. + * + * @param x Left offset. + * @param y Top offset + * @param width Width. + * @param height Height. + */ + public EPSGraphics2D(double x, double y, double width, double height) { + super(x, y, width, height); + processor = new EPSProcessor(); + /* + * The following are the default settings for the graphics state in an EPS file. + * Although they currently appear in the document output, they do not have to be set explicitly. + */ + // TODO: Default graphics state does not need to be printed in the document + setColor(Color.BLACK); + setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, null, 0f)); + } + + @Override + protected Processor getProcessor() { + return processor; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/GraphicsState.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/GraphicsState.java new file mode 100644 index 0000000..d418b6a --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/GraphicsState.java @@ -0,0 +1,275 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Rectangle2D; +import java.util.Objects; + +public class GraphicsState implements Cloneable { + /** + * Default background color. + */ + public static final Color DEFAULT_BACKGROUND = Color.BLACK; + /** + * Default color. + */ + public static final Color DEFAULT_COLOR = Color.WHITE; + /** + * Default clipping shape. + */ + public static final Shape DEFAULT_CLIP = null; + /** + * Default composite mode. + */ + public static final Composite DEFAULT_COMPOSITE = AlphaComposite.SrcOver; + /** + * Default font. + */ + public static final Font DEFAULT_FONT = Font.decode(null); + /** + * Default paint. + */ + public static final Color DEFAULT_PAINT = DEFAULT_COLOR; + /** + * Default stroke. + */ + public static final Stroke DEFAULT_STROKE = new BasicStroke(); + /** + * Default transformation. + */ + public static final AffineTransform DEFAULT_TRANSFORM = + new AffineTransform(); + /** + * Default XOR mode. + */ + public static final Color DEFAULT_XOR_MODE = Color.BLACK; + + /** + * Rendering hints. + */ + private RenderingHints hints; + /** + * Current background color. + */ + private Color background; + /** + * Current foreground color. + */ + private Color color; + /** + * Shape used for clipping paint operations. + */ + private Shape clip; + /** + * Method used for compositing. + */ + private Composite composite; + /** + * Current font. + */ + private Font font; + /** + * Paint used to fill shapes. + */ + private Paint paint; + /** + * Stroke used for drawing shapes. + */ + private Stroke stroke; + /** + * Current transformation matrix. + */ + private AffineTransform transform; + /** + * XOR mode used for rendering. + */ + private Color xorMode; + + public GraphicsState() { + hints = new RenderingHints(null); + background = DEFAULT_BACKGROUND; + color = DEFAULT_COLOR; + clip = DEFAULT_CLIP; + composite = DEFAULT_COMPOSITE; + font = DEFAULT_FONT; + paint = DEFAULT_PAINT; + stroke = DEFAULT_STROKE; + transform = new AffineTransform(DEFAULT_TRANSFORM); + xorMode = DEFAULT_XOR_MODE; + } + + private static Shape transformShape(Shape s, AffineTransform tx) { + if (s == null) { + return null; + } + if (tx == null || tx.isIdentity()) { + return GraphicsUtils.clone(s); + } + boolean isRectangle = s instanceof Rectangle2D; + int nonRectlinearTxMask = AffineTransform.TYPE_GENERAL_TRANSFORM | + AffineTransform.TYPE_GENERAL_ROTATION; + boolean isRectlinearTx = (tx.getType() & nonRectlinearTxMask) == 0; + if (isRectangle && isRectlinearTx) { + Rectangle2D rect = (Rectangle2D) s; + double[] corners = new double[]{ + rect.getMinX(), rect.getMinY(), + rect.getMaxX(), rect.getMaxY() + }; + tx.transform(corners, 0, corners, 0, 2); + rect = new Rectangle2D.Double(); + rect.setFrameFromDiagonal(corners[0], corners[1], corners[2], + corners[3]); + return rect; + } + return tx.createTransformedShape(s); + } + + private static Shape untransformShape(Shape s, AffineTransform tx) { + if (s == null) { + return null; + } + try { + AffineTransform inverse = tx.createInverse(); + return transformShape(s, inverse); + } catch (NoninvertibleTransformException e) { + return null; + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + GraphicsState clone = (GraphicsState) super.clone(); + clone.hints = (RenderingHints) hints.clone(); + clone.clip = GraphicsUtils.clone(clip); + clone.transform = new AffineTransform(transform); + return clone; + } + + public Shape transformShape(Shape shape) { + return transformShape(shape, transform); + } + + public Shape untransformShape(Shape shape) { + return untransformShape(shape, transform); + } + + public RenderingHints getHints() { + return hints; + } + + public Color getBackground() { + return background; + } + + public void setBackground(Color background) { + this.background = background; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public Shape getClip() { + return untransformShape(clip); + } + + public void setClip(Shape clip) { + this.clip = transformShape(clip); + } + + public Composite getComposite() { + return composite; + } + + public void setComposite(Composite composite) { + this.composite = composite; + } + + public Font getFont() { + return font; + } + + public void setFont(Font font) { + this.font = font; + } + + public Paint getPaint() { + return paint; + } + + public void setPaint(Paint paint) { + this.paint = paint; + } + + public Stroke getStroke() { + return stroke; + } + + public void setStroke(Stroke stroke) { + this.stroke = stroke; + } + + public AffineTransform getTransform() { + return new AffineTransform(transform); + } + + public void setTransform(AffineTransform tx) { + transform.setTransform(tx); + } + + public Color getXorMode() { + return xorMode; + } + + public void setXorMode(Color xorMode) { + this.xorMode = xorMode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GraphicsState)) { + return false; + } + GraphicsState o = (GraphicsState) obj; + return !(!hints.equals(o.hints) || !background.equals(o.background) || + !color.equals(o.color) || !composite.equals(o.composite) || + !font.equals(o.font) || !paint.equals(o.paint) || + !stroke.equals(o.stroke) || !transform.equals(o.transform) || + !xorMode.equals(o.xorMode) || + ((clip == null || o.clip == null) && clip != o.clip) || + (clip != null && !clip.equals(o.clip))); + } + + @Override + public int hashCode() { + return Objects.hash(hints, background, color, composite, font, paint, + stroke, transform, xorMode, clip); + } + + public boolean isDefault() { + return hints.isEmpty() && background.equals(DEFAULT_BACKGROUND) && + color.equals(DEFAULT_COLOR) && composite.equals(DEFAULT_COMPOSITE) && + font.equals(DEFAULT_FONT) && paint.equals(DEFAULT_PAINT) && + stroke.equals(DEFAULT_STROKE) && transform.equals(DEFAULT_TRANSFORM) && + xorMode.equals(DEFAULT_XOR_MODE) && clip == DEFAULT_CLIP; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/PDFGraphics2D.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/PDFGraphics2D.java new file mode 100644 index 0000000..9a0a8d2 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/PDFGraphics2D.java @@ -0,0 +1,38 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.pdf.PDFProcessor; + +import java.awt.BasicStroke; +import java.awt.Color; + +/** + * {@code Graphics2D} implementation that saves all operations to a string + * in the Portable Document Format (PDF). + */ +public class PDFGraphics2D extends ProcessingPipeline { + private final Processor processor; + + /** + * Initializes a new VectorGraphics2D pipeline for translating Graphics2D + * commands to PDF data. The document dimensions must be specified as + * parameters. + * + * @param x Left offset. + * @param y Top offset + * @param width Width. + * @param height Height. + */ + public PDFGraphics2D(double x, double y, double width, double height) { + super(x, y, width, height); + processor = new PDFProcessor(); + + // TODO: Default graphics state does not need to be printed in the document + setColor(Color.BLACK); + setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, null, 0f)); + } + + @Override + protected Processor getProcessor() { + return processor; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/ProcessingPipeline.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/ProcessingPipeline.java new file mode 100644 index 0000000..300b779 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/ProcessingPipeline.java @@ -0,0 +1,53 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Base class for convenience implementations of {@code VectorGraphics2D}. + */ +public abstract class ProcessingPipeline extends VectorGraphics2D { + private final PageSize pageSize; + + /** + * Initializes a processing pipeline. + * + * @param x Left offset. + * @param y Top offset + * @param width Width. + * @param height Height. + */ + public ProcessingPipeline(double x, double y, double width, double height) { + pageSize = new PageSize(x, y, width, height); + } + + public PageSize getPageSize() { + return pageSize; + } + + protected abstract Processor getProcessor(); + + public void writeTo(OutputStream out) throws IOException { + Document doc = getProcessor().process(getCommands(), getPageSize()); + doc.write(out); + } + + public byte[] getBytes() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + writeTo(out); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return out.toByteArray(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/Processor.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/Processor.java new file mode 100644 index 0000000..c2e40d9 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/Processor.java @@ -0,0 +1,9 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +public interface Processor { + Document process(Iterable> commands, PageSize pageSize); +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/SVGGraphics2D.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/SVGGraphics2D.java new file mode 100644 index 0000000..bd2084f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/SVGGraphics2D.java @@ -0,0 +1,36 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.svg.SVGProcessor; + +import java.awt.Color; + +/** + * {@code Graphics2D} implementation that saves all operations to a string + * in the Scaled Vector Graphics (SVG) format. + */ +public class SVGGraphics2D extends ProcessingPipeline { + private final Processor processor; + + /** + * Initializes a new VectorGraphics2D pipeline for translating Graphics2D + * commands to SVG data. The document dimensions must be specified as + * parameters. + * + * @param x Left offset. + * @param y Top offset + * @param width Width. + * @param height Height. + */ + public SVGGraphics2D(double x, double y, double width, double height) { + super(x, y, width, height); + processor = new SVGProcessor(); + + // Make graphics state match default state of Graphics2D + setColor(Color.BLACK); + } + + @Override + protected Processor getProcessor() { + return processor; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/SizedDocument.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/SizedDocument.java new file mode 100644 index 0000000..016cea3 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/SizedDocument.java @@ -0,0 +1,19 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.util.PageSize; + +public abstract class SizedDocument implements Document { + private final PageSize pageSize; + + public SizedDocument(PageSize pageSize) { + this.pageSize = pageSize; + } + + public PageSize getPageSize() { + return pageSize; + } + + public void close() { + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/VectorGraphics2D.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/VectorGraphics2D.java new file mode 100644 index 0000000..845bba0 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/VectorGraphics2D.java @@ -0,0 +1,894 @@ +package org.xbib.graphics.chart.io.vector; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.CreateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawImageCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawStringCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.FillShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.RotateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.ScaleCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetBackgroundCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetClipCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetColorCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetCompositeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetFontCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetHintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetPaintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetStrokeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetXORModeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.ShearCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.TransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.TranslateCommand; +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.Paint; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.RenderingHints.Key; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.RenderableImage; +import java.text.AttributedCharacterIterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Base for classes that want to implement vector export. + */ +public class VectorGraphics2D extends Graphics2D implements Cloneable { + /** + * List of operations that were performed on this graphics object and its + * derived objects. + */ + private final List> commands; + /** + * Device configuration settings. + */ + //private final GraphicsConfiguration deviceConfig; + /** + * Context settings used to render fonts. + */ + private final FontRenderContext fontRenderContext; + /** + * Flag that tells whether this graphics object has been disposed. + */ + private boolean disposed; + + private GraphicsState state; + + private Graphics2D _debug_validate_graphics; + + public VectorGraphics2D() { + commands = new LinkedList>(); + emit(new CreateCommand(this)); + fontRenderContext = new FontRenderContext(null, false, true); + + state = new GraphicsState(); + + BufferedImage _debug_validate_bimg = new BufferedImage(200, 250, BufferedImage.TYPE_INT_ARGB); + _debug_validate_graphics = (Graphics2D) _debug_validate_bimg.getGraphics(); + _debug_validate_graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + + private static Shape intersectShapes(Shape s1, Shape s2) { + if (s1 instanceof Rectangle2D && s2 instanceof Rectangle2D) { + Rectangle2D r1 = (Rectangle2D) s1; + Rectangle2D r2 = (Rectangle2D) s2; + double x1 = Math.max(r1.getMinX(), r2.getMinX()); + double y1 = Math.max(r1.getMinY(), r2.getMinY()); + double x2 = Math.min(r1.getMaxX(), r2.getMaxX()); + double y2 = Math.min(r1.getMaxY(), r2.getMaxY()); + + Rectangle2D intersection = new Rectangle2D.Double(); + if ((x2 < x1) || (y2 < y1)) { + intersection.setFrameFromDiagonal(0, 0, 0, 0); + } else { + intersection.setFrameFromDiagonal(x1, y1, x2, y2); + } + return intersection; + } else { + Area intersection = new Area(s1); + intersection.intersect(new Area(s2)); + return intersection; + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + try { + VectorGraphics2D clone = (VectorGraphics2D) super.clone(); + clone.state = (GraphicsState) state.clone(); + return clone; + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public void addRenderingHints(Map hints) { + if (isDisposed()) { + return; + } + for (Entry entry : hints.entrySet()) { + setRenderingHint((Key) entry.getKey(), entry.getValue()); + } + } + + @Override + public void clip(Shape s) { + _debug_validate_graphics.clip(s); + Shape clipOld = getClip(); + + Shape clip = getClip(); + if ((clip != null) && (s != null)) { + s = intersectShapes(clip, s); + } + setClip(s); + + Shape clipNew = getClip(); + if ((clipNew == null || _debug_validate_graphics.getClip() == null) && clipNew != _debug_validate_graphics.getClip()) { + System.err.println("clip() validation failed: clip(" + clipOld + ", " + s + ") => " + clipNew + " != " + _debug_validate_graphics.getClip()); + } + if (clipNew != null && !GraphicsUtils.equals(clipNew, _debug_validate_graphics.getClip())) { + System.err.println("clip() validation failed: clip(" + clipOld + ", " + s + ") => " + clipNew + " != " + _debug_validate_graphics.getClip()); + } + } + + @Override + public void draw(Shape s) { + if (isDisposed() || s == null) { + return; + } + emit(new DrawShapeCommand(s)); + + _debug_validate_graphics.draw(s); + } + + @Override + public void drawGlyphVector(GlyphVector g, float x, float y) { + Shape s = g.getOutline(x, y); + draw(s); + } + + @Override + public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { + BufferedImage bimg = getTransformedImage(img, xform); + return drawImage(bimg, bimg.getMinX(), bimg.getMinY(), + bimg.getWidth(), bimg.getHeight(), null, null); + } + + /** + * Returns a transformed version of an image. + * + * @param image Image to be transformed + * @param xform Affine transform to be applied + * @return Image with transformed content + */ + private BufferedImage getTransformedImage(Image image, + AffineTransform xform) { + Integer interpolationType = + (Integer) getRenderingHint(RenderingHints.KEY_INTERPOLATION); + if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR + .equals(interpolationType)) { + interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; + } else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR + .equals(interpolationType)) { + interpolationType = AffineTransformOp.TYPE_BILINEAR; + } else { + interpolationType = AffineTransformOp.TYPE_BICUBIC; + } + AffineTransformOp op = new AffineTransformOp(xform, interpolationType); + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + return op.filter(bufferedImage, null); + } + + @Override + public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { + if (op != null) { + img = op.filter(img, null); + } + drawImage(img, x, y, img.getWidth(), img.getHeight(), null, null); + } + + @Override + public void drawRenderableImage(RenderableImage img, AffineTransform xform) { + drawRenderedImage(img.createDefaultRendering(), xform); + } + + @Override + public void drawRenderedImage(RenderedImage img, AffineTransform xform) { + BufferedImage bimg = GraphicsUtils.toBufferedImage(img); + drawImage(bimg, xform, null); + } + + @Override + public void drawString(String str, int x, int y) { + drawString(str, (float) x, (float) y); + } + + @Override + public void drawString(String str, float x, float y) { + if (isDisposed() || str == null || str.trim().length() == 0) { + return; + } + boolean isTextAsVectors = false; + if (isTextAsVectors) { + TextLayout layout = new TextLayout(str, getFont(), + getFontRenderContext()); + Shape s = layout.getOutline( + AffineTransform.getTranslateInstance(x, y)); + fill(s); + } else { + emit(new DrawStringCommand(str, x, y)); + + _debug_validate_graphics.drawString(str, x, y); + } + + } + + @Override + public void drawString(AttributedCharacterIterator iterator, int x, int y) { + drawString(iterator, (float) x, (float) y); + } + + @Override + public void drawString(AttributedCharacterIterator iterator, float x, + float y) { + // TODO Draw styled text + StringBuilder buf = new StringBuilder(); + for (char c = iterator.first(); c != AttributedCharacterIterator.DONE; + c = iterator.next()) { + buf.append(c); + } + drawString(buf.toString(), x, y); + } + + @Override + public void fill(Shape s) { + if (isDisposed() || s == null) { + return; + } + emit(new FillShapeCommand(s)); + + _debug_validate_graphics.fill(s); + } + + @Override + public Color getBackground() { + return state.getBackground(); + } + + @Override + public void setBackground(Color color) { + if (isDisposed() || color == null || getColor().equals(color)) { + return; + } + emit(new SetBackgroundCommand(color)); + state.setBackground(color); + + _debug_validate_graphics.setBackground(color); + if (!getBackground().equals(_debug_validate_graphics.getBackground())) { + System.err.println("setBackground() validation failed"); + } + } + + @Override + public Composite getComposite() { + return state.getComposite(); + } + + @Override + public void setComposite(Composite comp) { + if (isDisposed()) { + return; + } + if (comp == null) { + throw new IllegalArgumentException("Cannot set a null composite."); + } + emit(new SetCompositeCommand(comp)); + state.setComposite(comp); + + _debug_validate_graphics.setComposite(comp); + if (!getComposite().equals(_debug_validate_graphics.getComposite())) { + System.err.println("setComposite() validation failed"); + } + } + + @Override + public GraphicsConfiguration getDeviceConfiguration() { + throw new UnsupportedOperationException(); + } + + @Override + public FontRenderContext getFontRenderContext() { + return fontRenderContext; + } + + @Override + public Paint getPaint() { + return state.getPaint(); + } + + @Override + public void setPaint(Paint paint) { + if (isDisposed() || paint == null) { + return; + } + if (paint instanceof Color) { + setColor((Color) paint); + return; + } + if (getPaint().equals(paint)) { + return; + } + emit(new SetPaintCommand(paint)); + state.setPaint(paint); + + _debug_validate_graphics.setPaint(paint); + if (!getPaint().equals(_debug_validate_graphics.getPaint())) { + System.err.println("setPaint() validation failed"); + } + } + + @Override + public Object getRenderingHint(Key hintKey) { + if (RenderingHints.KEY_ANTIALIASING.equals(hintKey)) { + return RenderingHints.VALUE_ANTIALIAS_OFF; + } else if (RenderingHints.KEY_TEXT_ANTIALIASING.equals(hintKey)) { + return RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; + } else if (RenderingHints.KEY_FRACTIONALMETRICS.equals(hintKey)) { + return RenderingHints.VALUE_FRACTIONALMETRICS_ON; + } + return state.getHints().get(hintKey); + } + + @Override + public RenderingHints getRenderingHints() { + return (RenderingHints) state.getHints().clone(); + } + + @Override + public void setRenderingHints(Map hints) { + if (isDisposed()) { + return; + } + state.getHints().clear(); + for (Entry hint : hints.entrySet()) { + setRenderingHint((Key) hint.getKey(), hint.getValue()); + } + } + + @Override + public Stroke getStroke() { + return state.getStroke(); + } + + @Override + public void setStroke(Stroke s) { + if (isDisposed()) { + return; + } + if (s == null) { + throw new IllegalArgumentException("Cannot set a null stroke."); + } + emit(new SetStrokeCommand(s)); + state.setStroke(s); + + _debug_validate_graphics.setStroke(s); + if (!getStroke().equals(_debug_validate_graphics.getStroke())) { + System.err.println("setStroke() validation failed"); + } + } + + @Override + public boolean hit(Rectangle rect, Shape s, boolean onStroke) { + Shape hitShape = s; + if (onStroke) { + hitShape = getStroke().createStrokedShape(hitShape); + } + hitShape = state.transformShape(hitShape); + boolean hit = hitShape.intersects(rect); + + boolean _debug_hit = _debug_validate_graphics.hit(rect, s, onStroke); + if (hit != _debug_hit) { + System.err.println("setClip() validation failed"); + } + + return hit; + } + + @Override + public void setRenderingHint(Key hintKey, Object hintValue) { + if (isDisposed()) { + return; + } + state.getHints().put(hintKey, hintValue); + emit(new SetHintCommand(hintKey, hintValue)); + } + + @Override + public AffineTransform getTransform() { + return new AffineTransform(state.getTransform()); + } + + @Override + public void setTransform(AffineTransform tx) { + if (isDisposed() || tx == null || state.getTransform().equals(tx)) { + return; + } + emit(new SetTransformCommand(tx)); + state.setTransform(tx); + + _debug_validate_graphics.setTransform(tx); + if (!getTransform().equals(_debug_validate_graphics.getTransform())) { + System.err.println("setTransform() validation failed"); + } + } + + @Override + public void shear(double shx, double shy) { + if (shx == 0.0 && shy == 0.0) { + return; + } + AffineTransform txNew = getTransform(); + txNew.shear(shx, shy); + emit(new ShearCommand(shx, shy)); + state.setTransform(txNew); + + _debug_validate_graphics.shear(shx, shy); + if (!getTransform().equals(_debug_validate_graphics.getTransform())) { + System.err.println("shear() validation failed"); + } + } + + @Override + public void transform(AffineTransform tx) { + if (tx.isIdentity()) { + return; + } + AffineTransform txNew = getTransform(); + txNew.concatenate(tx); + emit(new TransformCommand(tx)); + state.setTransform(txNew); + + _debug_validate_graphics.transform(tx); + if (!getTransform().equals(_debug_validate_graphics.getTransform())) { + System.err.println("transform() validation failed"); + } + } + + @Override + public void translate(int x, int y) { + translate((double) x, (double) y); + } + + @Override + public void translate(double tx, double ty) { + if (tx == 0.0 && ty == 0.0) { + return; + } + AffineTransform txNew = getTransform(); + txNew.translate(tx, ty); + emit(new TranslateCommand(tx, ty)); + state.setTransform(txNew); + + _debug_validate_graphics.translate(tx, ty); + if (!getTransform().equals(_debug_validate_graphics.getTransform())) { + System.err.println("translate() validation failed"); + } + } + + @Override + public void rotate(double theta) { + rotate(theta, 0.0, 0.0); + } + + @Override + public void rotate(double theta, double x, double y) { + if (theta == 0.0) { + return; + } + + AffineTransform txNew = getTransform(); + if (x == 0.0 && y == 0.0) { + txNew.rotate(theta); + } else { + txNew.rotate(theta, x, y); + } + + emit(new RotateCommand(theta, x, y)); + state.setTransform(txNew); + + if (x == 0.0 && y == 0.0) { + _debug_validate_graphics.rotate(theta); + if (!getTransform().equals(_debug_validate_graphics.getTransform())) { + System.err.println("rotate(theta) validation failed"); + } + } else { + _debug_validate_graphics.rotate(theta, x, y); + if (!getTransform().equals(_debug_validate_graphics.getTransform())) { + System.err.println("rotate(theta,x,y) validation failed"); + } + } + } + + @Override + public void scale(double sx, double sy) { + if (sx == 1.0 && sy == 1.0) { + return; + } + AffineTransform txNew = getTransform(); + txNew.scale(sx, sy); + emit(new ScaleCommand(sx, sy)); + state.setTransform(txNew); + + _debug_validate_graphics.scale(sx, sy); + if (!getTransform().equals(_debug_validate_graphics.getTransform())) { + System.err.println("scale() validation failed"); + } + } + + @Override + public void clearRect(int x, int y, int width, int height) { + Color colorOld = getColor(); + setColor(getBackground()); + fillRect(x, y, width, height); + setColor(colorOld); + } + + @Override + public void clipRect(int x, int y, int width, int height) { + clip(new Rectangle(x, y, width, height)); + } + + @Override + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + // TODO Implement + //throw new UnsupportedOperationException("copyArea() isn't supported by VectorGraphics2D."); + } + + @Override + public Graphics create() { + if (isDisposed()) { + return null; + } + VectorGraphics2D clone = null; + try { + clone = (VectorGraphics2D) this.clone(); + emit(new CreateCommand(clone)); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + + if (clone != null) { + clone._debug_validate_graphics = (Graphics2D) _debug_validate_graphics.create(); + } + + return clone; + } + + @Override + public void dispose() { + if (isDisposed()) { + return; + } + + emit(new DisposeCommand(this)); + + disposed = true; + + _debug_validate_graphics.dispose(); + } + + @Override + public void drawArc(int x, int y, int width, int height, int startAngle, + int arcAngle) { + draw(new Arc2D.Double(x, y, width, height, + startAngle, arcAngle, Arc2D.OPEN)); + } + + @Override + public boolean drawImage(Image img, int x, int y, ImageObserver observer) { + return drawImage(img, x, y, img.getWidth(observer), + img.getHeight(observer), null, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, Color bgcolor, + ImageObserver observer) { + return drawImage(img, x, y, img.getWidth(observer), + img.getHeight(observer), bgcolor, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, + ImageObserver observer) { + return drawImage(img, x, y, width, height, null, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, + Color bgcolor, ImageObserver observer) { + if (isDisposed() || img == null) { + return true; + } + + int imageWidth = img.getWidth(observer); + int imageHeight = img.getHeight(observer); + Rectangle bounds = new Rectangle(x, y, width, height); + + if (bgcolor != null) { + // Fill rectangle with bgcolor + Color bgcolorOld = getColor(); + setColor(bgcolor); + fill(bounds); + setColor(bgcolorOld); + } + + emit(new DrawImageCommand(img, imageWidth, imageHeight, x, y, width, height)); + + _debug_validate_graphics.drawImage(img, x, y, width, height, bgcolor, observer); + + return true; + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { + return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, + observer); + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, Color bgcolor, + ImageObserver observer) { + if (img == null) { + return true; + } + + int sx = Math.min(sx1, sx2); + int sy = Math.min(sy1, sy2); + int sw = Math.abs(sx2 - sx1); + int sh = Math.abs(sy2 - sy1); + int dx = Math.min(dx1, dx2); + int dy = Math.min(dy1, dy2); + int dw = Math.abs(dx2 - dx1); + int dh = Math.abs(dy2 - dy1); + + // Draw image on rectangle + BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img); + Image cropped = bufferedImg.getSubimage(sx, sy, sw, sh); + return drawImage(cropped, dx, dy, dw, dh, bgcolor, observer); + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + draw(new Line2D.Double(x1, y1, x2, y2)); + } + + @Override + public void drawOval(int x, int y, int width, int height) { + draw(new Ellipse2D.Double(x, y, width, height)); + } + + @Override + public void drawPolygon(Polygon p) { + draw(p); + } + + @Override + public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { + draw(new Polygon(xPoints, yPoints, nPoints)); + } + + @Override + public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { + Path2D p = new Path2D.Float(); + for (int i = 0; i < nPoints; i++) { + if (i > 0) { + p.lineTo(xPoints[i], yPoints[i]); + } else { + p.moveTo(xPoints[i], yPoints[i]); + } + } + draw(p); + } + + @Override + public void drawRect(int x, int y, int width, int height) { + draw(new Rectangle(x, y, width, height)); + } + + @Override + public void drawRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) { + draw(new RoundRectangle2D.Double(x, y, width, height, + arcWidth, arcHeight)); + } + + @Override + public void fillArc(int x, int y, int width, int height, + int startAngle, int arcAngle) { + fill(new Arc2D.Double(x, y, width, height, + startAngle, arcAngle, Arc2D.PIE)); + } + + @Override + public void fillOval(int x, int y, int width, int height) { + fill(new Ellipse2D.Double(x, y, width, height)); + } + + @Override + public void fillPolygon(Polygon p) { + fill(p); + } + + @Override + public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { + fill(new Polygon(xPoints, yPoints, nPoints)); + } + + @Override + public void fillRect(int x, int y, int width, int height) { + fill(new Rectangle(x, y, width, height)); + } + + @Override + public void fillRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) { + fill(new RoundRectangle2D.Double(x, y, width, height, + arcWidth, arcHeight)); + } + + @Override + public Shape getClip() { + return state.getClip(); + } + + @Override + public void setClip(Shape clip) { + if (isDisposed()) { + return; + } + emit(new SetClipCommand(clip)); + state.setClip(clip); + + _debug_validate_graphics.setClip(clip); + if (getClip() == null) { + if (_debug_validate_graphics.getClip() != null) { + System.err.printf("setClip() validation failed: clip=null, validation=%s\n", _debug_validate_graphics.getClip()); + } + } else if (!GraphicsUtils.equals(getClip(), _debug_validate_graphics.getClip())) { + System.err.printf("setClip() validation failed: clip=%s, validation=%s\n", getClip(), _debug_validate_graphics.getClip()); + } + } + + @Override + public Rectangle getClipBounds() { + if (getClip() == null) { + return null; + } + return getClip().getBounds(); + } + + @Override + public Color getColor() { + return state.getColor(); + } + + @Override + public void setColor(Color c) { + if (isDisposed() || c == null || getColor().equals(c)) { + return; + } + emit(new SetColorCommand(c)); + state.setColor(c); + state.setPaint(c); + + _debug_validate_graphics.setColor(c); + if (!getColor().equals(_debug_validate_graphics.getColor())) { + System.err.println("setColor() validation failed"); + } + } + + @Override + public Font getFont() { + return state.getFont(); + } + + @Override + public void setFont(Font font) { + if (isDisposed() || (font != null && getFont().equals(font))) { + return; + } + emit(new SetFontCommand(font)); + state.setFont(font); + + _debug_validate_graphics.setFont(font); + if (!getFont().equals(_debug_validate_graphics.getFont())) { + System.err.println("setFont() validation failed"); + } + } + + @Override + public FontMetrics getFontMetrics(Font f) { + BufferedImage bi = + new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); + Graphics g = bi.getGraphics(); + FontMetrics fontMetrics = g.getFontMetrics(getFont()); + g.dispose(); + return fontMetrics; + } + + @Override + public void setClip(int x, int y, int width, int height) { + setClip(new Rectangle(x, y, width, height)); + } + + @Override + public void setPaintMode() { + setComposite(AlphaComposite.SrcOver); + + _debug_validate_graphics.setPaintMode(); + } + + public Color getXORMode() { + return state.getXorMode(); + } + + @Override + public void setXORMode(Color c1) { + if (isDisposed() || c1 == null) { + return; + } + emit(new SetXORModeCommand(c1)); + state.setXorMode(c1); + + _debug_validate_graphics.setXORMode(c1); + } + + private void emit(Command command) { + commands.add(command); + } + + protected Iterable> getCommands() { + return commands; + } + + protected boolean isDisposed() { + return disposed; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/VectorHints.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/VectorHints.java new file mode 100644 index 0000000..8e8ae9f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/VectorHints.java @@ -0,0 +1,83 @@ +package org.xbib.graphics.chart.io.vector; + +import java.awt.RenderingHints; +import java.util.HashSet; +import java.util.Set; + +public abstract class VectorHints { + public static final Key KEY_EXPORT = new Key(0, "Vector export mode"); + public static final Object VALUE_EXPORT_READABILITY = new Value(KEY_EXPORT, 0, "Maximize readability for humans"); + public static final Object VALUE_EXPORT_QUALITY = new Value(KEY_EXPORT, 1, "Maximize render quality"); + public static final Object VALUE_EXPORT_SIZE = new Value(KEY_EXPORT, 2, "Minimize data size"); + public static final Key KEY_TEXT = new Key(1, "Text export mode"); + public static final Object VALUE_TEXT_DEFAULT = new Value(KEY_TEXT, 0, "Keep text"); + public static final Object VALUE_TEXT_VECTOR = new Value(KEY_TEXT, 1, "Convert text to vector shapes"); + + protected VectorHints() { + throw new UnsupportedOperationException(); + } + + public static class Key extends RenderingHints.Key { + private final String description; + + public Key(int privateKey, String description) { + super(privateKey); + this.description = description; + } + + public int getIndex() { + return intKey(); + } + + @Override + public boolean isCompatibleValue(Object val) { + return val instanceof Value && ((Value) val).isCompatibleKey(this); + } + + @Override + public String toString() { + return description; + } + } + + public static class Value { + private static final Set values = new HashSet(); + private final Key key; + private final int index; + private final String description; + + public Value(Key key, int index, String description) { + this.key = key; + this.index = index; + this.description = description; + register(this); + } + + private synchronized static void register(Value value) { + String id = value.getId(); + if (values.contains(id)) { + throw new ExceptionInInitializerError( + "Duplicate index: " + value.getIndex()); + } + values.add(id); + } + + public boolean isCompatibleKey(RenderingHints.Key key) { + return this.key == key; + } + + public int getIndex() { + return index; + } + + public String getId() { + return key.getIndex() + ":" + getIndex(); + } + + @Override + public String toString() { + return description; + } + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/eps/EPSDocument.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/eps/EPSDocument.java new file mode 100644 index 0000000..1ab1d44 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/eps/EPSDocument.java @@ -0,0 +1,476 @@ +package org.xbib.graphics.chart.io.vector.eps; + +import org.xbib.graphics.chart.io.vector.GraphicsState; +import org.xbib.graphics.chart.io.vector.SizedDocument; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.CreateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawImageCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawStringCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.FillShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.RotateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.ScaleCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetClipCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetColorCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetCompositeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetFontCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetPaintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetStrokeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.ShearCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.TransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.TranslateCommand; +import org.xbib.graphics.chart.io.vector.util.ASCII85EncodeStream; +import org.xbib.graphics.chart.io.vector.util.AlphaToMaskOp; +import org.xbib.graphics.chart.io.vector.util.DataUtils; +import org.xbib.graphics.chart.io.vector.util.FlateEncodeStream; +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; +import org.xbib.graphics.chart.io.vector.util.ImageDataStream; +import org.xbib.graphics.chart.io.vector.util.ImageDataStream.Interleaving; +import org.xbib.graphics.chart.io.vector.util.LineWrapOutputStream; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Image; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Arc2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EPSDocument extends SizedDocument { + /** + * Constant to convert values from millimeters to PostScript® units + * (1/72th inch). + */ + private static final double UNITS_PER_MM = 72.0 / 25.4; + private static final String CHARSET = "ISO-8859-1"; + private static final String EOL = "\n"; + private static final int MAX_LINE_WIDTH = 255; + private static final Pattern ELEMENT_SEPARATION_PATTERN = Pattern.compile("(.{1," + MAX_LINE_WIDTH + "})(\\s+|$)"); + + /** + * Mapping of stroke endcap values from Java to PostScript®. + */ + private static final Map STROKE_ENDCAPS = DataUtils.map( + new Integer[]{BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE}, + new Integer[]{0, 1, 2} + ); + + /** + * Mapping of line join values for path drawing from Java to + * PostScript®. + */ + private static final Map STROKE_LINEJOIN = DataUtils.map( + new Integer[]{BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL}, + new Integer[]{0, 1, 2} + ); + + private static final String FONT_LATIN1_SUFFIX = "Lat"; + + private final List elements; + + public EPSDocument(PageSize pageSize) { + super(pageSize); + elements = new LinkedList(); + addHeader(); + } + + private static String getOutput(Color c) { + // TODO Handle transparency + return String.valueOf(c.getRed() / 255.0) + " " + c.getGreen() / 255.0 + " " + c.getBlue() / 255.0 + " rgb"; + } + + private static String getOutput(Shape s) { + StringBuilder out = new StringBuilder(); + out.append("newpath "); + if (s instanceof Line2D) { + Line2D l = (Line2D) s; + out.append(l.getX1()).append(" ").append(l.getY1()).append(" M ") + .append(l.getX2()).append(" ").append(l.getY2()).append(" L"); + } else if (s instanceof Rectangle2D) { + Rectangle2D r = (Rectangle2D) s; + out.append(r.getX()).append(" ").append(r.getY()).append(" ") + .append(r.getWidth()).append(" ").append(r.getHeight()) + .append(" rect Z"); + } else if (s instanceof Ellipse2D) { + Ellipse2D e = (Ellipse2D) s; + double x = e.getX() + e.getWidth() / 2.0; + double y = e.getY() + e.getHeight() / 2.0; + double rx = e.getWidth() / 2.0; + double ry = e.getHeight() / 2.0; + out.append(x).append(" ").append(y).append(" ") + .append(rx).append(" ").append(ry).append(" ") + .append(360.0).append(" ").append(0.0) + .append(" ellipse Z"); + } else if (s instanceof Arc2D) { + Arc2D e = (Arc2D) s; + double x = (e.getX() + e.getWidth() / 2.0); + double y = (e.getY() + e.getHeight() / 2.0); + double rx = e.getWidth() / 2.0; + double ry = e.getHeight() / 2.0; + double startAngle = -e.getAngleStart(); + double endAngle = -(e.getAngleStart() + e.getAngleExtent()); + out.append(x).append(" ").append(y).append(" ") + .append(rx).append(" ").append(ry).append(" ") + .append(startAngle).append(" ").append(endAngle) + .append(" ellipse"); + if (e.getArcType() == Arc2D.CHORD) { + out.append(" Z"); + } else if (e.getArcType() == Arc2D.PIE) { + out.append(" ").append(x).append(" ").append(y).append(" L Z"); + } + } else { + PathIterator segments = s.getPathIterator(null); + double[] coordsCur = new double[6]; + double[] pointPrev = new double[2]; + for (int i = 0; !segments.isDone(); i++, segments.next()) { + if (i > 0) { + out.append(" "); + } + int segmentType = segments.currentSegment(coordsCur); + switch (segmentType) { + case PathIterator.SEG_MOVETO: + out.append(coordsCur[0]).append(" ").append(coordsCur[1]) + .append(" M"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_LINETO: + out.append(coordsCur[0]).append(" ").append(coordsCur[1]) + .append(" L"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_CUBICTO: + out.append(coordsCur[0]).append(" ").append(coordsCur[1]) + .append(" ").append(coordsCur[2]).append(" ") + .append(coordsCur[3]).append(" ").append(coordsCur[4]) + .append(" ").append(coordsCur[5]).append(" C"); + pointPrev[0] = coordsCur[4]; + pointPrev[1] = coordsCur[5]; + break; + case PathIterator.SEG_QUADTO: + double x1 = pointPrev[0] + 2.0 / 3.0 * (coordsCur[0] - pointPrev[0]); + double y1 = pointPrev[1] + 2.0 / 3.0 * (coordsCur[1] - pointPrev[1]); + double x2 = coordsCur[0] + 1.0 / 3.0 * (coordsCur[2] - coordsCur[0]); + double y2 = coordsCur[1] + 1.0 / 3.0 * (coordsCur[3] - coordsCur[1]); + double x3 = coordsCur[2]; + double y3 = coordsCur[3]; + out.append(x1).append(" ").append(y1).append(" ") + .append(x2).append(" ").append(y2).append(" ") + .append(x3).append(" ").append(y3).append(" C"); + pointPrev[0] = x3; + pointPrev[1] = y3; + break; + case PathIterator.SEG_CLOSE: + out.append("Z"); + break; + default: + throw new IllegalStateException("Unknown path operation."); + } + } + } + return out.toString(); + } + + private static String getOutput(Image image, int imageWidth, int imageHeight, + double x, double y, double width, double height) { + StringBuilder out = new StringBuilder(); + + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + int bands = bufferedImage.getSampleModel().getNumBands(); + int bitsPerSample = DataUtils.max(bufferedImage.getSampleModel().getSampleSize()); + bitsPerSample = (int) (Math.ceil(bitsPerSample / 8.0) * 8.0); + if (bands > 3) { + bands = 3; + } + + out.append("gsave").append(EOL); + if (x != 0.0 || y != 0.0) { + out.append(x).append(" ").append(y).append(" translate").append(EOL); + } + if (width != 1.0 || height != 1.0) { + out.append(width).append(" ").append(height).append(" scale").append(EOL); + } + + int decodeScale = 1; + if (bufferedImage.getColorModel().hasAlpha()) { + // TODO Use different InterleaveType (2 or 3) for more efficient compression + out.append("<< /ImageType 3 /InterleaveType 1 ") + .append("/MaskDict ") + .append(imageWidth).append(" ").append(imageHeight).append(" ") + .append(1).append(" ").append(bitsPerSample).append(" ").append(decodeScale).append(" ") + .append(false).append(" ").append(0).append(" imgdict ") + .append("/DataDict ") + .append(imageWidth).append(" ").append(imageHeight).append(" ") + .append(bands).append(" ").append(bitsPerSample).append(" ").append(decodeScale).append(" ") + .append(true).append(" currentfile /ASCII85Decode filter ") + .append("<< /BitsPerComponent ").append(bitsPerSample).append(" >> ") + .append("/FlateDecode filter ") + .append("imgdict ") + .append(">> image").append(EOL); + + // Convert alpha values to binary mask + // FIXME Do alpha conversion in a preprocessing step on commands + bufferedImage = new AlphaToMaskOp(true).filter(bufferedImage, null); + output(bufferedImage, out); + } else { + if (bands == 1) { + out.append("/DeviceGray setcolorspace").append(EOL); + } + if (bufferedImage.getType() == BufferedImage.TYPE_BYTE_BINARY) { + decodeScale = 255; + } + out.append(imageWidth).append(" ").append(imageHeight).append(" ") + .append(bands).append(" ").append(bitsPerSample).append(" ").append(decodeScale).append(" ") + .append(true).append(" currentfile /ASCII85Decode filter ") + .append("<< /BitsPerComponent ").append(bitsPerSample).append(" >> ") + .append("/FlateDecode filter ") + .append("imgdict ") + .append("image").append(EOL); + output(bufferedImage, out); + } + + out.append("grestore"); + return out.toString(); + } + + private static void output(BufferedImage image, StringBuilder out) { + InputStream imageDataStream = + new ImageDataStream(image, Interleaving.SAMPLE); + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + OutputStream compressionStream = new FlateEncodeStream( + new ASCII85EncodeStream( + new LineWrapOutputStream(outBytes, 80))); + try { + DataUtils.transfer(imageDataStream, compressionStream, 1024); + compressionStream.close(); + String compressed = outBytes.toString(CHARSET); + out.append(compressed).append(EOL); + } catch (IOException e) { + // TODO Handle exception + e.printStackTrace(); + } + } + + private static String getOutput(String str, double x, double y) { + + return "gsave 1 -1 scale " + x + " " + -y + " M " + getOutput(str) + " show " + "grestore"; + } + + private static StringBuilder getOutput(String str) { + StringBuilder out = new StringBuilder(); + + // Escape text + str = str.replaceAll("\\\\", "\\\\\\\\") + .replaceAll("\t", "\\\\t") + .replaceAll("\b", "\\\\b") + .replaceAll("\f", "\\\\f") + .replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)") + .replaceAll("[\r\n]", ""); + + out.append("(").append(str).append(")"); + + return out; + } + + private static String getOutput(Stroke s) { + StringBuilder out = new StringBuilder(); + if (s instanceof BasicStroke) { + BasicStroke bs = (BasicStroke) s; + out.append(bs.getLineWidth()).append(" setlinewidth ") + .append(STROKE_LINEJOIN.get(bs.getLineJoin())).append(" setlinejoin ") + .append(STROKE_ENDCAPS.get(bs.getEndCap())).append(" setlinecap ") + .append("[").append(DataUtils.join(" ", bs.getDashArray())).append("] ") + .append(bs.getDashPhase()).append(" setdash"); + } else { + out.append("% Custom strokes aren't supported at the moment"); + } + return out.toString(); + } + + private static String getOutput(Font font) { + StringBuilder out = new StringBuilder(); + font = GraphicsUtils.getPhysicalFont(font); + String fontName = font.getPSName(); + + // Convert font to ISO-8859-1 encoding + String fontNameLatin1 = fontName + FONT_LATIN1_SUFFIX; + out.append("/").append(fontNameLatin1).append(" ") + .append("/").append(font.getPSName()).append(" latinize "); + + // Use encoded font + out.append("/").append(fontNameLatin1).append(" ") + .append(font.getSize2D()).append(" selectfont"); + + return out.toString(); + } + + private void addHeader() { + double x = getPageSize().x * UNITS_PER_MM, + y = getPageSize().y * UNITS_PER_MM, + width = getPageSize().width * UNITS_PER_MM, + height = getPageSize().height * UNITS_PER_MM; + elements.addAll(Arrays.asList( + "%!PS-Adobe-3.0 EPSF-3.0", + "%%BoundingBox: " + ((int) Math.floor(x)) + " " + ((int) Math.floor(y)) + " " + ((int) Math.ceil(x + width)) + " " + ((int) Math.ceil(y + height)), + "%%HiResBoundingBox: " + x + " " + y + " " + (x + width) + " " + (y + height), + "%%LanguageLevel: 3", + "%%Pages: 1", + "%%EndComments", + "%%Page: 1 1", + "/M /moveto load def", + "/L /lineto load def", + "/C /curveto load def", + "/Z /closepath load def", + "/RL /rlineto load def", + "/rgb /setrgbcolor load def", + "/rect { /height exch def /width exch def /y exch def /x exch def x y M width 0 RL 0 height RL width neg 0 RL } bind def", + "/ellipse { /endangle exch def /startangle exch def /ry exch def /rx exch def /y exch def /x exch def /savematrix matrix currentmatrix def x y translate rx ry scale 0 0 1 startangle endangle arcn savematrix setmatrix } bind def", + "/imgdict { /datastream exch def /hasdata exch def /decodeScale exch def /bits exch def /bands exch def /imgheight exch def /imgwidth exch def << /ImageType 1 /Width imgwidth /Height imgheight /BitsPerComponent bits /Decode [bands {0 decodeScale} repeat] /ImageMatrix [imgwidth 0 0 imgheight 0 0] hasdata { /DataSource datastream } if >> } bind def", + "/latinize { /fontName exch def /fontNameNew exch def fontName findfont 0 dict copy begin /Encoding ISOLatin1Encoding def fontNameNew /FontName def currentdict end dup /FID undef fontNameNew exch definefont pop } bind def", + getOutput(GraphicsState.DEFAULT_FONT), + "gsave", + "clipsave", + "/DeviceRGB setcolorspace", + "0 " + height + " translate", + UNITS_PER_MM + " " + (-UNITS_PER_MM) + " scale", + "/basematrix matrix currentmatrix def" + )); + } + + public void write(OutputStream out) throws IOException { + OutputStreamWriter o = new OutputStreamWriter(out, CHARSET); + for (String element : elements) { + if (element == null) { + continue; + } + + // Write current element in lines of 255 bytes (excluding line terminators) + // Numbers must not be separated by line breaks or errors will occur + // TODO: Integrate functionality into LineWrapOutputStream + Matcher chunkMatcher = ELEMENT_SEPARATION_PATTERN.matcher(element); + + boolean chunkFound = false; + while (chunkMatcher.find()) { + chunkFound = true; + String chunk = chunkMatcher.group(); + o.write(chunk, 0, chunk.length()); + o.append(EOL); + } + if (!chunkFound) { + // TODO: Exception, if no whitespace can be found in the chunk + System.err.println("Unable to divide eps element into lines: " + element); + } + } + o.append("%%EOF"); + o.flush(); + } + + public void handle(Command command) { + if (command instanceof SetClipCommand) { + SetClipCommand c = (SetClipCommand) command; + Shape clip = c.getValue(); + elements.add("cliprestore"); + if (clip != null) { + elements.add(getOutput(clip) + " clip"); + } + } else if (command instanceof SetColorCommand) { + SetColorCommand c = (SetColorCommand) command; + elements.add(getOutput(c.getValue())); + } else if (command instanceof SetCompositeCommand) { + SetCompositeCommand c = (SetCompositeCommand) command; + // TODO Implement composite rendering for EPS + elements.add("% composite not yet implemented: " + c.getValue()); + } else if (command instanceof SetFontCommand) { + SetFontCommand c = (SetFontCommand) command; + elements.add(getOutput(c.getValue())); + } else if (command instanceof SetPaintCommand) { + SetPaintCommand c = (SetPaintCommand) command; + // TODO Implement paint rendering for EPS + elements.add("% paint not yet implemented: " + c.getValue()); + } else if (command instanceof SetStrokeCommand) { + SetStrokeCommand c = (SetStrokeCommand) command; + elements.add(getOutput(c.getValue())); + } else if (command instanceof SetTransformCommand) { + SetTransformCommand c = (SetTransformCommand) command; + StringBuilder e = new StringBuilder(); + double[] matrix = new double[6]; + c.getValue().getMatrix(matrix); + e.append("basematrix setmatrix [") + .append(DataUtils.join(" ", matrix)).append("] concat"); + elements.add(e.toString()); + } else if (command instanceof RotateCommand) { + RotateCommand c = (RotateCommand) command; + StringBuilder e = new StringBuilder(); + double x = c.getCenterX(); + double y = c.getCenterY(); + boolean translated = x != 0.0 || y != 0.0; + if (translated) { + e.append(x).append(" ").append(y).append(" translate "); + } + e.append(Math.toDegrees(c.getTheta())).append(" rotate"); + if (translated) { + e.append(" "); + e.append(-x).append(" ").append(-y).append(" translate"); + } + elements.add(e.toString()); + } else if (command instanceof ScaleCommand) { + ScaleCommand c = (ScaleCommand) command; + elements.add(DataUtils.format(c.getScaleX()) + " " + DataUtils.format(c.getScaleY()) + " scale"); + } else if (command instanceof ShearCommand) { + ShearCommand c = (ShearCommand) command; + elements.add("[1 " + DataUtils.format(c.getShearY()) + " " + DataUtils.format(c.getShearX()) + " 1 0 0] concat"); + } else if (command instanceof TransformCommand) { + TransformCommand c = (TransformCommand) command; + StringBuilder e = new StringBuilder(); + double[] matrix = new double[6]; + c.getValue().getMatrix(matrix); + e.append("[").append(DataUtils.join(" ", matrix)) + .append("] concat"); + elements.add(e.toString()); + } else if (command instanceof TranslateCommand) { + TranslateCommand c = (TranslateCommand) command; + elements.add(String.valueOf(c.getDeltaX()) + " " + c.getDeltaY() + " translate"); + } else if (command instanceof DrawImageCommand) { + DrawImageCommand c = (DrawImageCommand) command; + String e = getOutput(c.getValue(), + c.getImageWidth(), c.getImageHeight(), + c.getX(), c.getY(), c.getWidth(), c.getHeight()); + elements.add(e); + } else if (command instanceof DrawShapeCommand) { + DrawShapeCommand c = (DrawShapeCommand) command; + elements.add(getOutput(c.getValue()) + " stroke"); + } else if (command instanceof DrawStringCommand) { + DrawStringCommand c = (DrawStringCommand) command; + elements.add(getOutput(c.getValue(), c.getX(), c.getY())); + } else if (command instanceof FillShapeCommand) { + FillShapeCommand c = (FillShapeCommand) command; + elements.add(getOutput(c.getValue()) + " fill"); + } else if (command instanceof CreateCommand) { + elements.add("gsave"); + } else if (command instanceof DisposeCommand) { + elements.add("grestore"); + } + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/eps/EPSProcessor.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/eps/EPSProcessor.java new file mode 100644 index 0000000..65fa2b3 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/eps/EPSProcessor.java @@ -0,0 +1,23 @@ +package org.xbib.graphics.chart.io.vector.eps; + +import org.xbib.graphics.chart.io.vector.Document; +import org.xbib.graphics.chart.io.vector.Processor; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.filters.FillPaintedShapeAsImageFilter; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +public class EPSProcessor implements Processor { + public Document process(Iterable> commands, PageSize pageSize) { + // TODO Apply rotate(theta,x,y) => translate-rotate-translate filter + // TODO Apply image transparency => image mask filter + // TODO Apply optimization filter + FillPaintedShapeAsImageFilter paintedShapeAsImageFilter = new FillPaintedShapeAsImageFilter(commands); + EPSDocument doc = new EPSDocument(pageSize); + for (Command command : paintedShapeAsImageFilter) { + doc.handle(command); + } + doc.close(); + return doc; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/CommandHandler.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/CommandHandler.java new file mode 100644 index 0000000..ee0500c --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/CommandHandler.java @@ -0,0 +1,8 @@ +package org.xbib.graphics.chart.io.vector.intermediate; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; + +public interface CommandHandler { + void handle(Command command); +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/AffineTransformCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/AffineTransformCommand.java new file mode 100644 index 0000000..bcfa6c5 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/AffineTransformCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.geom.AffineTransform; + +public abstract class AffineTransformCommand extends StateCommand { + public AffineTransformCommand(AffineTransform transform) { + super(transform); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/Command.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/Command.java new file mode 100644 index 0000000..e1858fd --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/Command.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.util.Locale; + +public abstract class Command { + private final T value; + + public Command(T value) { + this.value = value; + } + + public T getValue() { + return value; + } + + @Override + public String toString() { + return String.format((Locale) null, "%s[value=%s]", + getClass().getName(), getValue()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !getClass().equals(obj.getClass())) { + return false; + } + Command o = (Command) obj; + return value == o.value || value.equals(o.value); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/CreateCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/CreateCommand.java new file mode 100644 index 0000000..a48e9bb --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/CreateCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import org.xbib.graphics.chart.io.vector.VectorGraphics2D; + +public class CreateCommand extends StateCommand { + public CreateCommand(VectorGraphics2D graphics) { + super(graphics); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DisposeCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DisposeCommand.java new file mode 100644 index 0000000..d9c20b9 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DisposeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import org.xbib.graphics.chart.io.vector.VectorGraphics2D; + +public class DisposeCommand extends StateCommand { + public DisposeCommand(VectorGraphics2D graphics) { + super(graphics); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawImageCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawImageCommand.java new file mode 100644 index 0000000..60c8ac1 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawImageCommand.java @@ -0,0 +1,58 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Image; +import java.util.Locale; + +public class DrawImageCommand extends Command { + private final int imageWidth; + private final int imageHeight; + private final double x; + private final double y; + private final double width; + private final double height; + + public DrawImageCommand(Image image, int imageWidth, int imageHeight, + double x, double y, double width, double height) { + super(image); + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public int getImageWidth() { + return imageWidth; + } + + public int getImageHeight() { + return imageHeight; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getWidth() { + return width; + } + + public double getHeight() { + return height; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[value=%s, imageWidth=%d, imageHeight=%d, x=%f, y=%f, width=%f, height=%f]", + getClass().getName(), getValue(), + getImageWidth(), getImageHeight(), + getX(), getY(), getWidth(), getHeight()); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawShapeCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawShapeCommand.java new file mode 100644 index 0000000..7757989 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawShapeCommand.java @@ -0,0 +1,12 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; + +import java.awt.Shape; + +public class DrawShapeCommand extends Command { + public DrawShapeCommand(Shape shape) { + super(GraphicsUtils.clone(shape)); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawStringCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawStringCommand.java new file mode 100644 index 0000000..1bb81a0 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/DrawStringCommand.java @@ -0,0 +1,30 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.util.Locale; + + +public class DrawStringCommand extends Command { + private final double x; + private final double y; + + public DrawStringCommand(String string, double x, double y) { + super(string); + this.x = x; + this.y = y; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + @Override + public String toString() { + return String.format((Locale) null, "%s[value=%s, x=%f, y=%f]", + getClass().getName(), getValue(), getX(), getY()); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/FillShapeCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/FillShapeCommand.java new file mode 100644 index 0000000..d8abbeb --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/FillShapeCommand.java @@ -0,0 +1,12 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; + +import java.awt.Shape; + +public class FillShapeCommand extends Command { + public FillShapeCommand(Shape shape) { + super(GraphicsUtils.clone(shape)); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/Group.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/Group.java new file mode 100644 index 0000000..0b67f65 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/Group.java @@ -0,0 +1,16 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.util.LinkedList; +import java.util.List; + +public class Group extends Command>> { + public Group() { + super(new LinkedList>()); + } + + public void add(Command command) { + List> group = getValue(); + group.add(command); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/RotateCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/RotateCommand.java new file mode 100644 index 0000000..e05a203 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/RotateCommand.java @@ -0,0 +1,38 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class RotateCommand extends AffineTransformCommand { + private final double theta; + private final double centerX; + private final double centerY; + + public RotateCommand(double theta, double centerX, double centerY) { + super(AffineTransform.getRotateInstance(theta, centerX, centerY)); + this.theta = theta; + this.centerX = centerX; + this.centerY = centerY; + } + + public double getTheta() { + return theta; + } + + public double getCenterX() { + return centerX; + } + + public double getCenterY() { + return centerY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[theta=%f, centerX=%f, centerY=%f, value=%s]", + getClass().getName(), getTheta(), getCenterX(), getCenterY(), + getValue()); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/ScaleCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/ScaleCommand.java new file mode 100644 index 0000000..2d61a6b --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/ScaleCommand.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class ScaleCommand extends AffineTransformCommand { + private final double scaleX; + private final double scaleY; + + public ScaleCommand(double scaleX, double scaleY) { + super(AffineTransform.getScaleInstance(scaleX, scaleY)); + this.scaleX = scaleX; + this.scaleY = scaleY; + } + + public double getScaleX() { + return scaleX; + } + + public double getScaleY() { + return scaleY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[scaleX=%f, scaleY=%f, value=%s]", getClass().getName(), + getScaleX(), getScaleY(), getValue()); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetBackgroundCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetBackgroundCommand.java new file mode 100644 index 0000000..79baa1f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetBackgroundCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Color; + +public class SetBackgroundCommand extends StateCommand { + public SetBackgroundCommand(Color color) { + super(color); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetClipCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetClipCommand.java new file mode 100644 index 0000000..4d865da --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetClipCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Shape; + +public class SetClipCommand extends StateCommand { + public SetClipCommand(Shape shape) { + super(shape); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetColorCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetColorCommand.java new file mode 100644 index 0000000..d5d3fc1 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetColorCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Color; + +public class SetColorCommand extends StateCommand { + public SetColorCommand(Color color) { + super(color); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetCompositeCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetCompositeCommand.java new file mode 100644 index 0000000..bddd414 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetCompositeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Composite; + +public class SetCompositeCommand extends StateCommand { + public SetCompositeCommand(Composite composite) { + super(composite); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetFontCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetFontCommand.java new file mode 100644 index 0000000..4e4d331 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetFontCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Font; + +public class SetFontCommand extends StateCommand { + public SetFontCommand(Font font) { + super(font); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetHintCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetHintCommand.java new file mode 100644 index 0000000..6a50801 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetHintCommand.java @@ -0,0 +1,24 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.util.Locale; + +public class SetHintCommand extends StateCommand { + private final Object key; + + public SetHintCommand(Object hintKey, Object hintValue) { + super(hintValue); + key = hintKey; + } + + public Object getKey() { + return key; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[key=%s, value=%s]", getClass().getName(), + getKey(), getValue()); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetPaintCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetPaintCommand.java new file mode 100644 index 0000000..b258680 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetPaintCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Paint; + +public class SetPaintCommand extends StateCommand { + public SetPaintCommand(Paint paint) { + super(paint); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetStrokeCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetStrokeCommand.java new file mode 100644 index 0000000..1489e3f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetStrokeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Stroke; + +public class SetStrokeCommand extends StateCommand { + public SetStrokeCommand(Stroke stroke) { + super(stroke); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetTransformCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetTransformCommand.java new file mode 100644 index 0000000..6730a8c --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetTransformCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.geom.AffineTransform; + +public class SetTransformCommand extends StateCommand { + public SetTransformCommand(AffineTransform transform) { + super(new AffineTransform(transform)); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetXORModeCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetXORModeCommand.java new file mode 100644 index 0000000..69239b8 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/SetXORModeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.Color; + +public class SetXORModeCommand extends StateCommand { + public SetXORModeCommand(Color mode) { + super(mode); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/ShearCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/ShearCommand.java new file mode 100644 index 0000000..0efd918 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/ShearCommand.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class ShearCommand extends AffineTransformCommand { + private final double shearX; + private final double shearY; + + public ShearCommand(double shearX, double shearY) { + super(AffineTransform.getShearInstance(shearX, shearY)); + this.shearX = shearX; + this.shearY = shearY; + } + + public double getShearX() { + return shearX; + } + + public double getShearY() { + return shearY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[shearX=%f, shearY=%f, value=%s]", getClass().getName(), + getShearX(), getShearY(), getValue()); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/StateCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/StateCommand.java new file mode 100644 index 0000000..fec7cae --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/StateCommand.java @@ -0,0 +1,8 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +public abstract class StateCommand extends Command { + public StateCommand(T value) { + super(value); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/TransformCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/TransformCommand.java new file mode 100644 index 0000000..deb6a27 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/TransformCommand.java @@ -0,0 +1,17 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.geom.AffineTransform; + +public class TransformCommand extends AffineTransformCommand { + private final AffineTransform transform; + + public TransformCommand(AffineTransform transform) { + super(transform); + this.transform = new AffineTransform(transform); + } + + public AffineTransform getTransform() { + return transform; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/TranslateCommand.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/TranslateCommand.java new file mode 100644 index 0000000..a58f092 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/commands/TranslateCommand.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.chart.io.vector.intermediate.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class TranslateCommand extends AffineTransformCommand { + private final double deltaX; + private final double deltaY; + + public TranslateCommand(double x, double y) { + super(AffineTransform.getTranslateInstance(x, y)); + this.deltaX = x; + this.deltaY = y; + } + + public double getDeltaX() { + return deltaX; + } + + public double getDeltaY() { + return deltaY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[deltaX=%f, deltaY=%f, value=%s]", getClass().getName(), + getDeltaX(), getDeltaY(), getValue()); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/AbsoluteToRelativeTransformsFilter.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/AbsoluteToRelativeTransformsFilter.java new file mode 100644 index 0000000..95a2e63 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/AbsoluteToRelativeTransformsFilter.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.AffineTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.CreateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.TransformCommand; + +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.util.Arrays; +import java.util.List; +import java.util.Stack; + +public class AbsoluteToRelativeTransformsFilter extends Filter { + private Stack transforms; + + public AbsoluteToRelativeTransformsFilter(Iterable> stream) { + super(stream); + transforms = new Stack(); + } + + @Override + public Command next() { + Command nextCommand = super.next(); + if (nextCommand instanceof AffineTransformCommand) { + AffineTransformCommand affineTransformCommand = (AffineTransformCommand) nextCommand; + getCurrentTransform().concatenate(affineTransformCommand.getValue()); + } else if (nextCommand instanceof CreateCommand) { + AffineTransform newTransform = transforms.isEmpty() ? new AffineTransform() : new AffineTransform(getCurrentTransform()); + transforms.push(newTransform); + } else if (nextCommand instanceof DisposeCommand) { + transforms.pop(); + } + + return nextCommand; + } + + @Override + protected List> filter(Command command) { + if (command instanceof SetTransformCommand) { + SetTransformCommand setTransformCommand = (SetTransformCommand) command; + AffineTransform absoluteTransform = setTransformCommand.getValue(); + AffineTransform relativeTransform = new AffineTransform(); + try { + AffineTransform invertedOldTransformation = getCurrentTransform().createInverse(); + relativeTransform.concatenate(invertedOldTransformation); + } catch (NoninvertibleTransformException e) { + e.printStackTrace(); + } + relativeTransform.concatenate(absoluteTransform); + TransformCommand transformCommand = new TransformCommand(relativeTransform); + return Arrays.>asList(transformCommand); + } + return Arrays.>asList(command); + } + + private AffineTransform getCurrentTransform() { + return transforms.peek(); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FillPaintedShapeAsImageFilter.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FillPaintedShapeAsImageFilter.java new file mode 100644 index 0000000..ba43f32 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FillPaintedShapeAsImageFilter.java @@ -0,0 +1,70 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawImageCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.FillShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetPaintCommand; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.List; + +public class FillPaintedShapeAsImageFilter extends Filter { + private SetPaintCommand lastSetPaintCommand; + + public FillPaintedShapeAsImageFilter(Iterable> stream) { + super(stream); + } + + @Override + public Command next() { + Command nextCommand = super.next(); + + if (nextCommand instanceof SetPaintCommand) { + lastSetPaintCommand = (SetPaintCommand) nextCommand; + } else if (nextCommand instanceof DisposeCommand) { + lastSetPaintCommand = null; + } + + return nextCommand; + } + + private DrawImageCommand getDrawImageCommand(FillShapeCommand shapeCommand, SetPaintCommand paintCommand) { + Shape shape = shapeCommand.getValue(); + Rectangle2D shapeBounds = shape.getBounds2D(); + double x = shapeBounds.getX(); + double y = shapeBounds.getY(); + double width = shapeBounds.getWidth(); + double height = shapeBounds.getHeight(); + int imageWidth = (int) Math.round(width); + int imageHeight = (int) Math.round(height); + BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D imageGraphics = (Graphics2D) image.getGraphics(); + imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + imageGraphics.scale(imageWidth / width, imageHeight / height); + imageGraphics.translate(-shapeBounds.getX(), -shapeBounds.getY()); + imageGraphics.setPaint(paintCommand.getValue()); + imageGraphics.fill(shape); + imageGraphics.dispose(); + + DrawImageCommand drawImageCommand = new DrawImageCommand(image, imageWidth, imageHeight, x, y, width, height); + return drawImageCommand; + } + + @Override + protected List> filter(Command command) { + if (lastSetPaintCommand != null && command instanceof FillShapeCommand) { + FillShapeCommand fillShapeCommand = (FillShapeCommand) command; + DrawImageCommand drawImageCommand = getDrawImageCommand(fillShapeCommand, lastSetPaintCommand); + return Arrays.>asList(drawImageCommand); + } + + return Arrays.>asList(command); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/Filter.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/Filter.java new file mode 100644 index 0000000..c937df7 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/Filter.java @@ -0,0 +1,48 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public abstract class Filter implements Iterable>, Iterator> { + private final Queue> buffer; + private final Iterator> iterator; + + public Filter(Iterable> stream) { + buffer = new LinkedList>(); + iterator = stream.iterator(); + } + + public Iterator> iterator() { + return this; + } + + public boolean hasNext() { + findNextCommand(); + return !buffer.isEmpty(); + } + + private void findNextCommand() { + while (buffer.isEmpty() && iterator.hasNext()) { + Command command = iterator.next(); + List> commands = filter(command); + if (commands != null) { + buffer.addAll(commands); + } + } + } + + public Command next() { + findNextCommand(); + return buffer.poll(); + } + + public void remove() { + } + + protected abstract List> filter(Command command); +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/GroupingFilter.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/GroupingFilter.java new file mode 100644 index 0000000..a0e60dc --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/GroupingFilter.java @@ -0,0 +1,47 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Group; + +import java.util.Arrays; +import java.util.List; + + +public abstract class GroupingFilter extends Filter { + private Group group; + + public GroupingFilter(Iterable> stream) { + super(stream); + } + + @Override + public boolean hasNext() { + return group != null || super.hasNext(); + } + + @Override + public Command next() { + if (group == null) { + return super.next(); + } + Group g = group; + group = null; + return g; + } + + @Override + protected List> filter(Command command) { + boolean grouped = isGrouped(command); + if (grouped) { + if (group == null) { + group = new Group(); + } + group.add(command); + return null; + } + return Arrays.>asList(command); + } + + protected abstract boolean isGrouped(Command command); +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/OptimizeFilter.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/OptimizeFilter.java new file mode 100644 index 0000000..82cf1c7 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/OptimizeFilter.java @@ -0,0 +1,57 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.AffineTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetHintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.StateCommand; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class OptimizeFilter extends Filter { + private final Queue> buffer; + + public OptimizeFilter(Iterable> stream) { + super(stream); + buffer = new LinkedList>(); + } + + private static boolean isStateChange(Command command) { + return (command instanceof StateCommand) && + !(command instanceof AffineTransformCommand) && + !(command instanceof SetHintCommand); + } + + @Override + public boolean hasNext() { + return super.hasNext(); + } + + @Override + public Command next() { + if (buffer.isEmpty()) { + return super.next(); + } + return buffer.poll(); + } + + @Override + protected List> filter(Command command) { + if (!isStateChange(command)) { + return Arrays.>asList(command); + } + Iterator> i = buffer.iterator(); + Class cls = command.getClass(); + while (i.hasNext()) { + if (cls.equals(i.next().getClass())) { + i.remove(); + } + } + buffer.add(command); + return null; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/StateChangeGroupingFilter.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/StateChangeGroupingFilter.java new file mode 100644 index 0000000..fe64a15 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/intermediate/filters/StateChangeGroupingFilter.java @@ -0,0 +1,18 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.StateCommand; + + +public class StateChangeGroupingFilter extends GroupingFilter { + + public StateChangeGroupingFilter(Iterable> stream) { + super(stream); + } + + @Override + protected boolean isGrouped(Command command) { + return command instanceof StateCommand; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/GeneratedPayload.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/GeneratedPayload.java new file mode 100644 index 0000000..ef063af --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/GeneratedPayload.java @@ -0,0 +1,30 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import java.io.IOException; + +public abstract class GeneratedPayload extends Payload { + + public GeneratedPayload(boolean stream) { + super(stream); + } + + @Override + public byte[] getBytes() { + try { + for (byte b : generatePayload()) { + super.write(b); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return super.getBytes(); + } + + @Override + public void write(int b) throws IOException { + throw new UnsupportedOperationException("Payload will be calculated and is read only."); + } + + protected abstract byte[] generatePayload(); +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFDocument.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFDocument.java new file mode 100644 index 0000000..e5f4cce --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFDocument.java @@ -0,0 +1,647 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import org.xbib.graphics.chart.io.vector.GraphicsState; +import org.xbib.graphics.chart.io.vector.SizedDocument; +import org.xbib.graphics.chart.io.vector.intermediate.commands.AffineTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.CreateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawImageCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawStringCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.FillShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Group; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetBackgroundCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetClipCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetColorCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetFontCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetHintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetPaintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetStrokeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; +import org.xbib.graphics.chart.io.vector.util.DataUtils; +import org.xbib.graphics.chart.io.vector.util.FlateEncodeStream; +import org.xbib.graphics.chart.io.vector.util.FormattingWriter; +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; +import org.xbib.graphics.chart.io.vector.util.ImageDataStream; +import org.xbib.graphics.chart.io.vector.util.ImageDataStream.Interleaving; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Image; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +public class PDFDocument extends SizedDocument { + private static final String EOL = "\n"; + private static final String CHARSET = "ISO-8859-1"; + private static final String HEADER = "%PDF-1.4"; + private static final String FOOTER = "%%EOF"; + + /** + * Constant to convert values from millimeters to PDF units (1/72th inch). + */ + private static final double MM_IN_UNITS = 72.0 / 25.4; + + /** + * Mapping of stroke endcap values from Java to PDF. + */ + private static final Map STROKE_ENDCAPS = DataUtils.map( + new Integer[]{BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE}, + new Integer[]{0, 1, 2} + ); + + /** + * Mapping of line join values for path drawing from Java to PDF. + */ + private static final Map STROKE_LINEJOIN = DataUtils.map( + new Integer[]{BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL}, + new Integer[]{0, 1, 2} + ); + + private final List objects; + private final Map xref; + private final Map images; + private final Stack states; + private int objectIdCounter; + private PDFObject contents; + private Resources resources; + private boolean transformed; + + private boolean compressed; + + public PDFDocument(PageSize pageSize) { + super(pageSize); + states = new Stack<>(); + states.push(new GraphicsState()); + objects = new LinkedList<>(); + objectIdCounter = 1; + xref = new HashMap<>(); + images = new HashMap<>(); + initPage(); + } + + public static String toString(PDFObject obj) { + StringBuilder out = new StringBuilder(); + out.append(obj.id).append(" ").append(obj.version).append(" obj") + .append(EOL); + if (!obj.dict.isEmpty()) { + out.append(serialize(obj.dict)).append(EOL); + } + if (obj.payload != null) { + String content; + try { + content = new String(obj.payload.getBytes(), CHARSET); + } catch (UnsupportedEncodingException e) { + content = ""; + } + if (content.length() > 0) { + if (obj.payload.isStream()) { + out.append("stream").append(EOL); + } + out.append(content); + if (obj.payload.isStream()) { + out.append("endstream"); + } + out.append(EOL); + } + } + out.append("endobj"); + return out.toString(); + } + + private static String serialize(Object obj) { + if (obj instanceof String) { + return "/" + obj.toString(); + } else if (obj instanceof float[]) { + return serialize(DataUtils.asList((float[]) obj)); + } else if (obj instanceof double[]) { + return serialize(DataUtils.asList((double[]) obj)); + } else if (obj instanceof Object[]) { + return serialize(Arrays.asList((Object[]) obj)); + } else if (obj instanceof List) { + List list = (List) obj; + StringBuilder out = new StringBuilder(); + out.append("["); + int i = 0; + for (Object elem : list) { + if (i++ > 0) { + out.append(" "); + } + out.append(serialize(elem)); + } + out.append("]"); + return out.toString(); + } else if (obj instanceof Map) { + Map dict = (Map) obj; + StringBuilder out = new StringBuilder(); + out.append("<<").append(EOL); + for (Map.Entry entry : dict.entrySet()) { + String key = entry.getKey().toString(); + out.append(serialize(key)).append(" "); + + Object value = entry.getValue(); + out.append(serialize(value)).append(EOL); + } + out.append(">>"); + return out.toString(); + } else if (obj instanceof PDFObject) { + PDFObject pdfObj = (PDFObject) obj; + return String.valueOf(pdfObj.id) + " " + pdfObj.version + " R"; + } else { + return DataUtils.format(obj); + } + } + + private static String getOutput(Color c) { + StringBuilder out = new StringBuilder(); + String r = serialize(c.getRed() / 255.0); + String g = serialize(c.getGreen() / 255.0); + String b = serialize(c.getBlue() / 255.0); + out.append(r).append(" ").append(g).append(" ").append(b).append(" rg ") + .append(r).append(" ").append(g).append(" ").append(b).append(" RG"); + return out.toString(); + } + + private static String getOutput(Shape s) { + StringBuilder out = new StringBuilder(); + PathIterator segments = s.getPathIterator(null); + double[] coordsCur = new double[6]; + double[] pointPrev = new double[2]; + for (int i = 0; !segments.isDone(); i++, segments.next()) { + if (i > 0) { + out.append(" "); + } + int segmentType = segments.currentSegment(coordsCur); + switch (segmentType) { + case PathIterator.SEG_MOVETO: + out.append(serialize(coordsCur[0])).append(" ") + .append(serialize(coordsCur[1])).append(" m"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_LINETO: + out.append(serialize(coordsCur[0])).append(" ") + .append(serialize(coordsCur[1])).append(" l"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_CUBICTO: + out.append(serialize(coordsCur[0])).append(" ") + .append(serialize(coordsCur[1])).append(" ") + .append(serialize(coordsCur[2])).append(" ") + .append(serialize(coordsCur[3])).append(" ") + .append(serialize(coordsCur[4])).append(" ") + .append(serialize(coordsCur[5])).append(" c"); + pointPrev[0] = coordsCur[4]; + pointPrev[1] = coordsCur[5]; + break; + case PathIterator.SEG_QUADTO: + double x1 = pointPrev[0] + 2.0 / 3.0 * (coordsCur[0] - pointPrev[0]); + double y1 = pointPrev[1] + 2.0 / 3.0 * (coordsCur[1] - pointPrev[1]); + double x2 = coordsCur[0] + 1.0 / 3.0 * (coordsCur[2] - coordsCur[0]); + double y2 = coordsCur[1] + 1.0 / 3.0 * (coordsCur[3] - coordsCur[1]); + double x3 = coordsCur[2]; + double y3 = coordsCur[3]; + out.append(serialize(x1)).append(" ") + .append(serialize(y1)).append(" ") + .append(serialize(x2)).append(" ") + .append(serialize(y2)).append(" ") + .append(serialize(x3)).append(" ") + .append(serialize(y3)).append(" c"); + pointPrev[0] = x3; + pointPrev[1] = y3; + break; + case PathIterator.SEG_CLOSE: + out.append("h"); + break; + default: + throw new IllegalStateException("Unknown path operation."); + } + } + + return out.toString(); + } + + private static String getOutput(GraphicsState state, Resources resources, boolean first) { + StringBuilder out = new StringBuilder(); + + if (!first) { + out.append("Q").append(EOL); + } + out.append("q").append(EOL); + + if (!state.getColor().equals(GraphicsState.DEFAULT_COLOR)) { + if (state.getColor().getAlpha() != GraphicsState.DEFAULT_COLOR.getAlpha()) { + double a = state.getColor().getAlpha() / 255.0; + String resourceId = resources.getId(a); + out.append("/").append(resourceId).append(" gs").append(EOL); + } + out.append(getOutput(state.getColor())).append(EOL); + } + if (!state.getTransform().equals(GraphicsState.DEFAULT_TRANSFORM)) { + out.append(getOutput(state.getTransform())).append(" cm").append(EOL); + } + if (!state.getStroke().equals(GraphicsState.DEFAULT_STROKE)) { + out.append(getOutput(state.getStroke())).append(EOL); + } + if (state.getClip() != GraphicsState.DEFAULT_CLIP) { + out.append(getOutput(state.getClip())).append(" W n").append(EOL); + } + if (!state.getFont().equals(GraphicsState.DEFAULT_FONT)) { + Font font = state.getFont(); + String fontResourceId = resources.getId(font); + float fontSize = font.getSize2D(); + out.append("/").append(fontResourceId).append(" ").append(fontSize) + .append(" Tf").append(EOL); + } + + return DataUtils.stripTrailing(out.toString(), EOL); + } + + private static String getOutput(Stroke s) { + StringBuilder out = new StringBuilder(); + if (s instanceof BasicStroke) { + BasicStroke strokeDefault = (BasicStroke) GraphicsState.DEFAULT_STROKE; + BasicStroke strokeNew = (BasicStroke) s; + if (strokeNew.getLineWidth() != strokeDefault.getLineWidth()) { + out.append(serialize(strokeNew.getLineWidth())) + .append(" w").append(EOL); + } + if (strokeNew.getLineJoin() == BasicStroke.JOIN_MITER && strokeNew.getMiterLimit() != strokeDefault.getMiterLimit()) { + out.append(serialize(strokeNew.getMiterLimit())) + .append(" M").append(EOL); + } + if (strokeNew.getLineJoin() != strokeDefault.getLineJoin()) { + out.append(serialize(STROKE_LINEJOIN.get(strokeNew.getLineJoin()))) + .append(" j").append(EOL); + } + if (strokeNew.getEndCap() != strokeDefault.getEndCap()) { + out.append(serialize(STROKE_ENDCAPS.get(strokeNew.getEndCap()))) + .append(" J").append(EOL); + } + if (strokeNew.getDashArray() != strokeDefault.getDashArray()) { + if (strokeNew.getDashArray() != null) { + out.append(serialize(strokeNew.getDashArray())).append(" ") + .append(serialize(strokeNew.getDashPhase())) + .append(" d").append(EOL); + } else { + out.append(EOL).append("[] 0 d").append(EOL); + } + } + } + return out.toString(); + } + + private static String getOutput(AffineTransform transform) { + double[] matrix = new double[6]; + transform.getMatrix(matrix); + return DataUtils.join(" ", matrix); + } + + private static String getOutput(String str, double x, double y) { + + // Save current graphics state + // Undo swapping of y axis + // Render text + // Restore previous graphics state + + return "q " + "1 0 0 -1 " + x + " " + y + " cm " + "BT " + getOutput(str) + " Tj ET " + "Q"; + } + + private static StringBuilder getOutput(String str) { + StringBuilder out = new StringBuilder(); + + // Escape string + str = str.replaceAll("\\\\", "\\\\\\\\") + .replaceAll("\t", "\\\\t") + .replaceAll("\b", "\\\\b") + .replaceAll("\f", "\\\\f") + .replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)") + .replaceAll("[\r\n]", ""); + + out.append("(").append(str).append(")"); + + return out; + } + + private static String getOutput(PDFObject image, double x, double y, + double width, double height, Resources resources) { + // Query image resource id + String resourceId = resources.getId(image); + + // Save graphics state + // Move image to correct position and scale it to (width, height) + // Swap y axis + // Draw image + // Restore old graphics state + + return "q " + width + " 0 0 " + height + " " + x + " " + y + " cm " + "1 0 0 -1 0 1 cm " + "/" + resourceId + " Do " + "Q"; + } + + private GraphicsState getCurrentState() { + return states.peek(); + } + + private void initPage() { + Map dict; + + // Catalog + dict = DataUtils.map( + new String[]{"Type"}, + new Object[]{"Catalog"} + ); + PDFObject catalog = addObject(dict, null); + + // Pages + List pagesKids = new LinkedList<>(); + dict = DataUtils.map( + new String[]{"Type", "Kids", "Count"}, + new Object[]{"Pages", pagesKids, 1} + ); + PDFObject pages = addObject(dict, null); + catalog.dict.put("Pages", pages); + + // Page + double x = getPageSize().x * MM_IN_UNITS; + double y = getPageSize().y * MM_IN_UNITS; + double width = getPageSize().width * MM_IN_UNITS; + double height = getPageSize().height * MM_IN_UNITS; + dict = DataUtils.map( + new String[]{"Type", "Parent", "MediaBox"}, + new Object[]{"Page", pages, new double[]{x, y, width, height}} + ); + PDFObject page = addObject(dict, null); + pagesKids.add(page); + + // Contents + Payload contentsPayload = new Payload(true); + contents = addObject(null, contentsPayload); + page.dict.put("Contents", contents); + + // Compression + if (compressed) { + try { + contentsPayload.addFilter(FlateEncodeStream.class); + contents.dict.put("Filter", new Object[]{"FlateDecode"}); + } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { + // ignore + } + } + + // Initial content + try { + contentsPayload.write(DataUtils.join("", new Object[]{ + "q", EOL, + getOutput(getCurrentState().getColor()), EOL, + MM_IN_UNITS, " 0 0 ", -MM_IN_UNITS, " 0 ", height, " cm", EOL + }).getBytes(CHARSET)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Content length + Payload contentLengthPayload = new SizePayload(contents, CHARSET, false); + PDFObject contentLength = addObject(null, contentLengthPayload); + contents.dict.put("Length", contentLength); + + // Resources + resources = new Resources(objectIdCounter++, 0); + objects.add(resources); + page.dict.put("Resources", resources); + + // Create initial font + Font font = getCurrentState().getFont(); + String fontResourceId = resources.getId(font); + float fontSize = font.getSize2D(); + StringBuilder out = new StringBuilder(); + out.append("/").append(fontResourceId).append(" ").append(fontSize).append(" Tf").append(EOL); + try { + contentsPayload.write(out.toString().getBytes(CHARSET)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private PDFObject addObject(Map dict, Payload payload) { + final int id = objectIdCounter++; + final int version = 0; + PDFObject object = new PDFObject(id, version, dict, payload); + objects.add(object); + return object; + } + + private PDFObject addObject(Image image) { + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + + int width = bufferedImage.getWidth(); + int height = bufferedImage.getHeight(); + int bitsPerSample = DataUtils.max(bufferedImage.getSampleModel().getSampleSize()); + int bands = bufferedImage.getSampleModel().getNumBands(); + String colorSpaceName = (bands == 1) ? "DeviceGray" : "DeviceRGB"; + + Payload imagePayload = new Payload(true); + + // Compression + String[] imageFilters = {}; + if (compressed) { + try { + imagePayload.addFilter(FlateEncodeStream.class); + imageFilters = new String[]{"FlateDecode"}; + } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { + // ignore + } + } + + InputStream imageDataStream = + new ImageDataStream(bufferedImage, Interleaving.WITHOUT_ALPHA); + + try { + DataUtils.transfer(imageDataStream, imagePayload, 1024); + imagePayload.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + int length = imagePayload.getBytes().length; + + Map imageDict = DataUtils.map( + new String[]{"Type", "Subtype", "Width", "Height", "ColorSpace", + "BitsPerComponent", "Length", "Filter"}, + new Object[]{"XObject", "Image", width, height, colorSpaceName, + bitsPerSample, length, imageFilters} + ); + + PDFObject imageObject = addObject(imageDict, imagePayload); + + boolean hasAlpha = bufferedImage.getColorModel().hasAlpha(); + if (hasAlpha) { + BufferedImage mask = GraphicsUtils.getAlphaImage(bufferedImage); + + PDFObject maskObject = addObject(mask); + + boolean isBitmask = mask.getSampleModel().getSampleSize(0) == 1; + if (isBitmask) { + maskObject.dict.put("ImageMask", true); + maskObject.dict.remove("ColorSpace"); + imageObject.dict.put("Mask", maskObject); + } else { + imageObject.dict.put("SMask", maskObject); + } + } + + return imageObject; + } + + public void write(OutputStream out) throws IOException { + FormattingWriter o = new FormattingWriter(out, CHARSET, EOL); + + o.writeln(HEADER); + + for (PDFObject obj : objects) { + xref.put(obj, o.tell()); + o.writeln(toString(obj)); + o.flush(); + } + + long xrefPos = o.tell(); + o.writeln("xref"); + o.write(0).write(" ").writeln(objects.size() + 1); + o.format("%010d %05d f ", 0, 65535).writeln(); + for (PDFObject obj : objects) { + o.format("%010d %05d n ", xref.get(obj), 0).writeln(); + } + o.flush(); + + o.writeln("trailer"); + o.writeln(serialize(DataUtils.map( + new String[]{"Size", "Root"}, + new Object[]{objects.size() + 1, objects.get(0)} + ))); + + o.writeln("startxref"); + o.writeln(xrefPos); + + o.writeln(FOOTER); + o.flush(); + } + + public void handle(Command command) { + String s = ""; + if (command instanceof Group) { + Group c = (Group) command; + applyStateCommands(c.getValue()); + s = getOutput(getCurrentState(), resources, !transformed); + transformed = true; + } else if (command instanceof DrawShapeCommand) { + DrawShapeCommand c = (DrawShapeCommand) command; + s = getOutput(c.getValue()) + " S"; + } else if (command instanceof FillShapeCommand) { + FillShapeCommand c = (FillShapeCommand) command; + s = getOutput(c.getValue()) + " f"; + } else if (command instanceof DrawStringCommand) { + DrawStringCommand c = (DrawStringCommand) command; + s = getOutput(c.getValue(), c.getX(), c.getY()); + } else if (command instanceof DrawImageCommand) { + DrawImageCommand c = (DrawImageCommand) command; + // Create object for image data + Image image = c.getValue(); + PDFObject imageObject = images.get(image.hashCode()); + if (imageObject == null) { + imageObject = addObject(image); + images.put(image.hashCode(), imageObject); + } + s = getOutput(imageObject, c.getX(), c.getY(), + c.getWidth(), c.getHeight(), resources); + } + + try { + Payload contentsPayload = contents.payload; + contentsPayload.write(s.getBytes(CHARSET)); + contentsPayload.write(EOL.getBytes(CHARSET)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void applyStateCommands(List> commands) { + for (Command command : commands) { + if (command instanceof SetHintCommand) { + SetHintCommand c = (SetHintCommand) command; + getCurrentState().getHints().put(c.getKey(), c.getValue()); + } else if (command instanceof SetBackgroundCommand) { + SetBackgroundCommand c = (SetBackgroundCommand) command; + getCurrentState().setBackground(c.getValue()); + } else if (command instanceof SetColorCommand) { + SetColorCommand c = (SetColorCommand) command; + getCurrentState().setColor(c.getValue()); + } else if (command instanceof SetPaintCommand) { + SetPaintCommand c = (SetPaintCommand) command; + getCurrentState().setPaint(c.getValue()); + } else if (command instanceof SetStrokeCommand) { + SetStrokeCommand c = (SetStrokeCommand) command; + getCurrentState().setStroke(c.getValue()); + } else if (command instanceof SetFontCommand) { + SetFontCommand c = (SetFontCommand) command; + getCurrentState().setFont(c.getValue()); + } else if (command instanceof SetTransformCommand) { + throw new UnsupportedOperationException("The PDF format has no means of setting the transformation matrix."); + } else if (command instanceof AffineTransformCommand) { + AffineTransformCommand c = (AffineTransformCommand) command; + AffineTransform stateTransform = getCurrentState().getTransform(); + AffineTransform transformToBeApplied = c.getValue(); + stateTransform.concatenate(transformToBeApplied); + getCurrentState().setTransform(stateTransform); + } else if (command instanceof SetClipCommand) { + SetClipCommand c = (SetClipCommand) command; + getCurrentState().setClip(c.getValue()); + } else if (command instanceof CreateCommand) { + try { + states.push((GraphicsState) getCurrentState().clone()); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + } else if (command instanceof DisposeCommand) { + states.pop(); + } + } + } + + @Override + public void close() { + try { + String footer = "Q" + EOL; + if (transformed) { + footer += "Q" + EOL; + } + Payload contentsPayload = contents.payload; + contentsPayload.write(footer.getBytes(CHARSET)); + contentsPayload.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + super.close(); + } + + public void setCompressed(boolean compressed) { + this.compressed = compressed; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFObject.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFObject.java new file mode 100644 index 0000000..6b49f3f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFObject.java @@ -0,0 +1,22 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class PDFObject { + public final int id; + public final int version; + public final Map dict; + public final Payload payload; + + public PDFObject(int id, int version, Map dict, Payload payload) { + this.dict = new LinkedHashMap<>(); + this.id = id; + this.version = version; + this.payload = payload; + if (dict != null) { + this.dict.putAll(dict); + } + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFProcessor.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFProcessor.java new file mode 100644 index 0000000..e259855 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/PDFProcessor.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import org.xbib.graphics.chart.io.vector.Document; +import org.xbib.graphics.chart.io.vector.Processor; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.filters.AbsoluteToRelativeTransformsFilter; +import org.xbib.graphics.chart.io.vector.intermediate.filters.FillPaintedShapeAsImageFilter; +import org.xbib.graphics.chart.io.vector.intermediate.filters.StateChangeGroupingFilter; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +public class PDFProcessor implements Processor { + + public Document process(Iterable> commands, PageSize pageSize) { + AbsoluteToRelativeTransformsFilter absoluteToRelativeTransformsFilter = new AbsoluteToRelativeTransformsFilter(commands); + FillPaintedShapeAsImageFilter paintedShapeAsImageFilter = new FillPaintedShapeAsImageFilter(absoluteToRelativeTransformsFilter); + Iterable> filtered = new StateChangeGroupingFilter(paintedShapeAsImageFilter); + PDFDocument doc = new PDFDocument(pageSize); + for (Command command : filtered) { + doc.handle(command); + } + doc.close(); + return doc; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/Payload.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/Payload.java new file mode 100644 index 0000000..13f9d20 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/Payload.java @@ -0,0 +1,50 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import org.xbib.graphics.chart.io.vector.util.FlateEncodeStream; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; + +public class Payload extends OutputStream { + private final ByteArrayOutputStream byteStream; + private final boolean stream; + private OutputStream filteredStream; + private boolean empty; + + public Payload(boolean stream) { + byteStream = new ByteArrayOutputStream(); + filteredStream = byteStream; + this.stream = stream; + empty = true; + } + + public byte[] getBytes() { + return byteStream.toByteArray(); + } + + public boolean isStream() { + return stream; + } + + @Override + public void write(int b) throws IOException { + filteredStream.write(b); + empty = false; + } + + @Override + public void close() throws IOException { + super.close(); + filteredStream.close(); + } + + public void addFilter(Class filterClass) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + if (!empty) { + throw new IllegalStateException("unable to add filter after writing to payload"); + } + filteredStream = filterClass.getConstructor(OutputStream.class).newInstance(filteredStream); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/Resources.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/Resources.java new file mode 100644 index 0000000..7532871 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/Resources.java @@ -0,0 +1,99 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import org.xbib.graphics.chart.io.vector.util.DataUtils; +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; + +import java.awt.Font; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class Resources extends PDFObject { + + private static final String KEY_PROC_SET = "ProcSet"; + private static final String KEY_TRANSPARENCY = "ExtGState"; + private static final String KEY_FONT = "Font"; + private static final String KEY_IMAGE = "XObject"; + + private static final String[] VALUE_PROC_SET = {"PDF", "Text", "ImageB", "ImageC", "ImageI"}; + + private static final String PREFIX_FONT = "Fnt"; + private static final String PREFIX_IMAGE = "Img"; + private static final String PREFIX_TRANSPARENCY = "Trp"; + + private final Map fonts; + private final Map images; + private final Map transparencies; + + private final AtomicInteger currentFontId = new AtomicInteger(); + private final AtomicInteger currentImageId = new AtomicInteger(); + private final AtomicInteger currentTransparencyId = new AtomicInteger(); + + public Resources(int id, int version) { + super(id, version, null, null); + fonts = new HashMap<>(); + images = new HashMap<>(); + transparencies = new HashMap<>(); + dict.put(KEY_PROC_SET, VALUE_PROC_SET); + } + + private String getResourceId(Map resources, T resource, + String idPrefix, AtomicInteger idCounter) { + String id = resources.get(resource); + if (id == null) { + id = String.format("%s%d", idPrefix, idCounter.getAndIncrement()); + resources.put(resource, id); + } + return id; + } + + @SuppressWarnings("unchecked") + public String getId(Font font) { + Map> dictEntry = + (Map>) dict.get(KEY_FONT); + if (dictEntry == null) { + dictEntry = new LinkedHashMap<>(); + dict.put(KEY_FONT, dictEntry); + } + font = GraphicsUtils.getPhysicalFont(font); + String resourceId = getResourceId(fonts, font, PREFIX_FONT, currentFontId); + String fontName = font.getPSName(); + // TODO: Determine font encoding (e.g. MacRomanEncoding, MacExpertEncoding, WinAnsiEncoding) + String fontEncoding = "WinAnsiEncoding"; + dictEntry.put(resourceId, DataUtils.map( + new String[]{"Type", "Subtype", "Encoding", "BaseFont"}, + new Object[]{"Font", "TrueType", fontEncoding, fontName} + )); + return resourceId; + } + + @SuppressWarnings("unchecked") + public String getId(PDFObject image) { + Map dictEntry = (Map) dict.get(KEY_IMAGE); + if (dictEntry == null) { + dictEntry = new LinkedHashMap<>(); + dict.put(KEY_IMAGE, dictEntry); + } + String resourceId = getResourceId(images, image, PREFIX_IMAGE, currentImageId); + dictEntry.put(resourceId, image); + return resourceId; + } + + @SuppressWarnings("unchecked") + public String getId(Double transparency) { + Map> dictEntry = + (Map>) dict.get(KEY_TRANSPARENCY); + if (dictEntry == null) { + dictEntry = new LinkedHashMap<>(); + dict.put(KEY_TRANSPARENCY, dictEntry); + } + String resourceId = getResourceId(transparencies, transparency, + PREFIX_TRANSPARENCY, currentTransparencyId); + dictEntry.put(resourceId, DataUtils.map( + new String[]{"Type", "ca", "CA"}, + new Object[]{"ExtGState", transparency, transparency} + )); + return resourceId; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/SizePayload.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/SizePayload.java new file mode 100644 index 0000000..1d3c156 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/pdf/SizePayload.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import org.xbib.graphics.chart.io.vector.util.DataUtils; + +import java.io.IOException; + +public class SizePayload extends GeneratedPayload { + private final PDFObject object; + private final String charset; + + public SizePayload(PDFObject object, String charset, boolean stream) { + super(stream); + this.object = object; + this.charset = charset; + } + + @Override + protected byte[] generatePayload() { + try { + object.payload.close(); + String content = DataUtils.format(object.payload.getBytes().length); + return content.getBytes(charset); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/svg/SVGDocument.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/svg/SVGDocument.java new file mode 100644 index 0000000..82fe7a7 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/svg/SVGDocument.java @@ -0,0 +1,552 @@ +package org.xbib.graphics.chart.io.vector.svg; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.xbib.graphics.chart.io.vector.GraphicsState; +import org.xbib.graphics.chart.io.vector.SizedDocument; +import org.xbib.graphics.chart.io.vector.VectorHints; +import org.xbib.graphics.chart.io.vector.intermediate.commands.AffineTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.CreateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawImageCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawStringCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.FillShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Group; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetBackgroundCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetClipCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetColorCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetCompositeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetFontCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetHintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetPaintCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetStrokeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; +import org.xbib.graphics.chart.io.vector.util.Base64EncodeStream; +import org.xbib.graphics.chart.io.vector.util.DataUtils; +import org.xbib.graphics.chart.io.vector.util.GraphicsUtils; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import javax.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Image; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Stack; + +public class SVGDocument extends SizedDocument { + private static final String SVG_DOCTYPE_QNAME = "svg"; + private static final String SVG_DOCTYPE_PUBLIC_ID = "-//W3C//DTD SVG 1.1//EN"; + private static final String SVG_DOCTYPE_SYSTEM_ID = "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"; + private static final String SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg"; + private static final String XLINK_NAMESPACE = "xlink"; + private static final String XLINK_NAMESPACE_URI = "http://www.w3.org/1999/xlink"; + + private static final String PREFIX_CLIP = "clip"; + + private static final String CHARSET = "UTF-8"; + + private static final double DOTS_PER_MM = 2.834646; // 72 dpi + //private static final double DOTS_PER_MM = 11.811024; // 300 dpi + + /** + * Mapping of stroke endcap values from Java to SVG. + */ + private static final Map STROKE_ENDCAPS = + DataUtils.map(new Integer[]{BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE}, + new String[]{"butt", "round", "square"} + ); + /** + * Mapping of line join values for path drawing from Java to SVG. + */ + private static final Map STROKE_LINEJOIN = + DataUtils.map(new Integer[]{BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL}, + new String[]{"miter", "round", "bevel"} + ); + private final Stack states; + private final Document doc; + private final Element root; + private final Map clippingPathElements; + private Element group; + private boolean groupAdded; + private Element defs; + + public SVGDocument(PageSize pageSize) { + super(pageSize); + + states = new Stack(); + states.push(new GraphicsState()); + clippingPathElements = new HashMap(); + + // Prepare DOM + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + docFactory.setValidating(false); + DocumentBuilder docBuilder; + try { + docBuilder = docFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException("Could not create XML builder."); + } + + // Create XML document with DOCTYPE + DOMImplementation domImpl = docBuilder.getDOMImplementation(); + DocumentType docType = domImpl.createDocumentType(SVG_DOCTYPE_QNAME, SVG_DOCTYPE_PUBLIC_ID, SVG_DOCTYPE_SYSTEM_ID); + doc = domImpl.createDocument(SVG_NAMESPACE_URI, "svg", docType); + // FIXME: Some XML parsers don't support setting standalone to "false" + try { + doc.setXmlStandalone(false); + } catch (AbstractMethodError e) { + System.err.println("Your XML parser does not support standalone XML documents."); + } + + root = doc.getDocumentElement(); + initRoot(); + + group = root; + } + + private static void appendStyle(StringBuilder style, String attribute, Object value) { + style.append(attribute).append(":") + .append(DataUtils.format(value)).append(";"); + } + + private static String getOutput(AffineTransform tx) { + StringBuilder out = new StringBuilder(); + // FIXME: Use tx.getType() to check for transformation components + if (AffineTransform.getTranslateInstance(tx.getTranslateX(), + tx.getTranslateY()).equals(tx)) { + out.append("translate(") + .append(DataUtils.format(tx.getTranslateX())).append(" ") + .append(DataUtils.format(tx.getTranslateY())).append(")"); + } else { + double[] matrix = new double[6]; + tx.getMatrix(matrix); + out.append("matrix(").append(DataUtils.join(" ", matrix)).append(")"); + } + return out.toString(); + } + + private static String getOutput(Color color) { + return String.format((Locale) null, "rgb(%d,%d,%d)", + color.getRed(), color.getGreen(), color.getBlue()); + } + + private static String getOutput(Shape shape) { + StringBuilder out = new StringBuilder(); + PathIterator segments = shape.getPathIterator(null); + double[] coords = new double[6]; + for (int i = 0; !segments.isDone(); i++, segments.next()) { + if (i > 0) { + out.append(" "); + } + int segmentType = segments.currentSegment(coords); + switch (segmentType) { + case PathIterator.SEG_MOVETO: + out.append("M").append(coords[0]).append(",").append(coords[1]); + break; + case PathIterator.SEG_LINETO: + out.append("L").append(coords[0]).append(",").append(coords[1]); + break; + case PathIterator.SEG_CUBICTO: + out.append("C") + .append(coords[0]).append(",").append(coords[1]).append(" ") + .append(coords[2]).append(",").append(coords[3]).append(" ") + .append(coords[4]).append(",").append(coords[5]); + break; + case PathIterator.SEG_QUADTO: + out.append("Q") + .append(coords[0]).append(",").append(coords[1]).append(" ") + .append(coords[2]).append(",").append(coords[3]); + break; + case PathIterator.SEG_CLOSE: + out.append("Z"); + break; + default: + throw new IllegalStateException("Unknown path operation."); + } + } + return out.toString(); + } + + private static String getOutput(Font font) { + StringBuilder out = new StringBuilder(); + if (!GraphicsState.DEFAULT_FONT.getFamily().equals(font.getFamily())) { + String physicalFamily = GraphicsUtils.getPhysicalFont(font).getFamily(); + out.append("font-family:\"").append(physicalFamily).append("\";"); + } + if (font.getSize2D() != GraphicsState.DEFAULT_FONT.getSize2D()) { + out.append("font-size:").append(DataUtils.format(font.getSize2D())).append("px;"); + } + if ((font.getStyle() & Font.ITALIC) != 0) { + out.append("font-style:italic;"); + } + if ((font.getStyle() & Font.BOLD) != 0) { + out.append("font-weight:bold;"); + } + return out.toString(); + } + + private static String getOutput(Image image, boolean lossyAllowed) { + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + + String encoded = encodeImage(bufferedImage, "png"); + if (!GraphicsUtils.usesAlpha(bufferedImage) && lossyAllowed) { + String encodedLossy = encodeImage(bufferedImage, "jpeg"); + if (encodedLossy.length() > 0 && encodedLossy.length() < encoded.length()) { + encoded = encodedLossy; + } + } + + return encoded; + } + + private static String encodeImage(BufferedImage bufferedImage, String format) { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + Base64EncodeStream encodeStream = new Base64EncodeStream(byteStream); + try { + ImageIO.write(bufferedImage, format, encodeStream); + encodeStream.close(); + String encoded = byteStream.toString("ISO-8859-1"); + return String.format("data:image/%s;base64,%s", format, encoded); + } catch (IOException e) { + return ""; + } + } + + private GraphicsState getCurrentState() { + return states.peek(); + } + + private void initRoot() { + double x = getPageSize().x; + double y = getPageSize().y; + double width = getPageSize().width; + double height = getPageSize().height; + + // Add svg element + root.setAttribute("xmlns:" + XLINK_NAMESPACE, XLINK_NAMESPACE_URI); + root.setAttribute("version", "1.1"); + root.setAttribute("x", DataUtils.format(x / DOTS_PER_MM) + "mm"); + root.setAttribute("y", DataUtils.format(y / DOTS_PER_MM) + "mm"); + root.setAttribute("width", DataUtils.format(width / DOTS_PER_MM) + "mm"); + root.setAttribute("height", DataUtils.format(height / DOTS_PER_MM) + "mm"); + root.setAttribute("viewBox", DataUtils.join(" ", new double[]{x, y, width, height})); + } + + public void write(OutputStream out) throws IOException { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + try { + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.STANDALONE, "no"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.setOutputProperty(OutputKeys.ENCODING, CHARSET); + transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, + doc.getDoctype().getPublicId()); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, + doc.getDoctype().getSystemId()); + transformer.transform(new DOMSource(doc), new StreamResult(out)); + } catch (TransformerException e) { + throw new IOException(e.getMessage()); + } + } + + @Override + public String toString() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + write(out); + return out.toString(CHARSET); + } catch (IOException e) { + return ""; + } + } + + private void newGroup() { + group = doc.createElement("g"); + groupAdded = false; + + Shape clip = getCurrentState().getClip(); + if (clip != GraphicsState.DEFAULT_CLIP) { + Element clipElem = getClipElement(clip); + String ref = "url(#" + clipElem.getAttribute("id") + ")"; + group.setAttribute("clip-path", ref); + } + + AffineTransform tx = getCurrentState().getTransform(); + if (!GraphicsState.DEFAULT_TRANSFORM.equals(tx)) { + group.setAttribute("transform", getOutput(tx)); + } + } + + private Element getClipElement(Shape clip) { + // Look for existing entries + Element path = clippingPathElements.get(clip.hashCode()); + if (path != null) { + return path; + } + + // Make sure exists + if (defs == null) { + defs = doc.createElement("defs"); + root.insertBefore(defs, root.getFirstChild()); + } + + // Store clipping path in without styling information + path = doc.createElement("clipPath"); + path.setAttribute("id", PREFIX_CLIP + clip.hashCode()); + Element shape = getElement(clip); + shape.removeAttribute("style"); + path.appendChild(shape); + defs.appendChild(path); + + // Register path + clippingPathElements.put(clip.hashCode(), path); + + return path; + } + + private void addToGroup(Element e) { + group.appendChild(e); + if (!groupAdded && group != root) { + root.appendChild(group); + groupAdded = true; + } + } + + public void handle(Command command) { + if (command instanceof Group) { + Group c = (Group) command; + applyStateCommands(c.getValue()); + if (containsGroupCommand(c.getValue())) { + newGroup(); + } + } else if (command instanceof DrawImageCommand) { + DrawImageCommand c = (DrawImageCommand) command; + Element e = getElement(c.getValue(), + c.getX(), c.getY(), c.getWidth(), c.getHeight()); + addToGroup(e); + } else if (command instanceof DrawShapeCommand) { + DrawShapeCommand c = (DrawShapeCommand) command; + Element e = getElement(c.getValue()); + e.setAttribute("style", getStyle(false)); + addToGroup(e); + } else if (command instanceof DrawStringCommand) { + DrawStringCommand c = (DrawStringCommand) command; + Element e = getElement(c.getValue(), c.getX(), c.getY()); + e.setAttribute("style", getStyle(getCurrentState().getFont())); + addToGroup(e); + } else if (command instanceof FillShapeCommand) { + FillShapeCommand c = (FillShapeCommand) command; + Element e = getElement(c.getValue()); + e.setAttribute("style", getStyle(true)); + addToGroup(e); + } + } + + private void applyStateCommands(List> commands) { + for (Command command : commands) { + GraphicsState state = getCurrentState(); + if (command instanceof SetBackgroundCommand) { + SetBackgroundCommand c = (SetBackgroundCommand) command; + state.setBackground(c.getValue()); + } else if (command instanceof SetClipCommand) { + SetClipCommand c = (SetClipCommand) command; + state.setClip(c.getValue()); + } else if (command instanceof SetColorCommand) { + SetColorCommand c = (SetColorCommand) command; + state.setColor(c.getValue()); + } else if (command instanceof SetCompositeCommand) { + SetCompositeCommand c = (SetCompositeCommand) command; + state.setComposite(c.getValue()); + } else if (command instanceof SetFontCommand) { + SetFontCommand c = (SetFontCommand) command; + state.setFont(c.getValue()); + } else if (command instanceof SetPaintCommand) { + SetPaintCommand c = (SetPaintCommand) command; + state.setPaint(c.getValue()); + } else if (command instanceof SetStrokeCommand) { + SetStrokeCommand c = (SetStrokeCommand) command; + state.setStroke(c.getValue()); + } else if (command instanceof SetTransformCommand) { + SetTransformCommand c = (SetTransformCommand) command; + state.setTransform(c.getValue()); + } else if (command instanceof AffineTransformCommand) { + AffineTransformCommand c = (AffineTransformCommand) command; + AffineTransform stateTransform = state.getTransform(); + AffineTransform transformToBeApplied = c.getValue(); + stateTransform.concatenate(transformToBeApplied); + state.setTransform(stateTransform); + } else if (command instanceof SetHintCommand) { + SetHintCommand c = (SetHintCommand) command; + state.getHints().put(c.getKey(), c.getValue()); + } else if (command instanceof CreateCommand) { + try { + states.push((GraphicsState) getCurrentState().clone()); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + } else if (command instanceof DisposeCommand) { + states.pop(); + } + } + } + + private boolean containsGroupCommand(List> commands) { + for (Command command : commands) { + if ((command instanceof SetClipCommand) || + (command instanceof SetTransformCommand) || + (command instanceof AffineTransformCommand)) { + return true; + } + } + return false; + } + + private String getStyle(boolean filled) { + StringBuilder style = new StringBuilder(); + + Color color = getCurrentState().getColor(); + String colorOutput = getOutput(color); + double opacity = color.getAlpha() / 255.0; + + if (filled) { + appendStyle(style, "fill", colorOutput); + if (color.getAlpha() < 255) { + appendStyle(style, "fill-opacity", opacity); + } + } else { + appendStyle(style, "fill", "none"); + } + + if (!filled) { + appendStyle(style, "stroke", colorOutput); + if (color.getAlpha() < 255) { + appendStyle(style, "stroke-opacity", opacity); + } + Stroke stroke = getCurrentState().getStroke(); + if (stroke instanceof BasicStroke) { + BasicStroke bs = (BasicStroke) stroke; + if (bs.getLineWidth() != 1f) { + appendStyle(style, "stroke-width", bs.getLineWidth()); + } + if (bs.getMiterLimit() != 4f) { + appendStyle(style, "stroke-miterlimit", bs.getMiterLimit()); + } + if (bs.getEndCap() != BasicStroke.CAP_BUTT) { + appendStyle(style, "stroke-linecap", STROKE_ENDCAPS.get(bs.getEndCap())); + } + if (bs.getLineJoin() != BasicStroke.JOIN_MITER) { + appendStyle(style, "stroke-linejoin", STROKE_LINEJOIN.get(bs.getLineJoin())); + } + if (bs.getDashArray() != null) { + appendStyle(style, "stroke-dasharray", DataUtils.join(",", bs.getDashArray())); + if (bs.getDashPhase() != 0f) { + appendStyle(style, "stroke-dashoffset", bs.getDashPhase()); + } + } + } + } else { + appendStyle(style, "stroke", "none"); + } + + return style.toString(); + } + + private String getStyle(Font font) { + String style = getStyle(true); + if (!GraphicsState.DEFAULT_FONT.equals(font)) { + style += getOutput(font); + } + return style; + } + + private Element getElement(Shape shape) { + Element elem; + if (shape instanceof Line2D) { + Line2D s = (Line2D) shape; + elem = doc.createElement("line"); + elem.setAttribute("x1", DataUtils.format(s.getX1())); + elem.setAttribute("y1", DataUtils.format(s.getY1())); + elem.setAttribute("x2", DataUtils.format(s.getX2())); + elem.setAttribute("y2", DataUtils.format(s.getY2())); + } else if (shape instanceof Rectangle2D) { + Rectangle2D s = (Rectangle2D) shape; + elem = doc.createElement("rect"); + elem.setAttribute("x", DataUtils.format(s.getX())); + elem.setAttribute("y", DataUtils.format(s.getY())); + elem.setAttribute("width", DataUtils.format(s.getWidth())); + elem.setAttribute("height", DataUtils.format(s.getHeight())); + } else if (shape instanceof RoundRectangle2D) { + RoundRectangle2D s = (RoundRectangle2D) shape; + elem = doc.createElement("rect"); + elem.setAttribute("x", DataUtils.format(s.getX())); + elem.setAttribute("y", DataUtils.format(s.getY())); + elem.setAttribute("width", DataUtils.format(s.getWidth())); + elem.setAttribute("height", DataUtils.format(s.getHeight())); + elem.setAttribute("rx", DataUtils.format(s.getArcWidth() / 2.0)); + elem.setAttribute("ry", DataUtils.format(s.getArcHeight() / 2.0)); + } else if (shape instanceof Ellipse2D) { + Ellipse2D s = (Ellipse2D) shape; + elem = doc.createElement("ellipse"); + elem.setAttribute("cx", DataUtils.format(s.getCenterX())); + elem.setAttribute("cy", DataUtils.format(s.getCenterY())); + elem.setAttribute("rx", DataUtils.format(s.getWidth() / 2.0)); + elem.setAttribute("ry", DataUtils.format(s.getHeight() / 2.0)); + } else { + elem = doc.createElement("path"); + elem.setAttribute("d", getOutput(shape)); + } + return elem; + } + + private Element getElement(String text, double x, double y) { + Element elem = doc.createElement("text"); + elem.appendChild(doc.createTextNode(text)); + elem.setAttribute("x", DataUtils.format(x)); + elem.setAttribute("y", DataUtils.format(y)); + return elem; + } + + private Element getElement(Image image, double x, double y, double width, double height) { + Element elem = doc.createElement("image"); + elem.setAttribute("x", DataUtils.format(x)); + elem.setAttribute("y", DataUtils.format(y)); + elem.setAttribute("width", DataUtils.format(width)); + elem.setAttribute("height", DataUtils.format(height)); + elem.setAttribute("preserveAspectRatio", "none"); + boolean lossyAllowed = getCurrentState().getHints().get(VectorHints.KEY_EXPORT) == + VectorHints.VALUE_EXPORT_SIZE; + elem.setAttribute("xlink:href", getOutput(image, lossyAllowed)); + return elem; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/svg/SVGProcessor.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/svg/SVGProcessor.java new file mode 100644 index 0000000..90be452 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/svg/SVGProcessor.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.chart.io.vector.svg; + +import org.xbib.graphics.chart.io.vector.Document; +import org.xbib.graphics.chart.io.vector.Processor; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.filters.FillPaintedShapeAsImageFilter; +import org.xbib.graphics.chart.io.vector.intermediate.filters.StateChangeGroupingFilter; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +public class SVGProcessor implements Processor { + public Document process(Iterable> commands, PageSize pageSize) { + FillPaintedShapeAsImageFilter shapesAsImages = new FillPaintedShapeAsImageFilter(commands); + Iterable> filtered = new StateChangeGroupingFilter(shapesAsImages); + SVGDocument doc = new SVGDocument(pageSize); + for (Command command : filtered) { + doc.handle(command); + } + doc.close(); + return doc; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/ASCII85EncodeStream.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/ASCII85EncodeStream.java new file mode 100644 index 0000000..887bece --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/ASCII85EncodeStream.java @@ -0,0 +1,102 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Arrays; + +public class ASCII85EncodeStream extends FilterOutputStream { + private static final Charset ISO88591 = Charset.forName("ISO-8859-1"); + private static final int BASE = 85; + private static final int[] POW_85 = + {BASE * BASE * BASE * BASE, BASE * BASE * BASE, BASE * BASE, BASE, 1}; + private static final char[] CHAR_MAP = + "!\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu" + .toCharArray(); + private final byte[] data; + private final byte[] prefixBytes; + private final byte[] suffixBytes; + private final byte[] encoded; + private boolean closed; + private int dataSize; + private boolean prefixDone; + + public ASCII85EncodeStream(OutputStream out, String prefix, String suffix) { + super(out); + prefixBytes = (prefix != null ? prefix : "").getBytes(ISO88591); + suffixBytes = (suffix != null ? suffix : "").getBytes(ISO88591); + data = new byte[4]; + encoded = new byte[5]; + } + + public ASCII85EncodeStream(OutputStream out) { + this(out, "", "~>"); + } + + private static long toUInt32(byte[] bytes, int size) { + long uint32 = 0L; + for (int i = 0; i < 4 && i < size; i++) { + uint32 |= (bytes[i] & 0xff) << (3 - i) * 8; + } + return toUnsignedInt(uint32); + } + + private static long toUnsignedInt(long x) { + return x & 0x00000000ffffffffL; + } + + @Override + public void write(int b) throws IOException { + if (closed) { + return; + } + if (!prefixDone) { + out.write(prefixBytes); + prefixDone = true; + } + if (dataSize == data.length) { + writeChunk(); + dataSize = 0; + } + data[dataSize++] = (byte) (b & 0xff); + } + + private void writeChunk() throws IOException { + if (dataSize == 0) { + return; + } + long uint32 = toUInt32(data, dataSize); + int padByteCount = data.length - dataSize; + int encodedSize = encodeChunk(uint32, padByteCount); + out.write(encoded, 0, encodedSize); + } + + private int encodeChunk(long uint32, int padByteCount) { + Arrays.fill(encoded, (byte) 0); + if (uint32 == 0L && padByteCount == 0) { + encoded[0] = 'z'; + return 1; + } + int size = encoded.length - padByteCount; + for (int i = 0; i < size; i++) { + encoded[i] = (byte) CHAR_MAP[(int) (uint32 / POW_85[i] % BASE)]; + } + return size; + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + writeChunk(); + + out.write(suffixBytes); + + super.close(); + closed = true; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/AlphaToMaskOp.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/AlphaToMaskOp.java new file mode 100644 index 0000000..0e774d8 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/AlphaToMaskOp.java @@ -0,0 +1,101 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.awt.RenderingHints; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.util.Hashtable; + +public class AlphaToMaskOp implements BufferedImageOp { + private final boolean inverted; + + public AlphaToMaskOp(boolean inverted) { + this.inverted = inverted; + } + + public AlphaToMaskOp() { + this(false); + } + + public boolean isInverted() { + return inverted; + } + + public BufferedImage filter(BufferedImage src, BufferedImage dest) { + ColorModel cm = src.getColorModel(); + + if (dest == null) { + dest = createCompatibleDestImage(src, cm); + } else if (dest.getWidth() != src.getWidth() || dest.getHeight() != src.getHeight()) { + throw new IllegalArgumentException("Source and destination images have different dimensions."); + } else if (dest.getColorModel() != cm) { + throw new IllegalArgumentException("Color models don't match."); + } + + if (cm.hasAlpha()) { + Raster srcRaster = src.getRaster(); + WritableRaster destRaster = dest.getRaster(); + + for (int y = 0; y < srcRaster.getHeight(); y++) { + for (int x = 0; x < srcRaster.getWidth(); x++) { + int argb = cm.getRGB(srcRaster.getDataElements(x, y, null)); + int alpha = argb >>> 24; + if (alpha >= 127 && !isInverted() || alpha < 127 && isInverted()) { + argb |= 0xff000000; + } else { + argb &= 0x00ffffff; + } + destRaster.setDataElements(x, y, cm.getDataElements(argb, null)); + } + } + } + + return dest; + } + + public Rectangle2D getBounds2D(BufferedImage src) { + Rectangle2D bounds = new Rectangle2D.Double(); + bounds.setRect(src.getRaster().getBounds()); + return bounds; + } + + public BufferedImage createCompatibleDestImage(BufferedImage src, + ColorModel destCM) { + if (destCM == null) { + destCM = src.getColorModel(); + } + WritableRaster raster = destCM.createCompatibleWritableRaster( + src.getWidth(), src.getHeight()); + boolean isRasterPremultiplied = destCM.isAlphaPremultiplied(); + Hashtable properties = null; + if (src.getPropertyNames() != null) { + properties = new Hashtable(); + for (String key : src.getPropertyNames()) { + properties.put(key, src.getProperty(key)); + } + } + + BufferedImage bimage = new BufferedImage(destCM, raster, + isRasterPremultiplied, properties); + src.copyData(raster); + return bimage; + } + + public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { + if (dstPt == null) { + dstPt = new Point2D.Double(); + } + dstPt.setLocation(srcPt); + return dstPt; + } + + public RenderingHints getRenderingHints() { + return null; + } + +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/Base64EncodeStream.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/Base64EncodeStream.java new file mode 100644 index 0000000..81a6929 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/Base64EncodeStream.java @@ -0,0 +1,83 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +public class Base64EncodeStream extends FilterOutputStream { + private static final int BASE = 64; + private static final int[] POW_64 = + {BASE * BASE * BASE, BASE * BASE, BASE, 1}; + private static final char[] CHAR_MAP = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + .toCharArray(); + private final byte[] data; + private final byte[] encoded; + private boolean closed; + private int dataSize; + + public Base64EncodeStream(OutputStream out) { + super(out); + data = new byte[3]; + encoded = new byte[4]; + } + + private static long toUInt32(byte[] bytes, int size) { + long uint32 = 0L; + int offset = (3 - size) * 8; + for (int i = size - 1; i >= 0; i--) { + uint32 |= (bytes[i] & 0xff) << offset; + offset += 8; + } + return toUnsignedInt(uint32); + } + + private static long toUnsignedInt(long x) { + return x & 0x00000000ffffffffL; + } + + @Override + public void write(int b) throws IOException { + if (closed) { + return; + } + if (dataSize == data.length) { + writeChunk(); + dataSize = 0; + } + data[dataSize++] = (byte) (b & 0xff); + } + + private void writeChunk() throws IOException { + if (dataSize == 0) { + return; + } + long uint32 = toUInt32(data, dataSize); + int padByteCount = data.length - dataSize; + int encodedSize = encodeChunk(uint32, padByteCount); + out.write(encoded, 0, encodedSize); + } + + private int encodeChunk(long uint32, int padByteCount) { + Arrays.fill(encoded, (byte) '='); + int size = encoded.length - padByteCount; + for (int i = 0; i < size; i++) { + encoded[i] = (byte) CHAR_MAP[(int) (uint32 / POW_64[i] % BASE)]; + } + return encoded.length; + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + writeChunk(); + + super.close(); + closed = true; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/DataUtils.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/DataUtils.java new file mode 100644 index 0000000..faa8a5f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/DataUtils.java @@ -0,0 +1,240 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Abstract class that contains utility functions for working with data + * collections like maps or lists. + */ +public abstract class DataUtils { + /** + * Default constructor that prevents creation of class. + */ + protected DataUtils() { + throw new UnsupportedOperationException(); + } + + /** + * Creates a mapping from two arrays, one with keys, one with values. + * + * @param Data type of the keys. + * @param Data type of the values. + * @param keys Array containing the keys. + * @param values Array containing the values. + * @return Map with keys and values from the specified arrays. + */ + public static Map map(K[] keys, V[] values) { + // Check for valid parameters + if (keys.length != values.length) { + throw new IllegalArgumentException( + "Cannot create a Map: " + + "The number of keys and values differs."); + } + // Fill map with keys and values + Map map = new LinkedHashMap(keys.length); + for (int i = 0; i < keys.length; i++) { + K key = keys[i]; + V value = values[i]; + map.put(key, value); + } + return map; + } + + /** + * Returns a string containing all elements concatenated by a specified + * separator. + * + * @param separator Separator string. + * @param elements List of elements that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, List elements) { + if (elements == null || elements.size() == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(elements.size() * 3); + int i = 0; + for (Object elem : elements) { + if (separator.length() > 0 && i++ > 0) { + sb.append(separator); + } + sb.append(format(elem)); + } + return sb.toString(); + } + + /** + * Returns a string containing all elements concatenated by a specified + * separator. + * + * @param separator Separator string. + * @param elements Array of elements that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, Object[] elements) { + if (elements == null || elements.length == 0) { + return ""; + } + return join(separator, Arrays.asList(elements)); + } + + /** + * Returns a string containing all double numbers concatenated by a + * specified separator. + * + * @param separator Separator string. + * @param elements Array of double numbers that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, double[] elements) { + if (elements == null || elements.length == 0) { + return ""; + } + List list = new ArrayList(elements.length); + for (Double element : elements) { + list.add(element); + } + return join(separator, list); + } + + /** + * Returns a string containing all float numbers concatenated by a + * specified separator. + * + * @param separator Separator string. + * @param elements Array of float numbers that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, float[] elements) { + if (elements == null || elements.length == 0) { + return ""; + } + List list = new ArrayList(elements.length); + for (Float element : elements) { + list.add(element); + } + return join(separator, list); + } + + /** + * Returns the largest of all specified values. + * + * @param values Several integer values. + * @return largest value. + */ + public static int max(int... values) { + int max = values[0]; + for (int i = 1; i < values.length; i++) { + if (values[i] > max) { + max = values[i]; + } + } + return max; + } + + /** + * Copies data from an input stream to an output stream using a buffer of + * specified size. + * + * @param in Input stream. + * @param out Output stream. + * @param bufferSize Size of the copy buffer. + * @throws IOException when an error occurs while copying. + */ + public static void transfer(InputStream in, OutputStream out, int bufferSize) + throws IOException { + byte[] buffer = new byte[bufferSize]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + + /** + * Returns a formatted string of the specified number. All trailing zeroes + * or decimal points will be stripped. + * + * @param number Number to convert to a string. + * @return A formatted string. + */ + public static String format(Number number) { + String formatted; + if (number instanceof Double || number instanceof Float) { + formatted = Double.toString(number.doubleValue()) + .replaceAll("\\.0+$", "") + .replaceAll("(\\.[0-9]*[1-9])0+$", "$1"); + } else { + formatted = number.toString(); + } + return formatted; + } + + /** + * Returns a formatted string of the specified object. + * + * @param obj Object to convert to a string. + * @return A formatted string. + */ + public static String format(Object obj) { + if (obj instanceof Number) { + return format((Number) obj); + } else { + return obj.toString(); + } + } + + /** + * Converts an array of {@code float} numbers to a list of {@code Float}s. + * The list will be empty if the array is empty or {@code null}. + * + * @param elements Array of float numbers. + * @return A list with all numbers as {@code Float}. + */ + public static List asList(float[] elements) { + int size = (elements != null) ? elements.length : 0; + List list = new ArrayList(size); + if (elements != null) { + for (Float elem : elements) { + list.add(elem); + } + } + return list; + } + + /** + * Converts an array of {@code double} numbers to a list of {@code Double}s. + * The list will be empty if the array is empty or {@code null}. + * + * @param elements Array of double numbers. + * @return A list with all numbers as {@code Double}. + */ + public static List asList(double[] elements) { + int size = (elements != null) ? elements.length : 0; + List list = new ArrayList(size); + if (elements != null) { + for (Double elem : elements) { + list.add(elem); + } + } + return list; + } + + /** + * Removes the specified trailing pattern from a string. + * + * @param s string. + * @param substr trailing pattern. + * @return A string without the trailing pattern. + */ + public static String stripTrailing(String s, String substr) { + return s.replaceAll("(" + Pattern.quote(substr) + ")+$", ""); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/FlateEncodeStream.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/FlateEncodeStream.java new file mode 100644 index 0000000..7c4a756 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/FlateEncodeStream.java @@ -0,0 +1,11 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; + +public class FlateEncodeStream extends DeflaterOutputStream { + public FlateEncodeStream(OutputStream out) { + super(out); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/FormattingWriter.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/FormattingWriter.java new file mode 100644 index 0000000..386884d --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/FormattingWriter.java @@ -0,0 +1,66 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.OutputStream; + +public class FormattingWriter implements Closeable, Flushable { + private final OutputStream out; + private final String encoding; + private final String eolString; + private long position; + + public FormattingWriter(OutputStream out, String encoding, String eol) { + this.out = out; + this.encoding = encoding; + this.eolString = eol; + } + + public FormattingWriter write(String string) throws IOException { + byte[] bytes = string.getBytes(encoding); + out.write(bytes, 0, bytes.length); + position += bytes.length; + return this; + } + + public FormattingWriter write(Number number) throws IOException { + write(DataUtils.format(number)); + return this; + } + + public FormattingWriter writeln() throws IOException { + write(eolString); + return this; + } + + public FormattingWriter writeln(String string) throws IOException { + write(string); + write(eolString); + return this; + } + + public FormattingWriter writeln(Number number) throws IOException { + write(number); + write(eolString); + return this; + } + + public FormattingWriter format(String format, Object... args) throws IOException { + write(String.format(null, format, args)); + return this; + } + + public void flush() throws IOException { + out.flush(); + } + + public void close() throws IOException { + out.close(); + } + + public long tell() { + return position; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/GraphicsUtils.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/GraphicsUtils.java new file mode 100644 index 0000000..1b64667 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/GraphicsUtils.java @@ -0,0 +1,411 @@ +package org.xbib.graphics.chart.io.vector.util; + +import javax.swing.ImageIcon; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.HeadlessException; +import java.awt.Image; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.Arc2D; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.QuadCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.PixelGrabber; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.WritableRaster; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; + +/** + * Abstract class that contains utility functions for working with graphics. + * For example, this includes font handling. + */ +public abstract class GraphicsUtils { + private static final FontRenderContext FONT_RENDER_CONTEXT = + new FontRenderContext(null, false, true); + private static final String FONT_TEST_STRING = + "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg"; + private static final FontExpressivenessComparator FONT_EXPRESSIVENESS_COMPARATOR = + new FontExpressivenessComparator(); + + /** + * Default constructor that prevents creation of class. + */ + protected GraphicsUtils() { + throw new UnsupportedOperationException(); + } + + /** + * This method returns {@code true} if the specified image has the + * possibility to store transparent pixels. + * Inspired by http://www.exampledepot.com/egs/java.awt.image/HasAlpha.html + * + * @param image Image that should be checked for alpha channel. + * @return {@code true} if the specified image can have transparent pixels, + * {@code false} otherwise + */ + public static boolean hasAlpha(Image image) { + ColorModel cm; + // If buffered image, the color model is readily available + if (image instanceof BufferedImage) { + BufferedImage bimage = (BufferedImage) image; + cm = bimage.getColorModel(); + } else { + // Use a pixel grabber to retrieve the image's color model; + // grabbing a single pixel is usually sufficient + PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); + try { + pg.grabPixels(); + } catch (InterruptedException e) { + return false; + } + // Get the image's color model + cm = pg.getColorModel(); + } + return cm.hasAlpha(); + } + + /** + * This method returns {@code true} if the specified image has at least one + * pixel that is not fully opaque. + * + * @param image Image that should be checked for non-opaque pixels. + * @return {@code true} if the specified image has transparent pixels, + * {@code false} otherwise + */ + public static boolean usesAlpha(Image image) { + if (image == null) { + return false; + } + BufferedImage bimage = toBufferedImage(image); + Raster alphaRaster = bimage.getAlphaRaster(); + if (alphaRaster == null) { + return false; + } + DataBuffer dataBuffer = alphaRaster.getDataBuffer(); + for (int i = 0; i < dataBuffer.getSize(); i++) { + int alpha = dataBuffer.getElem(i); + if (alpha < 255) { + return true; + } + } + return false; + } + + /** + * Converts an arbitrary image to a {@code BufferedImage}. + * + * @param image Image that should be converted. + * @return a buffered image containing the image pixels, or the original + * instance if the image already was of type {@code BufferedImage}. + */ + public static BufferedImage toBufferedImage(RenderedImage image) { + if (image instanceof BufferedImage) { + return (BufferedImage) image; + } + + ColorModel cm = image.getColorModel(); + WritableRaster raster = cm.createCompatibleWritableRaster( + image.getWidth(), image.getHeight()); + boolean isRasterPremultiplied = cm.isAlphaPremultiplied(); + Hashtable properties = null; + if (image.getPropertyNames() != null) { + properties = new Hashtable(); + for (String key : image.getPropertyNames()) { + properties.put(key, image.getProperty(key)); + } + } + + BufferedImage bimage = new BufferedImage(cm, raster, + isRasterPremultiplied, properties); + image.copyData(raster); + return bimage; + } + + /** + * This method returns a buffered image with the contents of an image. + * Taken from http://www.exampledepot.com/egs/java.awt.image/Image2Buf.html + * + * @param image Image to be converted + * @return a buffered image with the contents of the specified image + */ + public static BufferedImage toBufferedImage(Image image) { + if (image instanceof BufferedImage) { + return (BufferedImage) image; + } + // This code ensures that all the pixels in the image are loaded + image = new ImageIcon(image).getImage(); + // Determine if the image has transparent pixels + boolean hasAlpha = hasAlpha(image); + + // Create a buffered image with a format that's compatible with the + // screen + BufferedImage bimage; + GraphicsEnvironment ge = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + try { + // Determine the type of transparency of the new buffered image + int transparency = Transparency.OPAQUE; + if (hasAlpha) { + transparency = Transparency.TRANSLUCENT; + } + // Create the buffered image + GraphicsDevice gs = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gs.getDefaultConfiguration(); + bimage = gc.createCompatibleImage( + image.getWidth(null), image.getHeight(null), transparency); + } catch (HeadlessException e) { + // The system does not have a screen + bimage = null; + } + if (bimage == null) { + // Create a buffered image using the default color model + int type = BufferedImage.TYPE_INT_RGB; + if (hasAlpha) { + type = BufferedImage.TYPE_INT_ARGB; + } + bimage = new BufferedImage( + image.getWidth(null), image.getHeight(null), type); + } + // Copy image to buffered image + Graphics g = bimage.createGraphics(); + // Paint the image onto the buffered image + g.drawImage(image, 0, 0, null); + g.dispose(); + return bimage; + } + + public static Shape clone(Shape shape) { + if (shape == null) { + return null; + } + Shape clone; + if (shape instanceof Line2D) { + clone = (shape instanceof Line2D.Float) ? + new Line2D.Float() : new Line2D.Double(); + ((Line2D) clone).setLine((Line2D) shape); + } else if (shape instanceof Rectangle) { + clone = new Rectangle((Rectangle) shape); + } else if (shape instanceof Rectangle2D) { + clone = (shape instanceof Rectangle2D.Float) ? + new Rectangle2D.Float() : new Rectangle2D.Double(); + ((Rectangle2D) clone).setRect((Rectangle2D) shape); + } else if (shape instanceof RoundRectangle2D) { + clone = (shape instanceof RoundRectangle2D.Float) ? + new RoundRectangle2D.Float() : new RoundRectangle2D.Double(); + ((RoundRectangle2D) clone).setRoundRect((RoundRectangle2D) shape); + } else if (shape instanceof Ellipse2D) { + clone = (shape instanceof Ellipse2D.Float) ? + new Ellipse2D.Float() : new Ellipse2D.Double(); + ((Ellipse2D) clone).setFrame(((Ellipse2D) shape).getFrame()); + } else if (shape instanceof Arc2D) { + clone = (shape instanceof Arc2D.Float) ? + new Arc2D.Float() : new Arc2D.Double(); + ((Arc2D) clone).setArc((Arc2D) shape); + } else if (shape instanceof Polygon) { + Polygon p = (Polygon) shape; + clone = new Polygon(p.xpoints, p.ypoints, p.npoints); + } else if (shape instanceof CubicCurve2D) { + clone = (shape instanceof CubicCurve2D.Float) ? + new CubicCurve2D.Float() : new CubicCurve2D.Double(); + ((CubicCurve2D) clone).setCurve((CubicCurve2D) shape); + } else if (shape instanceof QuadCurve2D) { + clone = (shape instanceof QuadCurve2D.Float) ? + new QuadCurve2D.Float() : new QuadCurve2D.Double(); + ((QuadCurve2D) clone).setCurve((QuadCurve2D) shape); + } else if (shape instanceof Path2D.Float) { + clone = new Path2D.Float(shape); + } else { + clone = new Path2D.Double(shape); + } + return clone; + } + + private static boolean isLogicalFontFamily(String family) { + return (Font.DIALOG.equals(family) || + Font.DIALOG_INPUT.equals(family) || + Font.SANS_SERIF.equals(family) || + Font.SERIF.equals(family) || + Font.MONOSPACED.equals(family)); + } + + /** + * Try to guess physical font from the properties of a logical font, like + * "Dialog", "Serif", "Monospaced" etc. + * + * @param logicalFont Logical font object. + * @param testText Text used to determine font properties. + * @return An object of the first matching physical font. The original font + * object is returned if it was a physical font or no font matched. + */ + public static Font getPhysicalFont(Font logicalFont, String testText) { + String logicalFamily = logicalFont.getFamily(); + if (!isLogicalFontFamily(logicalFamily)) { + return logicalFont; + } + + final TextLayout logicalLayout = + new TextLayout(testText, logicalFont, FONT_RENDER_CONTEXT); + + // Create a list of matches sorted by font expressiveness (in descending order) + Queue physicalFonts = + new PriorityQueue(1, FONT_EXPRESSIVENESS_COMPARATOR); + + Font[] allPhysicalFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); + for (Font physicalFont : allPhysicalFonts) { + String physicalFamily = physicalFont.getFamily(); + // Skip logical fonts + if (isLogicalFontFamily(physicalFamily)) { + continue; + } + + // Derive identical variant of physical font + physicalFont = physicalFont.deriveFont( + logicalFont.getStyle(), logicalFont.getSize2D()); + TextLayout physicalLayout = + new TextLayout(testText, physicalFont, FONT_RENDER_CONTEXT); + + // Compare various properties of physical and logical font + if (physicalLayout.getBounds().equals(logicalLayout.getBounds()) && + physicalLayout.getAscent() == logicalLayout.getAscent() && + physicalLayout.getDescent() == logicalLayout.getDescent() && + physicalLayout.getLeading() == logicalLayout.getLeading() && + physicalLayout.getAdvance() == logicalLayout.getAdvance() && + physicalLayout.getVisibleAdvance() == logicalLayout.getVisibleAdvance()) { + // Store matching font in list + physicalFonts.add(physicalFont); + } + } + + // Return a valid font even when no matching font could be found + if (physicalFonts.isEmpty()) { + return logicalFont; + } + + return physicalFonts.poll(); + } + + public static Font getPhysicalFont(Font logicalFont) { + return getPhysicalFont(logicalFont, FONT_TEST_STRING); + } + + public static BufferedImage getAlphaImage(BufferedImage image) { + WritableRaster alphaRaster = image.getAlphaRaster(); + int width = image.getWidth(); + int height = image.getHeight(); + + ColorModel cm; + WritableRaster raster; + // TODO Handle bitmap masks (work on ImageDataStream is necessary) + /* + if (image.getTransparency() == BufferedImage.BITMASK) { + byte[] arr = {(byte) 0, (byte) 255}; + + cm = new IndexColorModel(1, 2, arr, arr, arr); + raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, + width, height, 1, 1, null); + } else {*/ + ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); + int[] bits = {8}; + cm = new ComponentColorModel(colorSpace, bits, false, true, + Transparency.OPAQUE, DataBuffer.TYPE_BYTE); + raster = cm.createCompatibleWritableRaster(width, height); + //} + + BufferedImage alphaImage = new BufferedImage(cm, raster, false, null); + + int[] alphaValues = new int[image.getWidth() * alphaRaster.getNumBands()]; + for (int y = 0; y < image.getHeight(); y++) { + alphaRaster.getPixels(0, y, image.getWidth(), 1, alphaValues); + // FIXME Don't force 8-bit alpha channel (see TODO above) + if (image.getTransparency() == BufferedImage.BITMASK) { + for (int i = 0; i < alphaValues.length; i++) { + if (alphaValues[i] > 0) { + alphaValues[i] = 255; + } + } + } + alphaImage.getRaster().setPixels(0, y, image.getWidth(), 1, alphaValues); + } + + return alphaImage; + } + + public static boolean equals(Shape shapeA, Shape shapeB) { + PathIterator pathAIterator = shapeA.getPathIterator(null); + PathIterator pathBIterator = shapeB.getPathIterator(null); + + if (pathAIterator.getWindingRule() != pathBIterator.getWindingRule()) { + return false; + } + double[] pathASegment = new double[6]; + double[] pathBSegment = new double[6]; + while (!pathAIterator.isDone()) { + int pathASegmentType = pathAIterator.currentSegment(pathASegment); + int pathBSegmentType = pathBIterator.currentSegment(pathBSegment); + if (pathASegmentType != pathBSegmentType) { + return false; + } + for (int segmentIndex = 0; segmentIndex < pathASegment.length; segmentIndex++) { + if (pathASegment[segmentIndex] != pathBSegment[segmentIndex]) { + return false; + } + } + + pathAIterator.next(); + pathBIterator.next(); + } + // When the iterator of shapeA is done and shapeA equals shapeB, the iterator of shapeB must also be done + if (!pathBIterator.isDone()) { + return false; + } + return true; + } + + private static class FontExpressivenessComparator implements Comparator { + private static final int[] STYLES = { + Font.PLAIN, Font.ITALIC, Font.BOLD, Font.BOLD | Font.ITALIC + }; + + public int compare(Font font1, Font font2) { + if (font1 == font2) { + return 0; + } + Set variantNames1 = new HashSet(); + Set variantNames2 = new HashSet(); + for (int style : STYLES) { + variantNames1.add(font1.deriveFont(style).getPSName()); + variantNames2.add(font2.deriveFont(style).getPSName()); + } + if (variantNames1.size() < variantNames2.size()) { + return 1; + } else if (variantNames1.size() > variantNames2.size()) { + return -1; + } + return font1.getName().compareTo(font2.getName()); + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/ImageDataStream.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/ImageDataStream.java new file mode 100644 index 0000000..e44e7ae --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/ImageDataStream.java @@ -0,0 +1,131 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.Queue; + +public class ImageDataStream extends InputStream { + private final BufferedImage image; + private final int width; + private final int height; + private final Interleaving interleaving; + private final Raster raster; + private final boolean opaque; + private final Queue byteBuffer; + private final int[] sampleValues; + private final int[] sampleSizes; + private int x; + private int y; + + public ImageDataStream(BufferedImage image, Interleaving interleaving) { + this.image = image; + this.interleaving = interleaving; + + width = image.getWidth(); + height = image.getHeight(); + x = -1; + y = 0; + + Raster alphaRaster = image.getAlphaRaster(); + if (interleaving == Interleaving.ALPHA_ONLY) { + raster = alphaRaster; + } else { + raster = image.getRaster(); + } + opaque = alphaRaster == null; + + byteBuffer = new LinkedList(); + sampleValues = new int[raster.getNumBands()]; + sampleSizes = raster.getSampleModel().getSampleSize(); + } + + public BufferedImage getImage() { + return image; + } + + public Interleaving getInterleaving() { + return interleaving; + } + + @Override + public int read() throws IOException { + if (!byteBuffer.isEmpty()) { + return byteBuffer.poll(); + } else { + if (!nextSample()) { + return -1; + } + int bands = sampleValues.length; + if (interleaving == Interleaving.WITHOUT_ALPHA || + interleaving == Interleaving.ALPHA_ONLY) { + if (interleaving == Interleaving.WITHOUT_ALPHA && !opaque) { + // Ignore alpha band + bands--; + } + for (int band = 0; band < bands; band++) { + bufferSampleValue(band); + } + } else { + if (opaque) { + for (int band = 0; band < bands; band++) { + bufferSampleValue(band); + } + } else { + for (int band = 0; band < bands; band++) { + // Fix order to be ARGB instead of RGBA + if (band == 0) { + bufferSampleValue(bands - 1); + } else { + bufferSampleValue(band - 1); + } + } + } + } + if (!byteBuffer.isEmpty()) { + return byteBuffer.poll(); + } else { + return -1; + } + } + } + + private void bufferSampleValue(int band) { + if (sampleSizes[band] < 8) { + int byteValue = sampleValues[band] & 0xFF; + byteBuffer.offer(byteValue); + } else { + int byteCount = sampleSizes[band] / 8; + for (int i = byteCount - 1; i >= 0; i--) { + int byteValue = (sampleValues[band] >> i * 8) & 0xFF; + byteBuffer.offer(byteValue); + } + } + } + + private boolean nextSample() { + if (interleaving == Interleaving.SAMPLE || interleaving == Interleaving.WITHOUT_ALPHA) { + x++; + if (x >= width) { + x = 0; + y++; + } + } + if (x < 0 || x >= width || y < 0 || y >= height) { + return false; + } else { + raster.getPixel(x, y, sampleValues); + return true; + } + } + + public enum Interleaving { + SAMPLE, + ROW, + WITHOUT_ALPHA, + ALPHA_ONLY + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/LineWrapOutputStream.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/LineWrapOutputStream.java new file mode 100644 index 0000000..71263da --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/LineWrapOutputStream.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class LineWrapOutputStream extends FilterOutputStream { + public static final String STANDARD_EOL = "\r\n"; + + private final int lineWidth; + private final byte[] eolBytes; + private int written; + + public LineWrapOutputStream(OutputStream sink, int lineWidth, String eol) { + super(sink); + this.lineWidth = lineWidth; + this.eolBytes = eol.getBytes(); + if (lineWidth <= 0) { + throw new IllegalArgumentException("Width must be at least 0."); + } + } + + public LineWrapOutputStream(OutputStream sink, int lineWidth) { + this(sink, lineWidth, STANDARD_EOL); + } + + @Override + public void write(int b) throws IOException { + if (written == lineWidth) { + out.write(eolBytes); + written = 0; + } + out.write(b); + written++; + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/PageSize.java b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/PageSize.java new file mode 100644 index 0000000..f51f2c7 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/io/vector/util/PageSize.java @@ -0,0 +1,48 @@ +package org.xbib.graphics.chart.io.vector.util; + +import java.awt.geom.Rectangle2D; + +public class PageSize { + private static final double MM_PER_INCH = 2.54; + public static final PageSize TABLOID = new PageSize(11.0 * MM_PER_INCH, 17.0 * MM_PER_INCH); + public static final PageSize LETTER = new PageSize(8.5 * MM_PER_INCH, 11.0 * MM_PER_INCH); + public static final PageSize LEGAL = new PageSize(8.5 * MM_PER_INCH, 14.0 * MM_PER_INCH); + public static final PageSize LEDGER = TABLOID.getLandscape(); + public static final PageSize A3 = new PageSize(297.0, 420.0); + public static final PageSize A4 = new PageSize(210.0, 297.0); + public static final PageSize A5 = new PageSize(148.0, 210.0); + public final double x; + public final double y; + public final double width; + public final double height; + + public PageSize(double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public PageSize(double width, double height) { + this(0.0, 0.0, width, height); + } + + public PageSize(Rectangle2D size) { + this(size.getX(), size.getY(), size.getWidth(), size.getHeight()); + } + + public PageSize getPortrait() { + if (width <= height) { + return this; + } + return new PageSize(x, y, height, width); + } + + public PageSize getLandscape() { + if (width >= height) { + return this; + } + return new PageSize(x, y, height, width); + } +} + diff --git a/chart/src/main/java/org/xbib/graphics/chart/legend/AbstractLegend.java b/chart/src/main/java/org/xbib/graphics/chart/legend/AbstractLegend.java new file mode 100644 index 0000000..20be123 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/legend/AbstractLegend.java @@ -0,0 +1,234 @@ +package org.xbib.graphics.chart.legend; + +import org.xbib.graphics.chart.theme.Theme; +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.LinkedHashMap; +import java.util.Map; + +public abstract class AbstractLegend implements Legend { + + protected static final int BOX_SIZE = 20; + protected static final int BOX_OUTLINE_WIDTH = 5; + + private static final int LEGEND_MARGIN = 6; + private static final int MULTI_LINE_SPACE = 3; + + protected Chart chart; + protected Rectangle2D bounds; + protected double xOffset = 0; + protected double yOffset = 0; + + protected AbstractLegend(Chart chart) { + this.chart = chart; + } + + public abstract double getSeriesLegendRenderGraphicHeight(S series); + + protected abstract void doPaint(Graphics2D g); + + @Override + public Rectangle2D getBounds() { + if (chart.getStyler().getLegendLayout() == LegendLayout.Vertical) { + return getBoundsHintVertical(); + } else { + return getBoundsHintHorizontal(); + } + } + + @Override + public void paint(Graphics2D g) { + if (!chart.getStyler().isLegendVisible()) { + return; + } + if (chart.getSeriesMap().isEmpty()) { + return; + } + if (chart.getPlot().getBounds().getWidth() < 30) { + return; + } + if (chart.getStyler().getLegendLayout() == LegendLayout.Vertical) { + bounds = getBoundsHintVertical(); + } else { + bounds = getBoundsHintHorizontal(); + } + double height = bounds.getHeight(); + switch (chart.getStyler().getLegendPosition()) { + case OutsideE: + xOffset = chart.getWidth() - bounds.getWidth() - chart.getStyler().getChartPadding(); + yOffset = chart.getPlot().getBounds().getY() + (chart.getPlot().getBounds().getHeight() - bounds.getHeight()) / 2.0; + break; + case InsideNW: + xOffset = chart.getPlot().getBounds().getX() + LEGEND_MARGIN; + yOffset = chart.getPlot().getBounds().getY() + LEGEND_MARGIN; + break; + case InsideNE: + xOffset = chart.getPlot().getBounds().getX() + chart.getPlot().getBounds().getWidth() - bounds.getWidth() - LEGEND_MARGIN; + yOffset = chart.getPlot().getBounds().getY() + LEGEND_MARGIN; + break; + case InsideSE: + xOffset = chart.getPlot().getBounds().getX() + chart.getPlot().getBounds().getWidth() - bounds.getWidth() - LEGEND_MARGIN; + yOffset = chart.getPlot().getBounds().getY() + chart.getPlot().getBounds().getHeight() - bounds.getHeight() - LEGEND_MARGIN; + break; + case InsideSW: + xOffset = chart.getPlot().getBounds().getX() + LEGEND_MARGIN; + yOffset = chart.getPlot().getBounds().getY() + chart.getPlot().getBounds().getHeight() - bounds.getHeight() - LEGEND_MARGIN; + break; + case InsideN: + xOffset = chart.getPlot().getBounds().getX() + (chart.getPlot().getBounds().getWidth() - bounds.getWidth()) / 2 + LEGEND_MARGIN; + yOffset = chart.getPlot().getBounds().getY() + LEGEND_MARGIN; + break; + case OutsideS: + xOffset = chart.getPlot().getBounds().getX() + (chart.getPlot().getBounds().getWidth() - bounds.getWidth()) / 2.0; + yOffset = chart.getHeight() - bounds.getHeight() - LEGEND_MARGIN; + break; + default: + break; + } + Shape rect = new Rectangle2D.Double(xOffset, yOffset, bounds.getWidth(), height); + g.setColor(chart.getStyler().getLegendBackgroundColor()); + g.fill(rect); + g.setStroke(Theme.Strokes.LEGEND); + g.setColor(chart.getStyler().getLegendBorderColor()); + g.draw(rect); + doPaint(g); + } + + protected Map getSeriesTextBounds(S series) { + String[] lines = series.getName().split("\\n"); + Map seriesTextBounds = new LinkedHashMap<>(lines.length); + for (String line : lines) { + TextLayout textLayout = new TextLayout(line, chart.getStyler().getLegendFont(), new FontRenderContext(null, true, false)); + Shape shape = textLayout.getOutline(null); + Rectangle2D bounds = shape.getBounds2D(); + seriesTextBounds.put(line, bounds); + } + return seriesTextBounds; + } + + protected float getLegendEntryHeight(Map seriesTextBounds, int markerSize) { + float legendEntryHeight = 0; + for (Map.Entry entry : seriesTextBounds.entrySet()) { + legendEntryHeight += entry.getValue().getHeight() + MULTI_LINE_SPACE; + } + legendEntryHeight -= MULTI_LINE_SPACE; + legendEntryHeight = Math.max(legendEntryHeight, markerSize); + return legendEntryHeight; + } + + protected float getLegendEntryWidth(Map seriesTextBounds, int markerSize) { + float legendEntryWidth = 0; + for (Map.Entry entry : seriesTextBounds.entrySet()) { + legendEntryWidth = Math.max(legendEntryWidth, (float) entry.getValue().getWidth()); + } + return legendEntryWidth + markerSize + chart.getStyler().getLegendPadding(); + } + + protected void paintSeriesText(Graphics2D g, Map seriesTextBounds, int markerSize, double x, double starty) { + g.setColor(chart.getStyler().getChartFontColor()); + g.setFont(chart.getStyler().getLegendFont()); + double multiLineOffset = 0.0; + for (Map.Entry entry : seriesTextBounds.entrySet()) { + double height = entry.getValue().getHeight(); + double centerOffsetY = (Math.max(markerSize, height) - height) / 2.0; + FontRenderContext frc = g.getFontRenderContext(); + TextLayout tl = new TextLayout(entry.getKey(), chart.getStyler().getLegendFont(), frc); + Shape shape = tl.getOutline(null); + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + at.translate(x, starty + height + centerOffsetY + multiLineOffset); + g.transform(at); + g.fill(shape); + g.setTransform(orig); + multiLineOffset += height + MULTI_LINE_SPACE; + } + } + + /** + * Determine the width and height of the chart legend. + */ + private Rectangle2D getBoundsHintVertical() { + if (!chart.getStyler().isLegendVisible()) { + return new Rectangle2D.Double(); + } + boolean containsBox = false; + double legendTextContentMaxWidth = 0; + double legendContentHeight = 0; + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (series.isNotShownInLegend()) { + continue; + } + if (!series.isEnabled()) { + continue; + } + Map seriesTextBounds = getSeriesTextBounds(series); + double legendEntryHeight = 0; + for (Map.Entry entry : seriesTextBounds.entrySet()) { + legendEntryHeight += entry.getValue().getHeight() + MULTI_LINE_SPACE; + legendTextContentMaxWidth = Math.max(legendTextContentMaxWidth, entry.getValue().getWidth()); + } + legendEntryHeight -= MULTI_LINE_SPACE; + legendEntryHeight = Math.max(legendEntryHeight, (getSeriesLegendRenderGraphicHeight(series))); + legendContentHeight += legendEntryHeight + chart.getStyler().getLegendPadding(); + if (series.getLegendRenderType() == LegendRenderType.Box) { + containsBox = true; + } + } + double legendContentWidth; + if (!containsBox) { + legendContentWidth = chart.getStyler().getLegendSeriesLineLength() + chart.getStyler().getLegendPadding() + legendTextContentMaxWidth; + } else { + legendContentWidth = BOX_SIZE + chart.getStyler().getLegendPadding() + legendTextContentMaxWidth; + } + double width = legendContentWidth + 2 * chart.getStyler().getLegendPadding(); + double height = legendContentHeight + chart.getStyler().getLegendPadding(); + return new Rectangle2D.Double(Double.NaN, Double.NaN, width, height); + } + + private Rectangle2D getBoundsHintHorizontal() { + if (!chart.getStyler().isLegendVisible()) { + return new Rectangle2D.Double(); + } + double legendTextContentMaxHeight = 0; + double legendContentWidth = 0; + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (series.isNotShownInLegend()) { + continue; + } + if (!series.isEnabled()) { + continue; + } + Map seriesTextBounds = getSeriesTextBounds(series); + double legendEntryHeight = 0; + double legendEntryMaxWidth = 0; + for (Map.Entry entry : seriesTextBounds.entrySet()) { + legendEntryHeight += entry.getValue().getHeight() + MULTI_LINE_SPACE; + legendEntryMaxWidth = Math.max(legendEntryMaxWidth, entry.getValue().getWidth()); + } + legendEntryHeight -= MULTI_LINE_SPACE; + legendTextContentMaxHeight = Math.max(legendEntryHeight, getSeriesLegendRenderGraphicHeight(series)); + legendContentWidth += legendEntryMaxWidth + chart.getStyler().getLegendPadding(); + if (series.getLegendRenderType() == LegendRenderType.Line) { + legendContentWidth = chart.getStyler().getLegendSeriesLineLength() + + chart.getStyler().getLegendPadding() + + legendContentWidth; + } else { + legendContentWidth = BOX_SIZE + chart.getStyler().getLegendPadding() + legendContentWidth; + } + } + double width = legendContentWidth + chart.getStyler().getLegendPadding(); + double height = legendTextContentMaxHeight + chart.getStyler().getLegendPadding() * 2; + return new Rectangle2D.Double(0, 0, width, height); + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/legend/Legend.java b/chart/src/main/java/org/xbib/graphics/chart/legend/Legend.java new file mode 100644 index 0000000..2397978 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/legend/Legend.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.legend; + +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; + +public interface Legend extends ChartComponent { + + double getSeriesLegendRenderGraphicHeight(S series); +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/legend/LegendLayout.java b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendLayout.java new file mode 100644 index 0000000..397b70b --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendLayout.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.legend; + +public enum LegendLayout { + Vertical, Horizontal +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/legend/LegendPosition.java b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendPosition.java new file mode 100644 index 0000000..3f6791e --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendPosition.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.legend; + +public enum LegendPosition { + OutsideE, InsideNW, InsideNE, InsideSE, InsideSW, InsideN, InsideS, OutsideS +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/legend/LegendRenderType.java b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendRenderType.java new file mode 100644 index 0000000..ff5cb8e --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendRenderType.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.legend; + +public enum LegendRenderType { + Line, Scatter, Box, BoxNoOutline +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/legend/LegendRenderable.java b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendRenderable.java new file mode 100644 index 0000000..c891cad --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/legend/LegendRenderable.java @@ -0,0 +1,6 @@ +package org.xbib.graphics.chart.legend; + +public interface LegendRenderable { + + LegendRenderType getLegendRenderType(); +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/legend/MarkerLegend.java b/chart/src/main/java/org/xbib/graphics/chart/legend/MarkerLegend.java new file mode 100644 index 0000000..9575711 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/legend/MarkerLegend.java @@ -0,0 +1,117 @@ +package org.xbib.graphics.chart.legend; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.series.MarkerSeries; +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.BasicStroke; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +public class MarkerLegend extends AbstractLegend { + + private final ST axesChartStyler; + + public MarkerLegend(Chart chart) { + super(chart); + axesChartStyler = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double startx = xOffset + chart.getStyler().getLegendPadding(); + double starty = yOffset + chart.getStyler().getLegendPadding(); + Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (series.isNotShownInLegend()) { + continue; + } + if (!series.isEnabled()) { + continue; + } + Map seriesTextBounds = getSeriesTextBounds(series); + float legendEntryHeight = getLegendEntryHeight(seriesTextBounds, ((series.getLegendRenderType() == LegendRenderType.Line || + series.getLegendRenderType() == LegendRenderType.Scatter) ? + axesChartStyler.getMarkerSize() : BOX_SIZE)); + if (series.getLegendRenderType() == LegendRenderType.Line || + series.getLegendRenderType() == LegendRenderType.Scatter) { + if (series.getLegendRenderType() == LegendRenderType.Line + && series.getLineStyle() != Theme.Series.NONE_STROKE) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + Shape line = new Line2D.Double(startx, + starty + legendEntryHeight / 2.0, + startx + chart.getStyler().getLegendSeriesLineLength(), + starty + legendEntryHeight / 2.0); + g.draw(line); + } + if (series.getMarker() != null) { + g.setColor(series.getMarkerColor()); + series.getMarker().paint(g, + startx + chart.getStyler().getLegendSeriesLineLength() / 2.0, + starty + legendEntryHeight / 2.0, + axesChartStyler.getMarkerSize()); + } + } else { + Shape rectSmall = new Rectangle2D.Double(startx, starty, BOX_SIZE, BOX_SIZE); + g.setColor(series.getFillColor()); + g.fill(rectSmall); + if (series.getLegendRenderType() != LegendRenderType.BoxNoOutline) { + g.setColor(series.getLineColor()); + BasicStroke existingLineStyle = series.getLineStyle(); + BasicStroke newLineStyle = new BasicStroke(Math.min(existingLineStyle.getLineWidth(), BOX_OUTLINE_WIDTH * 0.5f), + existingLineStyle.getEndCap(), + existingLineStyle.getLineJoin(), + existingLineStyle.getMiterLimit(), + existingLineStyle.getDashArray(), + existingLineStyle.getDashPhase()); + g.setPaint(series.getLineColor()); + g.setStroke(newLineStyle); + Path2D.Double outlinePath = new Path2D.Double(); + double lineOffset = existingLineStyle.getLineWidth() * 0.5; + outlinePath.moveTo(startx + lineOffset, starty + lineOffset); + outlinePath.lineTo(startx + lineOffset, starty + BOX_SIZE - lineOffset); + outlinePath.lineTo(startx + BOX_SIZE - lineOffset, starty + BOX_SIZE - lineOffset); + outlinePath.lineTo(startx + BOX_SIZE - lineOffset, starty + lineOffset); + outlinePath.closePath(); + g.draw(outlinePath); + } + } + if (series.getLegendRenderType() == LegendRenderType.Line || + series.getLegendRenderType() == LegendRenderType.Scatter) { + double x = startx + chart.getStyler().getLegendSeriesLineLength() + + chart.getStyler().getLegendPadding(); + paintSeriesText(g, seriesTextBounds, axesChartStyler.getMarkerSize(), x, starty); + } else { + double x = startx + BOX_SIZE + chart.getStyler().getLegendPadding(); + paintSeriesText(g, seriesTextBounds, BOX_SIZE, x, starty); + } + if (chart.getStyler().getLegendLayout() == LegendLayout.Vertical) { + starty += legendEntryHeight + chart.getStyler().getLegendPadding(); + } else { + int markerWidth = BOX_SIZE; + if (series.getLegendRenderType() == LegendRenderType.Line) { + markerWidth = chart.getStyler().getLegendSeriesLineLength(); + } + float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth); + startx += legendEntryWidth + chart.getStyler().getLegendPadding(); + } + } + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint); + } + + @Override + public double getSeriesLegendRenderGraphicHeight(S series) { + return (series.getLegendRenderType() == LegendRenderType.Box || + series.getLegendRenderType() == LegendRenderType.BoxNoOutline) ? + BOX_SIZE : axesChartStyler.getMarkerSize(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCChart.java b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCChart.java new file mode 100644 index 0000000..bc8b7e6 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCChart.java @@ -0,0 +1,328 @@ +package org.xbib.graphics.chart.ohlc; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.axis.Axis; +import org.xbib.graphics.chart.axis.AxisPair; +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.plot.AxesChartPlot; +import org.xbib.graphics.chart.plot.ContentPlot; +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyle; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyleCycler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.time.Instant; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class OHLCChart extends Chart { + + public OHLCChart(int width, int height) { + super(width, height, new OHLCStyler()); + axisPair = new AxisPair<>(this); + plot = new OHLCPlot<>(this); + legend = new OHLCLegend<>(this); + } + + public OHLCChart(int width, int height, Theme theme) { + this(width, height); + styler.setTheme(theme); + } + + public OHLCChart(OHLCChartBuilder chartBuilder) { + this(chartBuilder.getWidth(), chartBuilder.getHeight(), chartBuilder.getTheme()); + setTitle(chartBuilder.getTitle()); + setXAxisTitle(chartBuilder.getxAxisTitle()); + setYAxisTitle(chartBuilder.getyAxisTitle()); + } + + public OHLCSeries addSeries(String seriesName, + List xData, + List openData, + List highData, + List lowData, + List closeData) { + + DataType dataType = getDataType(xData); + if (dataType == DataType.Instant) { + return addSeries(seriesName, xData, openData, highData, lowData, closeData, DataType.Instant); + } + return addSeries(seriesName, xData, openData, highData, lowData, closeData, DataType.Number); + } + + public OHLCSeries addSeries(String seriesName, + double[] xData, + double[] openData, + double[] highData, + double[] lowData, + double[] closeData) { + return addSeries(seriesName, + listFromDoubleArray(xData), + listFromDoubleArray(openData), + listFromDoubleArray(highData), + listFromDoubleArray(lowData), + listFromDoubleArray(closeData), DataType.Number); + } + + private OHLCSeries addSeries(String seriesName, + List xData, + List openData, + List highData, + List lowData, + List closeData, + DataType dataType) { + if (seriesMap.containsKey(seriesName)) { + throw new IllegalArgumentException("Series name >" + seriesName + + "< has already been used. Use unique names for each series!!!"); + } + sanityCheck(seriesName, openData, highData, lowData, closeData); + final List xDataToUse; + if (xData != null) { + checkDataLengths(seriesName, "X-Axis", xData, closeData); + xDataToUse = xData; + } else { + xDataToUse = getGeneratedData(closeData.size()); + } + OHLCSeries series = new OHLCSeries(seriesName, xDataToUse, openData, highData, lowData, closeData, dataType); + seriesMap.put(seriesName, series); + return series; + } + + private DataType getDataType(List data) { + if (data == null) { + return DataType.Number; + } + DataType axisType; + Iterator itr = data.iterator(); + Object dataPoint = itr.next(); + if (dataPoint instanceof Number) { + axisType = DataType.Number; + } else if (dataPoint instanceof Instant) { + axisType = DataType.Instant; + } else { + throw new IllegalArgumentException("Series data must be either Number or Instant type"); + } + return axisType; + } + + private void checkData(String seriesName, String dataName, List data) { + if (data == null) { + throw new IllegalArgumentException(dataName + " data cannot be null >" + seriesName); + } + if (data.size() == 0) { + throw new IllegalArgumentException(dataName + " data cannot be empty >" + seriesName); + } + } + + private void checkDataLengths(String seriesName, String data1Name, + List data1, + List data2) { + String data2Name = "Close"; + if (data1.size() != data2.size()) { + throw new IllegalArgumentException( + data1Name + " and " + data2Name + " sizes are not the same >" + seriesName); + } + } + private void sanityCheck(String seriesName, + List openData, + List highData, + List lowData, + List closeData) { + checkData(seriesName, "Open", openData); + checkData(seriesName, "High", highData); + checkData(seriesName, "Low", lowData); + checkData(seriesName, "Close", closeData); + checkDataLengths(seriesName, "Open", openData, closeData); + checkDataLengths(seriesName, "High", highData, closeData); + checkDataLengths(seriesName, "Low", lowData, closeData); + } + + @Override + public void paint(Graphics2D g, int width, int height) { + setWidth(width); + setHeight(height); + // set the series render styles if they are not set. Legend and Plot need it. + for (OHLCSeries series : getSeriesMap().values()) { + OHLCSeriesRenderStyle renderStyle = + series.getOhlcSeriesRenderStyle(); // would be directly set + if (renderStyle == null) { // wasn't overridden, use default from Style Manager + series.setOhlcSeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle()); + } + } + setSeriesStyles(); + paintBackground(g); + axisPair.paint(g); + plot.paint(g); + chartTitle.paint(g); + legend.paint(g); + } + + /** set the series color, marker and line style based on theme */ + private void setSeriesStyles() { + SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler = + new SeriesColorMarkerLineStyleCycler(getStyler().getSeriesColors(), + getStyler().getSeriesMarkers(), + getStyler().getSeriesLines()); + for (OHLCSeries series : getSeriesMap().values()) { + SeriesColorMarkerLineStyle seriesColorMarkerLineStyle = + seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle(); + if (series.getLineStyle() == null) { + series.setLineStyle(seriesColorMarkerLineStyle.getStroke()); + } + if (series.getLineColor() == null) { + series.setLineColor(seriesColorMarkerLineStyle.getColor()); + } + if (series.getFillColor() == null) { + series.setFillColor(seriesColorMarkerLineStyle.getColor()); + } + if (series.getUpColor() == null) { + series.setUpColor(Color.GREEN); + } + if (series.getDownColor() == null) { + series.setDownColor(Color.RED); + } + } + } + + private static class OHLCPlot extends AxesChartPlot { + + private OHLCPlot(Chart chart) { + super(chart); + this.contentPlot = new ContentPlotOHLC<>(chart); + } + } + + private static class ContentPlotOHLC extends ContentPlot { + + private final ST ohlcStyler; + + private ContentPlotOHLC(Chart chart) { + super(chart); + ohlcStyler = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double xTickSpace = ohlcStyler.getPlotContentSize() * getBounds().getWidth(); + double xLeftMargin = ((int) getBounds().getWidth() - xTickSpace) / 2.0; + double yTickSpace = ohlcStyler.getPlotContentSize() * getBounds().getHeight(); + double yTopMargin = ((int) getBounds().getHeight() - yTickSpace) / 2.0; + double xMin = chart.getXAxis().getMin(); + double xMax = chart.getXAxis().getMax(); + Line2D.Double line = new Line2D.Double(); + Rectangle2D.Double rect = new Rectangle2D.Double(); + if (ohlcStyler.isXAxisLogarithmic()) { + xMin = Math.log10(xMin); + xMax = Math.log10(xMax); + } + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (!series.isEnabled()) { + continue; + } + Axis yAxis = chart.getYAxis(series.getYAxisGroup()); + double yMin = yAxis.getMin(); + double yMax = yAxis.getMax(); + if (ohlcStyler.isYAxisLogarithmic()) { + yMin = Math.log10(yMin); + yMax = Math.log10(yMax); + } + List xData = series.getXData(); + List openData = series.getOpenData(); + List highData = series.getHighData(); + List lowData = series.getLowData(); + List closeData = series.getCloseData(); + double candleHalfWidth = + Math.max(3, xTickSpace / xData.size() / 2 - ohlcStyler.getAxisTickPadding()); + for (int i = 0; i < xData.size(); i++) { + Double x = (Double) xData.get(i); + if (ohlcStyler.isXAxisLogarithmic()) { + x = Math.log10(x); + } + if (Double.isNaN((Double) closeData.get(i))) { + continue; + } + Double openOrig = (Double) openData.get(i); + Double highOrig = (Double) highData.get(i); + Double lowOrig = (Double) lowData.get(i); + Double closeOrig = (Double) closeData.get(i); + double openY; + double highY; + double lowY; + double closeY; + if (ohlcStyler.isYAxisLogarithmic()) { + openY = Math.log10(openOrig); + highY = Math.log10(highOrig); + lowY = Math.log10(lowOrig); + closeY = Math.log10(closeOrig); + } else { + openY = openOrig; + highY = highOrig; + lowY = lowOrig; + closeY = closeOrig; + } + double xTransform = xLeftMargin + ((x - xMin) / (xMax - xMin) * xTickSpace); + double openTransform = + getBounds().getHeight() - (yTopMargin + (openY - yMin) / (yMax - yMin) * yTickSpace); + double highTransform = + getBounds().getHeight() - (yTopMargin + (highY - yMin) / (yMax - yMin) * yTickSpace); + double lowTransform = + getBounds().getHeight() - (yTopMargin + (lowY - yMin) / (yMax - yMin) * yTickSpace); + double closeTransform = + getBounds().getHeight() - (yTopMargin + (closeY - yMin) / (yMax - yMin) * yTickSpace); + if (Math.abs(xMax - xMin) / 5 == 0.0) { + xTransform = getBounds().getWidth() / 2.0; + } + if (Math.abs(yMax - yMin) / 5 == 0.0) { + openTransform = getBounds().getHeight() / 2.0; + highTransform = getBounds().getHeight() / 2.0; + lowTransform = getBounds().getHeight() / 2.0; + closeTransform = getBounds().getHeight() / 2.0; + } + double xOffset = getBounds().getX() + xTransform; + double openOffset = getBounds().getY() + openTransform; + double highOffset = getBounds().getY() + highTransform; + double lowOffset = getBounds().getY() + lowTransform; + double closeOffset = getBounds().getY() + closeTransform; + if (series.getLineStyle() != Theme.Series.NONE_STROKE) { + if (xOffset != -Double.MAX_VALUE + && openOffset != -Double.MAX_VALUE + && highOffset != -Double.MAX_VALUE + && lowOffset != -Double.MAX_VALUE + && closeOffset != -Double.MAX_VALUE) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + line.setLine(xOffset, highOffset, xOffset, lowOffset); + g.draw(line); + final double xStart = xOffset - candleHalfWidth; + final double xEnd = xOffset + candleHalfWidth; + if (series.getOhlcSeriesRenderStyle() == OHLCSeriesRenderStyle.Candle) { + if (closeOrig > openOrig) { + g.setPaint(series.getUpColor()); + } else { + g.setPaint(series.getDownColor()); + } + rect.setRect(xStart, + Math.min(openOffset, closeOffset), + xEnd - xStart, + Math.abs(closeOffset - openOffset)); + g.fill(rect); + } else { + line.setLine(xStart, openOffset, xOffset, openOffset); + g.draw(line); + line.setLine(xOffset, closeOffset, xEnd, closeOffset); + g.draw(line); + } + } + } + } + g.setColor(series.getFillColor()); + } + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCChartBuilder.java b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCChartBuilder.java new file mode 100644 index 0000000..8fda9a9 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCChartBuilder.java @@ -0,0 +1,35 @@ +package org.xbib.graphics.chart.ohlc; + +import org.xbib.graphics.chart.ChartBuilder; + +public class OHLCChartBuilder extends ChartBuilder { + + private String xAxisTitle = ""; + private String yAxisTitle = ""; + + public OHLCChartBuilder() { + } + + public OHLCChartBuilder xAxisTitle(String xAxisTitle) { + this.xAxisTitle = xAxisTitle; + return this; + } + + public String getxAxisTitle() { + return xAxisTitle; + } + + public OHLCChartBuilder yAxisTitle(String yAxisTitle) { + this.yAxisTitle = yAxisTitle; + return this; + } + + public String getyAxisTitle() { + return yAxisTitle; + } + + @Override + public OHLCChart build() { + return new OHLCChart(this); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCLegend.java b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCLegend.java new file mode 100644 index 0000000..7ce5940 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCLegend.java @@ -0,0 +1,72 @@ +package org.xbib.graphics.chart.ohlc; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.legend.AbstractLegend; +import org.xbib.graphics.chart.theme.Theme; +import org.xbib.graphics.chart.legend.LegendLayout; +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +public class OHLCLegend extends AbstractLegend { + + private final ST axesChartStyler; + + public OHLCLegend(Chart chart) { + super(chart); + axesChartStyler = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double startx = xOffset + chart.getStyler().getLegendPadding(); + double starty = yOffset + chart.getStyler().getLegendPadding(); + Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (series.isNotShownInLegend()) { + continue; + } + if (!series.isEnabled()) { + continue; + } + Map seriesTextBounds = getSeriesTextBounds(series); + float legendEntryHeight = getLegendEntryHeight(seriesTextBounds, axesChartStyler.getMarkerSize()); + if (series.getLegendRenderType() == LegendRenderType.Line + && series.getLineStyle() != Theme.Series.NONE_STROKE) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + Shape line = new Line2D.Double(startx, + starty + legendEntryHeight / 2.0, + startx + chart.getStyler().getLegendSeriesLineLength(), + starty + legendEntryHeight / 2.0); + g.draw(line); + } + double x = startx + chart.getStyler().getLegendSeriesLineLength() + + chart.getStyler().getLegendPadding(); + paintSeriesText(g, seriesTextBounds, axesChartStyler.getMarkerSize(), x, starty); + if (chart.getStyler().getLegendLayout() == LegendLayout.Vertical) { + starty += legendEntryHeight + chart.getStyler().getLegendPadding(); + } else { + int markerWidth = chart.getStyler().getLegendSeriesLineLength(); + float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth); + startx += legendEntryWidth + chart.getStyler().getLegendPadding(); + } + } + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint); + } + + @Override + public double getSeriesLegendRenderGraphicHeight(S series) { + return (series.getLegendRenderType() == LegendRenderType.Box + || series.getLegendRenderType() == LegendRenderType.BoxNoOutline) + ? BOX_SIZE + : axesChartStyler.getMarkerSize(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCSeries.java b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCSeries.java new file mode 100644 index 0000000..46c863f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCSeries.java @@ -0,0 +1,130 @@ +package org.xbib.graphics.chart.ohlc; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.series.AxesChartSeries; +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.awt.Color; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; + +public class OHLCSeries extends AxesChartSeries { + + private final List xData; + private final List openData; + private final List highData; + private final List lowData; + private final List closeData; + private OHLCSeriesRenderStyle ohlcSeriesRenderStyle; + private Color upColor; + private Color downColor; + + public OHLCSeries(String name, + List xData, + List openData, + List highData, + List lowData, + List closeData, + DataType xDataType) { + super(name, xDataType); + this.xData = xData; + this.openData = openData; + this.highData = highData; + this.lowData = lowData; + this.closeData = closeData; + calculateMinMax(); + } + + public OHLCSeriesRenderStyle getOhlcSeriesRenderStyle() { + return ohlcSeriesRenderStyle; + } + + public void setOhlcSeriesRenderStyle(OHLCSeriesRenderStyle ohlcSeriesRenderStyle) { + this.ohlcSeriesRenderStyle = ohlcSeriesRenderStyle; + } + + public Color getUpColor() { + return upColor; + } + + public void setUpColor(Color color) { + this.upColor = color; + } + + public Color getDownColor() { + return downColor; + } + + public void setDownColor(Color color) { + this.downColor = color; + } + + @Override + public LegendRenderType getLegendRenderType() { + return ohlcSeriesRenderStyle.getLegendRenderType(); + } + + private List findMinMax(List lows, List highs) { + double min = Double.MAX_VALUE; + double max = -Double.MAX_VALUE; + for (int i = 0; i < highs.size(); i++) { + Object h = highs.get(i); + Object l = lows.get(i); + if (h instanceof Double) { + double d = (Double) h; + if (!Double.isNaN(d) && d > max) { + max = d; + } + } else if (h instanceof Instant) { + Instant t = (Instant) h; + if (t.toEpochMilli() > max) { + max = (double) t.toEpochMilli(); + } + } + if (l instanceof Double) { + double d = (Double) l; + if (!Double.isNaN(d) && d < min) { + min = d; + } + } else if (l instanceof Instant) { + Instant t = (Instant) l; + if (t.toEpochMilli() < min) { + min = (double) t.toEpochMilli(); + } + } + } + return Arrays.asList(min, max); + } + + @Override + protected void calculateMinMax() { + List xMinMax = findMinMax(xData, xData); + setXMin(xMinMax.get(0)); + setXMax(xMinMax.get(1)); + List yMinMax = findMinMax(lowData, highData); + setYMin(yMinMax.get(0)); + setYMax(yMinMax.get(1)); + } + + public List getXData() { + return xData; + } + + public List getOpenData() { + return openData; + } + + public List getHighData() { + return highData; + } + + public List getLowData() { + return lowData; + } + + public List getCloseData() { + return closeData; + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCSeriesRenderStyle.java b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCSeriesRenderStyle.java new file mode 100644 index 0000000..5bf5149 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCSeriesRenderStyle.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.chart.ohlc; + +import org.xbib.graphics.chart.legend.LegendRenderable; +import org.xbib.graphics.chart.legend.LegendRenderType; + +public enum OHLCSeriesRenderStyle implements LegendRenderable { + Candle(LegendRenderType.Line), + + HiLo(LegendRenderType.Line); + + private final LegendRenderType legendRenderType; + + OHLCSeriesRenderStyle(LegendRenderType legendRenderType) { + this.legendRenderType = legendRenderType; + } + + @Override + public LegendRenderType getLegendRenderType() { + return legendRenderType; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCStyler.java b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCStyler.java new file mode 100644 index 0000000..d9f77a9 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/ohlc/OHLCStyler.java @@ -0,0 +1,33 @@ +package org.xbib.graphics.chart.ohlc; + +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.theme.Theme; + +public class OHLCStyler extends AxesChartStyler { + + private OHLCSeriesRenderStyle ohlcSeriesRenderStyle; + + public OHLCStyler() { + this.setAllStyles(); + super.setAllStyles(); + } + + @Override + protected void setAllStyles() { + ohlcSeriesRenderStyle = OHLCSeriesRenderStyle.Candle; + } + + public OHLCSeriesRenderStyle getDefaultSeriesRenderStyle() { + return ohlcSeriesRenderStyle; + } + + public OHLCStyler setDefaultSeriesRenderStyle(OHLCSeriesRenderStyle ohlcSeriesRenderStyle) { + this.ohlcSeriesRenderStyle = ohlcSeriesRenderStyle; + return this; + } + + public void setTheme(Theme theme) { + this.theme = theme; + super.setAllStyles(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/pie/PieChart.java b/chart/src/main/java/org/xbib/graphics/chart/pie/PieChart.java new file mode 100644 index 0000000..94b9118 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/pie/PieChart.java @@ -0,0 +1,354 @@ +package org.xbib.graphics.chart.pie; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.plot.CircularPlot; +import org.xbib.graphics.chart.plot.ContentPlot; +import org.xbib.graphics.chart.plot.SurfacePlot; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyle; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyleCycler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.text.DecimalFormat; +import java.util.Map; + +public class PieChart extends Chart { + + public PieChart(int width, int height) { + super(width, height, new PieStyler()); + plot = new PiePlot<>(this); + legend = new PieLegend<>(this); + } + + public PieChart(int width, int height, Theme theme) { + this(width, height); + styler.setTheme(theme); + } + + public PieChart(PieChartBuilder chartBuilder) { + this(chartBuilder.getWidth(), chartBuilder.getHeight(), chartBuilder.getTheme()); + setTitle(chartBuilder.getTitle()); + } + + public PieSeries addSeries(String seriesName, Number value) { + PieSeries series = new PieSeries(seriesName, value); + if (seriesMap.containsKey(seriesName)) { + throw new IllegalArgumentException("Series name >" + seriesName + "< has already been used. Use unique names for each series"); + } + seriesMap.put(seriesName, series); + return series; + } + + @Override + public void paint(Graphics2D g, int width, int height) { + setWidth(width); + setHeight(height); + for (PieSeries pieSeries : getSeriesMap().values()) { + PieSeriesRenderStyle seriesType = pieSeries.getPieSeriesRenderStyle(); + if (seriesType == null) { + pieSeries.setPieSeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle()); + } + } + setSeriesStyles(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(styler.getChartBackgroundColor()); + Shape rect = new Rectangle2D.Double(0, 0, getWidth(), getHeight()); + g.fill(rect); + plot.paint(g); + chartTitle.paint(g); + legend.paint(g); + } + + public void setSeriesStyles() { + SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler = + new SeriesColorMarkerLineStyleCycler(getStyler().getSeriesColors(), + getStyler().getSeriesMarkers(), + getStyler().getSeriesLines()); + for (Series series : getSeriesMap().values()) { + SeriesColorMarkerLineStyle seriesColorMarkerLineStyle = + seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle(); + if (series.getFillColor() == null) { // wasn't set manually + series.setFillColor(seriesColorMarkerLineStyle.getColor()); + } + } + } + + private static class PiePlot extends CircularPlot { + + private ContentPlot contentPlot; + + private SurfacePlot surfacePlot; + + private PiePlot(Chart chart) { + super(chart); + } + + protected void initContentAndSurface(Chart chart) { + this.contentPlot = new ContentPlotPie<>(chart); + this.surfacePlot = new SurfacePlotPie<>(chart); + } + + @Override + public void paint(Graphics2D g) { + super.paint(g); + surfacePlot.paint(g); + if (chart.getSeriesMap().isEmpty()) { + return; + } + contentPlot.paint(g); + } + } + + private static class ContentPlotPie extends ContentPlot { + + private final PieStyler pieStyler; + + private final DecimalFormat df = new DecimalFormat("#.0"); + + private ContentPlotPie(Chart chart) { + super(chart); + pieStyler = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double pieFillPercentage = pieStyler.getPlotContentSize(); + double halfBorderPercentage = (1 - pieFillPercentage) / 2.0; + double width = pieStyler.isCircular() ? + Math.min(getBounds().getWidth(), getBounds().getHeight()) : getBounds().getWidth(); + double height = pieStyler.isCircular() ? + Math.min(getBounds().getWidth(), getBounds().getHeight()) : getBounds().getHeight(); + Rectangle2D pieBounds = new Rectangle2D.Double( + getBounds().getX() + + getBounds().getWidth() / 2 + - width / 2 + + halfBorderPercentage * width, + getBounds().getY() + + getBounds().getHeight() / 2 + - height / 2 + + halfBorderPercentage * height, + width * pieFillPercentage, + height * pieFillPercentage); + double total = 0.0; + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (!series.isEnabled()) { + continue; + } + total += series.getValue().doubleValue(); + } + if (pieStyler.isSumVisible()) { + DecimalFormat totalDf = + (pieStyler.getDecimalPattern() == null) + ? df + : new DecimalFormat(pieStyler.getDecimalPattern()); + + String annotation = totalDf.format(total); + + TextLayout textLayout = + new TextLayout( + annotation, pieStyler.getSumFont(), new FontRenderContext(null, true, false)); + Shape shape = textLayout.getOutline(null); + g.setColor(pieStyler.getChartFontColor()); + + // compute center + Rectangle2D annotationRectangle = textLayout.getBounds(); + double xCenter = + pieBounds.getX() + pieBounds.getWidth() / 2 - annotationRectangle.getWidth() / 2; + double yCenter = + pieBounds.getY() + pieBounds.getHeight() / 2 + annotationRectangle.getHeight() / 2; + + // set text + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + at.translate(xCenter, yCenter); + g.transform(at); + g.fill(shape); + g.setTransform(orig); + } + double startAngle = pieStyler.getStartAngleInDegrees() + 90; + map = chart.getSeriesMap(); + for (S series : map.values()) { + if (!series.isEnabled()) { + continue; + } + Number y = series.getValue(); + // draw slice/donut + double arcAngle = (y.doubleValue() * 360 / total); + g.setColor(series.getFillColor()); + // slice + if (PieSeriesRenderStyle.Pie == series.getPieSeriesRenderStyle()) { + Arc2D.Double pieShape = new Arc2D.Double( + pieBounds.getX(), + pieBounds.getY(), + pieBounds.getWidth(), + pieBounds.getHeight(), + startAngle, + arcAngle, + Arc2D.PIE); + g.fill(pieShape); + g.setColor(pieStyler.getPlotBackgroundColor()); + g.draw(pieShape); + } + else { + Shape donutSlice = + getDonutSliceShape(pieBounds, pieStyler.getDonutThickness(), startAngle, arcAngle); + g.fill(donutSlice); + g.setColor(pieStyler.getPlotBackgroundColor()); + g.draw(donutSlice); + } + if (pieStyler.hasAnnotations()) { + String annotation = ""; + if (pieStyler.getAnnotationType() == PieStyler.AnnotationType.Value) { + if (pieStyler.getDecimalPattern() != null) { + DecimalFormat df = new DecimalFormat(pieStyler.getDecimalPattern()); + annotation = df.format(y); + } else { + annotation = y.toString(); + } + } else if (pieStyler.getAnnotationType() == PieStyler.AnnotationType.Label) { + annotation = series.getName(); + } else if (pieStyler.getAnnotationType() == PieStyler.AnnotationType.LabelAndPercentage) { + double percentage = y.doubleValue() / total * 100; + annotation = series.getName() + " (" + df.format(percentage) + "%)"; + } else if (pieStyler.getAnnotationType() == PieStyler.AnnotationType.Percentage) { + double percentage = y.doubleValue() / total * 100; + annotation = df.format(percentage) + "%"; + } + TextLayout textLayout = new TextLayout(annotation, + pieStyler.getAnnotationsFont(), + new FontRenderContext(null, true, false)); + Rectangle2D annotationRectangle = textLayout.getBounds(); + double xCenter = + pieBounds.getX() + pieBounds.getWidth() / 2 - annotationRectangle.getWidth() / 2; + double yCenter = + pieBounds.getY() + pieBounds.getHeight() / 2 + annotationRectangle.getHeight() / 2; + double angle = (arcAngle + startAngle) - arcAngle / 2; + double xOffset = xCenter + Math.cos(Math.toRadians(angle)) + * (pieBounds.getWidth() / 2 * pieStyler.getAnnotationDistance()); + double yOffset = yCenter - Math.sin(Math.toRadians(angle)) + * (pieBounds.getHeight() / 2 * pieStyler.getAnnotationDistance()); + + // get annotation width + Shape shape = textLayout.getOutline(null); + Rectangle2D annotationBounds = shape.getBounds2D(); + double annotationWidth = annotationBounds.getWidth(); + double annotationHeight = annotationBounds.getHeight(); + + // get slice area + double xOffset1 = xCenter + + Math.cos(Math.toRadians(startAngle)) + * (pieBounds.getWidth() / 2 * pieStyler.getAnnotationDistance()); + double yOffset1 = yCenter + - Math.sin(Math.toRadians(startAngle)) + * (pieBounds.getHeight() / 2 * pieStyler.getAnnotationDistance()); + double xOffset2 = xCenter + + Math.cos(Math.toRadians((arcAngle + startAngle))) + * (pieBounds.getWidth() / 2 * pieStyler.getAnnotationDistance()); + double yOffset2 = yCenter + - Math.sin(Math.toRadians((arcAngle + startAngle))) + * (pieBounds.getHeight() / 2 * pieStyler.getAnnotationDistance()); + double xDiff = Math.abs(xOffset1 - xOffset2); + double yDiff = Math.abs(yOffset1 - yOffset2); + boolean annotationWillFit = false; + if (xDiff >= yDiff) { // assume more vertically orientated slice + if (annotationWidth < xDiff) { + annotationWillFit = true; + } + } else if (xDiff <= yDiff) { // assume more horizontally orientated slice + if (annotationHeight < yDiff) { + annotationWillFit = true; + } + } + if (pieStyler.isDrawAllAnnotations() || annotationWillFit) { + g.setColor(pieStyler.getChartFontColor()); + g.setFont(pieStyler.getAnnotationsFont()); + AffineTransform orig = g.getTransform(); + AffineTransform at = new AffineTransform(); + if (pieStyler.getAnnotationDistance() <= 1.0) { + at.translate(xOffset, yOffset); + } + else { + xCenter = pieBounds.getX() + pieBounds.getWidth() / 2; + yCenter = pieBounds.getY() + pieBounds.getHeight() / 2; + double endPoint = (3.0 - pieStyler.getAnnotationDistance()); + double xOffsetStart = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / 2.01); + double xOffsetEnd = xCenter + Math.cos(Math.toRadians(angle)) * (pieBounds.getWidth() / endPoint); + double yOffsetStart = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / 2.01); + double yOffsetEnd = yCenter - Math.sin(Math.toRadians(angle)) * (pieBounds.getHeight() / endPoint); + g.setStroke(Theme.Strokes.PIE); + Shape line = new Line2D.Double(xOffsetStart, yOffsetStart, xOffsetEnd, yOffsetEnd); + g.draw(line); + at.translate(xOffset - Math.sin(Math.toRadians(angle - 90)) * annotationWidth / 2 + 3, yOffset); + } + g.transform(at); + g.fill(shape); + g.setTransform(orig); + } + } + startAngle += arcAngle; + } + } + + Shape getDonutSliceShape(Rectangle2D pieBounds, double thickness, double start, double extent) { + thickness = thickness / 2; + GeneralPath generalPath = new GeneralPath(); + GeneralPath dummy = new GeneralPath(); + double x = pieBounds.getX(); + double y = pieBounds.getY(); + double width = pieBounds.getWidth(); + double height = pieBounds.getHeight(); + Shape outer = new Arc2D.Double(x, y, width, height, start, extent, Arc2D.OPEN); + double wt = width * thickness; + double ht = height * thickness; + Shape inner = new Arc2D.Double(x + wt, y + ht, width - 2 * wt, height - 2 * ht, start + extent, -extent, Arc2D.OPEN); + generalPath.append(outer, false); + dummy.append(new Arc2D.Double(x + wt, y + ht, width - 2 * wt, height - 2 * ht, start, extent, Arc2D.OPEN), + false); + Point2D point = dummy.getCurrentPoint(); + if (point != null) { + generalPath.lineTo(point.getX(), point.getY()); + } + generalPath.append(inner, false); + dummy.append(new Arc2D.Double(x, y, width, height, start + extent, -extent, Arc2D.OPEN), false); + point = dummy.getCurrentPoint(); + generalPath.lineTo(point.getX(), point.getY()); + return generalPath; + } + } + + private static class SurfacePlotPie extends SurfacePlot { + + private final ST pieStyler; + + private SurfacePlotPie(Chart chart) { + super(chart); + this.pieStyler = chart.getStyler(); + } + + @Override + public void paint(Graphics2D g) { + Rectangle2D bounds = getBounds(); + if (bounds != null) { + Shape rect = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + g.setColor(pieStyler.getPlotBackgroundColor()); + g.fill(rect); + if (pieStyler.isPlotBorderVisible()) { + g.setColor(pieStyler.getPlotBorderColor()); + g.draw(rect); + } + } + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/pie/PieChartBuilder.java b/chart/src/main/java/org/xbib/graphics/chart/pie/PieChartBuilder.java new file mode 100644 index 0000000..ac793fe --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/pie/PieChartBuilder.java @@ -0,0 +1,15 @@ +package org.xbib.graphics.chart.pie; + +import org.xbib.graphics.chart.ChartBuilder; + +public class PieChartBuilder extends ChartBuilder { + + public PieChartBuilder() { + } + + @Override + public PieChart build() { + + return new PieChart(this); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/pie/PieLegend.java b/chart/src/main/java/org/xbib/graphics/chart/pie/PieLegend.java new file mode 100644 index 0000000..2d9b577 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/pie/PieLegend.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.chart.pie; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.legend.AbstractLegend; +import org.xbib.graphics.chart.legend.LegendLayout; +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +public class PieLegend extends AbstractLegend { + + public PieLegend(Chart chart) { + super(chart); + } + + @Override + public void doPaint(Graphics2D g) { + double startx = xOffset + chart.getStyler().getLegendPadding(); + double starty = yOffset + chart.getStyler().getLegendPadding(); + Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (series.isNotShownInLegend()) { + continue; + } + if (!series.isEnabled()) { + continue; + } + Map seriesTextBounds = getSeriesTextBounds(series); + float legendEntryHeight = getLegendEntryHeight(seriesTextBounds, BOX_SIZE); + Shape rectSmall = new Rectangle2D.Double(startx, starty, BOX_SIZE, BOX_SIZE); + g.setColor(series.getFillColor()); + g.fill(rectSmall); + final double x = startx + BOX_SIZE + chart.getStyler().getLegendPadding(); + paintSeriesText(g, seriesTextBounds, BOX_SIZE, x, starty); + if (chart.getStyler().getLegendLayout() == LegendLayout.Vertical) { + starty += legendEntryHeight + chart.getStyler().getLegendPadding(); + } else { + int markerWidth = BOX_SIZE; + if (series.getLegendRenderType() == LegendRenderType.Line) { + markerWidth = chart.getStyler().getLegendSeriesLineLength(); + } + float legendEntryWidth = getLegendEntryWidth(seriesTextBounds, markerWidth); + startx += legendEntryWidth + chart.getStyler().getLegendPadding(); + } + } + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint); + } + + @Override + public double getSeriesLegendRenderGraphicHeight(S series) { + return BOX_SIZE; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/pie/PieSeries.java b/chart/src/main/java/org/xbib/graphics/chart/pie/PieSeries.java new file mode 100644 index 0000000..ddebf22 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/pie/PieSeries.java @@ -0,0 +1,41 @@ +package org.xbib.graphics.chart.pie; + +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.legend.LegendRenderType; + +/** + * A Series containing Pie data to be plotted on a Chart. + */ +public class PieSeries extends Series { + + private PieSeriesRenderStyle pieSeriesRenderStyle = null; + private Number value; + + + public PieSeries(String name, Number value) { + super(name); + this.value = value; + } + + public void setPieSeriesRenderStyle(PieSeriesRenderStyle pieSeriesRenderStyle) { + this.pieSeriesRenderStyle = pieSeriesRenderStyle; + } + + public PieSeriesRenderStyle getPieSeriesRenderStyle() { + return pieSeriesRenderStyle; + } + + @Override + public LegendRenderType getLegendRenderType() { + return null; + } + + public Number getValue() { + return value; + } + + public void setValue(Number value) { + this.value = value; + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/pie/PieSeriesRenderStyle.java b/chart/src/main/java/org/xbib/graphics/chart/pie/PieSeriesRenderStyle.java new file mode 100644 index 0000000..7276905 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/pie/PieSeriesRenderStyle.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.chart.pie; + +public enum PieSeriesRenderStyle { + Pie, Donut; +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/pie/PieStyler.java b/chart/src/main/java/org/xbib/graphics/chart/pie/PieStyler.java new file mode 100644 index 0000000..9032a41 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/pie/PieStyler.java @@ -0,0 +1,198 @@ +package org.xbib.graphics.chart.pie; + +import org.xbib.graphics.chart.style.Styler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Font; + +public class PieStyler extends Styler { + + private PieSeriesRenderStyle pieSeriesRenderStyle; + private boolean isCircular; + private double startAngleInDegrees; + private Font annotationFont; + private double annotationDistance; + private AnnotationType annotationType; + private boolean drawAllAnnotations; + private double donutThickness; + private boolean isSumVisible; + private Font sumFont; + + public PieStyler() { + this.setAllStyles(); + super.setAllStyles(); + } + + @Override + protected void setAllStyles() { + this.pieSeriesRenderStyle = PieSeriesRenderStyle.Pie; + this.isCircular = theme.isCircular(); + this.annotationFont = theme.getPieFont(); + this.annotationDistance = theme.getAnnotationDistance(); + this.annotationType = theme.getAnnotationType(); + this.drawAllAnnotations = theme.isDrawAllAnnotations(); + this.donutThickness = theme.getDonutThickness(); + this.hasAnnotations = true; + this.isSumVisible = theme.isSumVisible(); + this.sumFont = theme.getSumFont(); + } + + public PieSeriesRenderStyle getDefaultSeriesRenderStyle() { + return pieSeriesRenderStyle; + } + + /** + * Sets the default series render style for the chart (line, scatter, area, etc.) You can override the series + * render + * style individually on each Series object. + * + * @param pieSeriesRenderStyle render style + */ + public void setDefaultSeriesRenderStyle(PieSeriesRenderStyle pieSeriesRenderStyle) { + this.pieSeriesRenderStyle = pieSeriesRenderStyle; + } + + public boolean isCircular() { + return isCircular; + } + + /** + * Sets whether or not the pie chart is forced to be circular. Otherwise it's shape is oval, matching the + * containing + * plot. + * + * @param isCircular circular + */ + public void setCircular(boolean isCircular) { + this.isCircular = isCircular; + } + + public double getStartAngleInDegrees() { + return startAngleInDegrees; + } + + /** + * Sets the start angle in degrees. Zero degrees is straight up. + * + * @param startAngleInDegrees start angle in degrees + */ + public void setStartAngleInDegrees(double startAngleInDegrees) { + this.startAngleInDegrees = startAngleInDegrees; + } + + /** + * Sets the font used on the Pie Chart's annotations + * + * @param pieFont pie font + */ + public void setAnnotationFont(Font pieFont) { + this.annotationFont = pieFont; + } + + public Font getAnnotationFont() { + return annotationFont; + } + + /** + * Sets the distance of the pie chart's annotation where 0 is the center, 1 is at the edge and greater than 1 is + * outside of the pie chart. + * + * @param annotationDistance annotation distance + */ + public void setAnnotationDistance(double annotationDistance) { + this.annotationDistance = annotationDistance; + } + + public double getAnnotationDistance() { + return annotationDistance; + } + + /** + * Sets the Pie chart's annotation type + * + * @param annotationType annotation type + */ + public void setAnnotationType(AnnotationType annotationType) { + this.annotationType = annotationType; + } + + public AnnotationType getAnnotationType() { + return annotationType; + } + + public void setDrawAllAnnotations(boolean drawAllAnnotations) { + this.drawAllAnnotations = drawAllAnnotations; + } + + public boolean isDrawAllAnnotations() { + return drawAllAnnotations; + } + + /** + * Sets the thickness of the donut ring for donut style pie chart series. + * + * @param donutThickness - Valid range is between 0 and 1. + */ + public void setDonutThickness(double donutThickness) { + this.donutThickness = donutThickness; + } + + public double getDonutThickness() { + return donutThickness; + } + + /** + * Sets whether or not the sum is visible in the centre of the pie chart. + * + * @param isSumVisible sum visible + */ + public void setSumVisible(boolean isSumVisible) { + this.isSumVisible = isSumVisible; + } + + public boolean isSumVisible() { + return isSumVisible; + } + + /** + * Sets the font size for the sum. + * + * @param sumFontSize font size + */ + public void setSumFontSize(float sumFontSize) { + this.sumFont = this.sumFont.deriveFont(sumFontSize); + } + + public Font getSumFont() { + return sumFont; + } + + /** + * Sets the font for the sum. + * + * @param sumFont font + */ + public void setSumFont(Font sumFont) { + this.sumFont = sumFont; + } + + /** + * Set the theme the styler should use + * + * @param theme theme + */ + public void setTheme(Theme theme) { + this.theme = theme; + super.setAllStyles(); + } + + public Theme getTheme() { + return theme; + } + + + public enum AnnotationType { + Value, Percentage, Label, LabelAndPercentage + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/plot/AxesChartPlot.java b/chart/src/main/java/org/xbib/graphics/chart/plot/AxesChartPlot.java new file mode 100644 index 0000000..62d6490 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/plot/AxesChartPlot.java @@ -0,0 +1,43 @@ +package org.xbib.graphics.chart.plot; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +public class AxesChartPlot extends Plot { + + protected ContentPlot contentPlot; + + protected SurfacePlot surfacePlot; + + protected Rectangle2D bounds; + + public AxesChartPlot(Chart chart) { + super(chart); + this.surfacePlot = new SurfacePlotAxesChart<>(chart); + } + + @Override + public void paint(Graphics2D g) { + Rectangle2D yAxisBounds = chart.getAxisPair().getLeftYAxisBounds(); + Rectangle2D xAxisBounds = chart.getXAxis().getBounds(); + double xOffset = xAxisBounds.getX(); + double yOffset = yAxisBounds.getY(); + double width = xAxisBounds.getWidth(); + double height = yAxisBounds.getHeight(); + this.bounds = new Rectangle2D.Double(xOffset, yOffset, width, height); + surfacePlot.paint(g); + if (chart.getSeriesMap().isEmpty()) { + return; + } + contentPlot.paint(g); + } + + @Override + public Rectangle2D getBounds() { + return bounds; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/plot/CircularPlot.java b/chart/src/main/java/org/xbib/graphics/chart/plot/CircularPlot.java new file mode 100644 index 0000000..683109b --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/plot/CircularPlot.java @@ -0,0 +1,48 @@ +package org.xbib.graphics.chart.plot; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; +import org.xbib.graphics.chart.legend.LegendPosition; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +public abstract class CircularPlot extends Plot { + + protected Rectangle2D bounds; + + public CircularPlot(Chart chart) { + super(chart); + initContentAndSurface(chart); + } + + protected abstract void initContentAndSurface(Chart chart); + + @Override + public void paint(Graphics2D g) { + double xOffset = chart.getStyler().getChartPadding(); + double yOffset = chart.getChartTitle().getBounds().getHeight() + chart.getStyler().getChartPadding(); + double width = chart.getWidth() + - (chart.getStyler().getLegendPosition() == LegendPosition.OutsideE + ? chart.getLegend().getBounds().getWidth() + : 0) + - 2 * chart.getStyler().getChartPadding() + - (chart.getStyler().getLegendPosition() == LegendPosition.OutsideE + && chart.getStyler().isLegendVisible() + ? chart.getStyler().getChartPadding() + : 0); + double height = chart.getHeight() + - chart.getChartTitle().getBounds().getHeight() + - (chart.getStyler().getLegendPosition() == LegendPosition.OutsideS + ? chart.getLegend().getBounds().getHeight() + : 0) + - 2 * chart.getStyler().getChartPadding(); + this.bounds = new Rectangle2D.Double(xOffset, yOffset, width, height); + } + + @Override + public Rectangle2D getBounds() { + return bounds; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/plot/ContentPlot.java b/chart/src/main/java/org/xbib/graphics/chart/plot/ContentPlot.java new file mode 100644 index 0000000..ce3cee1 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/plot/ContentPlot.java @@ -0,0 +1,46 @@ +package org.xbib.graphics.chart.plot; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; + +public abstract class ContentPlot extends Plot implements ChartComponent { + + public ContentPlot(Chart chart) { + super(chart); + } + + @Override + public void paint(Graphics2D g) { + Rectangle2D bounds = getBounds(); + if (bounds != null) { + if (bounds.getWidth() < 30) { + return; + } + Shape saveClip = g.getClip(); + g.setClip(bounds.createIntersection(bounds)); + doPaint(g); + g.setClip(saveClip); + } + } + + /** + * Closes a path for area charts if one is available. + */ + protected void closePath(Graphics2D g, Path2D.Double path, double previousX, double yTopMargin) { + if (path != null) { + double yBottomOfArea = getBounds().getY() + getBounds().getHeight() - yTopMargin; + path.lineTo(previousX, yBottomOfArea); + path.closePath(); + g.fill(path); + } + } + + protected abstract void doPaint(Graphics2D g); +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/plot/Plot.java b/chart/src/main/java/org/xbib/graphics/chart/plot/Plot.java new file mode 100644 index 0000000..a1853f5 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/plot/Plot.java @@ -0,0 +1,22 @@ +package org.xbib.graphics.chart.plot; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; + +import java.awt.geom.Rectangle2D; + +public abstract class Plot implements ChartComponent { + + protected final Chart chart; + + public Plot(Chart chart) { + this.chart = chart; + } + + @Override + public Rectangle2D getBounds() { + return chart.getPlot().getBounds(); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/plot/SurfacePlot.java b/chart/src/main/java/org/xbib/graphics/chart/plot/SurfacePlot.java new file mode 100644 index 0000000..6628e51 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/plot/SurfacePlot.java @@ -0,0 +1,13 @@ +package org.xbib.graphics.chart.plot; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.ChartComponent; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.Styler; + +public abstract class SurfacePlot extends Plot implements ChartComponent { + + protected SurfacePlot(Chart chart) { + super(chart); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/plot/SurfacePlotAxesChart.java b/chart/src/main/java/org/xbib/graphics/chart/plot/SurfacePlotAxesChart.java new file mode 100644 index 0000000..34e36e6 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/plot/SurfacePlotAxesChart.java @@ -0,0 +1,104 @@ +package org.xbib.graphics.chart.plot; + +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.series.Series; +import org.xbib.graphics.chart.style.AxesChartStyler; + +import java.awt.BasicStroke; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.util.List; + +/** + * Draws the plot background, the plot border and the horizontal and vertical grid lines. + */ +public class SurfacePlotAxesChart extends SurfacePlot { + + private final ST axesChartStyler; + + protected SurfacePlotAxesChart(Chart chart) { + super(chart); + this.axesChartStyler = chart.getStyler(); + } + + @Override + public void paint(Graphics2D g) { + Rectangle2D bounds = getBounds(); + Shape rect = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + g.setColor(axesChartStyler.getPlotBackgroundColor()); + g.fill(rect); + if (axesChartStyler.isPlotGridHorizontalLinesVisible()) { + List yAxisTickLocations = chart.getYAxis().getAxisTickCalculator().getTickLocations(); + for (Double yAxisTickLocation : yAxisTickLocations) { + double yOffset = bounds.getY() + bounds.getHeight() - yAxisTickLocation; + if (yOffset > bounds.getY() && yOffset < bounds.getY() + bounds.getHeight()) { + g.setColor(axesChartStyler.getPlotGridLinesColor()); + g.setStroke(axesChartStyler.getPlotGridLinesStroke()); + Shape line = axesChartStyler.getPlotGridLinesStroke().createStrokedShape(new Line2D.Double( + bounds.getX(), yOffset, bounds.getX() + bounds.getWidth(), yOffset)); + g.draw(line); + } + } + } + if (axesChartStyler.isPlotTicksMarksVisible()) { + List yAxisTickLocations = + chart.getAxisPair().getLeftMainYAxis().getAxisTickCalculator().getTickLocations(); + for (Double yAxisTickLocation : yAxisTickLocations) { + double yOffset = bounds.getY() + bounds.getHeight() - yAxisTickLocation; + if (yOffset > bounds.getY() && yOffset < bounds.getY() + bounds.getHeight()) { + g.setColor(axesChartStyler.getAxisTickMarksColor()); + g.setStroke(axesChartStyler.getAxisTickMarksStroke()); + Shape line = new Line2D.Double(bounds.getX(), yOffset, + bounds.getX() + axesChartStyler.getAxisTickMarkLength(), + yOffset); + g.draw(line); + } + } + yAxisTickLocations = chart.getAxisPair().getRightMainYAxis().getAxisTickCalculator().getTickLocations(); + for (Double yAxisTickLocation : yAxisTickLocations) { + double yOffset = bounds.getY() + bounds.getHeight() - yAxisTickLocation; + if (yOffset > bounds.getY() && yOffset < bounds.getY() + bounds.getHeight()) { + g.setColor(axesChartStyler.getAxisTickMarksColor()); + g.setStroke(axesChartStyler.getAxisTickMarksStroke()); + Shape line = new Line2D.Double(bounds.getX() + bounds.getWidth(), yOffset, + bounds.getX() + bounds.getWidth() - axesChartStyler.getAxisTickMarkLength(), + yOffset); + g.draw(line); + } + } + } + if (axesChartStyler.isPlotGridVerticalLinesVisible() || axesChartStyler.isPlotTicksMarksVisible()) { + List xAxisTickLocations = chart.getXAxis().getAxisTickCalculator().getTickLocations(); + for (Double xAxisTickLocation : xAxisTickLocations) { + double tickLocation = xAxisTickLocation; + double xOffset = bounds.getX() + tickLocation; + if (xOffset > bounds.getX() && xOffset < bounds.getX() + bounds.getWidth()) { + if (axesChartStyler.isPlotGridVerticalLinesVisible()) { + g.setColor(axesChartStyler.getPlotGridLinesColor()); + g.setStroke(axesChartStyler.getPlotGridLinesStroke()); + Shape line = axesChartStyler.getPlotGridLinesStroke().createStrokedShape(new Line2D.Double( + xOffset, bounds.getY(), xOffset, bounds.getY() + bounds.getHeight())); + g.draw(line); + } + if (axesChartStyler.isPlotTicksMarksVisible()) { + g.setColor(axesChartStyler.getAxisTickMarksColor()); + g.setStroke(axesChartStyler.getAxisTickMarksStroke()); + Shape line = new Line2D.Double(xOffset, bounds.getY(), xOffset, + bounds.getY() + axesChartStyler.getAxisTickMarkLength()); + g.draw(line); + line = new Line2D.Double(xOffset, bounds.getY() + bounds.getHeight(), xOffset, + bounds.getY() + bounds.getHeight() - axesChartStyler.getAxisTickMarkLength()); + g.draw(line); + } + } + } + } + if (axesChartStyler.isPlotBorderVisible()) { + g.setColor(axesChartStyler.getPlotBorderColor()); + g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 0.0f}, 0.0f)); + g.draw(rect); + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeries.java b/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeries.java new file mode 100644 index 0000000..5c67460 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeries.java @@ -0,0 +1,104 @@ +package org.xbib.graphics.chart.series; + +import org.xbib.graphics.chart.axis.DataType; + +import java.awt.BasicStroke; +import java.awt.Color; + +/** + * A Series containing X and Y data to be plotted on a Chart with X and Y Axes. + */ +public abstract class AxesChartSeries extends Series { + + private final DataType xAxisType; + private final DataType yAxisType; + private double xMin; + private double xMax; + private double yMin; + private double yMax; + private BasicStroke stroke; + private Color lineColor; + private float lineWidth = -1.0f; + + public AxesChartSeries(String name, DataType xAxisType) { + super(name); + this.xAxisType = xAxisType; + this.yAxisType = DataType.Number; + } + + protected abstract void calculateMinMax(); + + public void setLineColor(Color color) { + this.lineColor = color; + } + + public Color getLineColor() { + return lineColor; + } + + public DataType getxAxisDataType() { + return xAxisType; + } + + public DataType getyAxisDataType() { + return yAxisType; + } + + public void setXMin(double xMin) { + this.xMin = xMin; + } + + public double getXMin() { + return xMin; + } + + public void setXMax(double xMax) { + this.xMax = xMax; + } + + public double getXMax() { + return xMax; + } + + public void setYMin(double yMin) { + this.yMin = yMin; + } + + public double getYMin() { + return yMin; + } + + public void setYMax(double yMax) { + this.yMax = yMax; + } + + public double getYMax() { + return yMax; + } + + public void setLineStyle(BasicStroke basicStroke) { + stroke = basicStroke; + if (this.lineWidth > 0.0f) { + stroke = new BasicStroke(lineWidth, + this.stroke.getEndCap(), + this.stroke.getLineJoin(), + this.stroke.getMiterLimit(), + this.stroke.getDashArray(), + this.stroke.getDashPhase()); + } + } + + public BasicStroke getLineStyle() { + return stroke; + } + + public AxesChartSeries setLineWidth(float lineWidth) { + this.lineWidth = lineWidth; + return this; + } + + public float getLineWidth() { + return lineWidth; + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeriesCategory.java b/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeriesCategory.java new file mode 100644 index 0000000..5b055d4 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeriesCategory.java @@ -0,0 +1,102 @@ +package org.xbib.graphics.chart.series; + +import org.xbib.graphics.chart.axis.DataType; + +import java.time.Instant; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * A Series containing X and Y data to be plotted on a Chart with X and Y Axes. xData can be Number + * or Date or String, hence a List. + */ +public abstract class AxesChartSeriesCategory extends MarkerSeries { + + private final List xData; + private final List yData; + private final List extraValues; + + public AxesChartSeriesCategory(String name, + List xData, + List yData, + List extraValues, + DataType xDataType) { + super(name, xDataType); + this.xData = xData; + this.yData = yData; + this.extraValues = extraValues; + calculateMinMax(); + } + + @Override + protected void calculateMinMax() { + double[] xMinMax = findMinMax(xData, getxAxisDataType()); + setXMin(xMinMax[0]); + setXMax(xMinMax[1]); + double[] yMinMax; + if (extraValues == null) { + yMinMax = findMinMax(yData, getyAxisDataType()); + } else { + yMinMax = findMinMaxWithErrorBars(yData, extraValues); + } + setYMin(yMinMax[0]); + setYMax(yMinMax[1]); + } + + private double[] findMinMaxWithErrorBars(Collection data, Collection errorBars) { + double min = Double.MAX_VALUE; + double max = -Double.MAX_VALUE; + Iterator itr = data.iterator(); + Iterator ebItr = errorBars.iterator(); + while (itr.hasNext()) { + double bigDecimal = itr.next().doubleValue(); + double eb = ebItr.next().doubleValue(); + if (bigDecimal - eb < min) { + min = bigDecimal - eb; + } + if (bigDecimal + eb > max) { + max = bigDecimal + eb; + } + } + return new double[] {min, max}; + } + + private double[] findMinMax(Collection data, DataType dataType) { + double min = Double.MAX_VALUE; + double max = -Double.MAX_VALUE; + for (Object dataPoint : data) { + if (dataPoint == null) { + continue; + } + double value = 0.0; + if (dataType == DataType.Number) { + value = ((Number) dataPoint).doubleValue(); + } else if (dataType == DataType.Instant) { + Instant date = (Instant) dataPoint; + value = date.toEpochMilli(); + } else if (dataType == DataType.String) { + return new double[] {Double.NaN, Double.NaN}; + } + if (value < min) { + min = value; + } + if (value > max) { + max = value; + } + } + return new double[] {min, max}; + } + + public Collection getXData() { + return xData; + } + + public Collection getYData() { + return yData; + } + + public Collection getExtraValues() { + return extraValues; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeriesNumericalNoErrorBars.java b/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeriesNumericalNoErrorBars.java new file mode 100644 index 0000000..af1365c --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/series/AxesChartSeriesNumericalNoErrorBars.java @@ -0,0 +1,147 @@ +package org.xbib.graphics.chart.series; + +import org.xbib.graphics.chart.axis.DataType; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; + +/** + * A Series containing X and Y data to be plotted on a Chart with X and Y Axes. + */ +public abstract class AxesChartSeriesNumericalNoErrorBars extends MarkerSeries { + + protected List xData; // can be Number or Instant + protected List yData; + protected List extraValues; + + public AxesChartSeriesNumericalNoErrorBars(String name, + List xData, + List yData, + List extraValues, + DataType xDataType) { + super(name, xDataType); + this.xData = xData; + this.yData = yData; + this.extraValues = extraValues; + calculateMinMax(); + } + + @Override + protected void calculateMinMax() { + // xData + List xMinMax = findMinMax(xData); + setXMin(xMinMax.get(0)); + setXMax(xMinMax.get(1)); + // yData + List yMinMax; + if (extraValues == null) { + yMinMax = findMinMax(yData); + } else { + yMinMax = findMinMaxWithErrorBars(yData, extraValues); + } + setYMin(yMinMax.get(0)); + setYMax(yMinMax.get(1)); + } + + List findMinMax(List data) { + Double min = null; + Double max = null; + for (Object dataPoint : data) { + if (dataPoint instanceof Double) { + Double d = (Double) dataPoint; + if (min == null) { + min = d; + } + if (max == null) { + max = d; + } + if (!Double.isNaN(d)) { + if (d < min) { + min = d; + } + if (d > max) { + max = d; + } + } + } else if (dataPoint instanceof BigDecimal) { + BigDecimal bd = (BigDecimal) dataPoint; + if (min == null) { + min = bd.doubleValue(); + } + if (max == null) { + max = bd.doubleValue(); + } + if (bd.doubleValue() < min) { + min = bd.doubleValue(); + } + if (bd.doubleValue() > max) { + max = bd.doubleValue(); + } + } else if (dataPoint instanceof Integer) { + int i = (Integer) dataPoint; + if (min == null) { + min = (double) i; + } + if (max == null) { + max = (double) i; + } + if (i < min) { + min = (double) i; + } + if (i > max) { + max = (double) i; + } + } else if (dataPoint instanceof Instant) { + Instant instant = (Instant) dataPoint; + if (min == null) { + min = (double) instant.toEpochMilli(); + } + if (max == null) { + max = (double) instant.toEpochMilli(); + } + if (instant.toEpochMilli() < min) { + min = (double) instant.toEpochMilli(); + } + if (instant.toEpochMilli() > max) { + max = (double) instant.toEpochMilli(); + } + } + } + return Arrays.asList(min, max); + } + + private List findMinMaxWithErrorBars(List data, + List errorBars) { + double min = Double.MAX_VALUE; + double max = -Double.MAX_VALUE; + for (int i = 0; i < data.size(); i++) { + Number n = data.get(i); + Double d = n instanceof Double ? (Double) n : null; + Number nn = errorBars.get(i); + Double eb = nn instanceof Double ? (Double) nn : null; + if (d != null && eb != null) { + if (d - eb < min) { + min = d - eb; + } + if (d + eb > max) { + max = d + eb; + } + } + } + return Arrays.asList(min, max); + } + + public List getXData() { + return xData; + } + + public List getYData() { + return yData; + } + + public List getExtraValues() { + return extraValues; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/series/MarkerSeries.java b/chart/src/main/java/org/xbib/graphics/chart/series/MarkerSeries.java new file mode 100644 index 0000000..cd77894 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/series/MarkerSeries.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.chart.series; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Color; + +/** + * A Series containing X and Y data to be plotted on a Chart with X and Y Axes, + * contains series markers and error bars. + */ +public abstract class MarkerSeries extends AxesChartSeries { + + private Theme.Series.Marker marker; + + private Color markerColor; + + protected MarkerSeries(String name, DataType xDataType) { + super(name, xDataType); + } + + public void setMarker(Theme.Series.Marker marker) { + this.marker = marker; + } + + public Theme.Series.Marker getMarker() { + return marker; + } + + public void setMarkerColor(Color color) { + this.markerColor = color; + } + + public Color getMarkerColor() { + return markerColor; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/series/NoMarkersSeries.java b/chart/src/main/java/org/xbib/graphics/chart/series/NoMarkersSeries.java new file mode 100644 index 0000000..a8fc08f --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/series/NoMarkersSeries.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.chart.series; + +import org.xbib.graphics.chart.axis.DataType; + +import java.util.List; + +/** + * A Series containing X and Y data to be plotted on a Chart with X and Y Axes, values associated + * with each X-Y point, could be used for bubble sizes for example, but no error bars, as the min + * and max are calculated differently. No markers. + */ +public abstract class NoMarkersSeries extends AxesChartSeriesNumericalNoErrorBars { + + protected NoMarkersSeries(String name, List xData, + List yData, + List extraValues, DataType axisType) { + super(name, xData, yData, extraValues, axisType); + this.extraValues = extraValues; + calculateMinMax(); + } + + @Override + protected void calculateMinMax() { + List xMinMax = findMinMax(xData); + setXMin(xMinMax.get(0)); + setXMax(xMinMax.get(1)); + List yMinMax = findMinMax(yData); + setYMin(yMinMax.get(0)); + setYMax(yMinMax.get(1)); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/series/Series.java b/chart/src/main/java/org/xbib/graphics/chart/series/Series.java new file mode 100644 index 0000000..c5199be --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/series/Series.java @@ -0,0 +1,73 @@ +package org.xbib.graphics.chart.series; + +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.awt.Color; + +/** + * A Series containing data to be plotted on a Chart + */ +public abstract class Series { + + private final String name; + private String label; + private Color fillColor; + private boolean showInLegend = true; + private boolean isEnabled = true; + private int yAxisGroup = 0; + + public Series(String name) { + if (name == null || name.length() < 1) { + throw new IllegalArgumentException("Series name cannot be null or zero-length"); + } + this.name = name; + this.label = name; + } + + public abstract LegendRenderType getLegendRenderType(); + + public Color getFillColor() { + return fillColor; + } + + public void setFillColor(Color fillColor) { + this.fillColor = fillColor; + } + + public String getName() { + return name; + } + + public String getLabel() { + return label; + } + + public Series setLabel(String label) { + this.label = label; + return this; + } + + public boolean isNotShownInLegend() { + return !showInLegend; + } + + public void setShowInLegend(boolean showInLegend) { + this.showInLegend = showInLegend; + } + + public void setEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public boolean isEnabled() { + return isEnabled; + } + + public void setYAxisGroup(int yAxisGroup) { + this.yAxisGroup = yAxisGroup; + } + + public int getYAxisGroup() { + return yAxisGroup; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/style/AxesChartStyler.java b/chart/src/main/java/org/xbib/graphics/chart/style/AxesChartStyler.java new file mode 100644 index 0000000..69b9633 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/style/AxesChartStyler.java @@ -0,0 +1,677 @@ +package org.xbib.graphics.chart.style; + +import org.xbib.graphics.chart.axis.TextAlignment; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Stroke; +import java.time.ZoneId; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public abstract class AxesChartStyler extends Styler { + + private boolean xAxisTitleVisible; + private boolean yAxisTitleVisible; + private Font axisTitleFont; + private boolean xAxisTicksVisible; + private boolean yAxisTicksVisible; + private Font axisTickLabelsFont; + private int axisTickMarkLength; + private int axisTickPadding; + private Color axisTickMarksColor; + private Stroke axisTickMarksStroke; + private Color axisTickLabelsColor; + private boolean isAxisTicksLineVisible; + private boolean isAxisTicksMarksVisible; + private int plotMargin; + private int axisTitlePadding; + private int xAxisTickMarkSpacingHint; + private int yAxisTickMarkSpacingHint; + private boolean isXAxisLogarithmic; + private boolean isYAxisLogarithmic; + private Double xAxisMin; + private Double xAxisMax; + private final Map yAxisMinMap = new HashMap<>(); + private final Map yAxisMaxMap = new HashMap<>(); + private TextAlignment xAxisLabelAlignment = TextAlignment.Centre; + private TextAlignment xAxisLabelAlignmentVertical = TextAlignment.Centre; + private TextAlignment yAxisLabelAlignment = TextAlignment.Left; + private int xAxisLabelRotation = 0; + + // Chart Plot Area + private boolean isPlotGridHorizontalLinesVisible; + private boolean isPlotGridVerticalLinesVisible; + private boolean isPlotTicksMarksVisible; + private Color plotGridLinesColor; + private Stroke plotGridLinesStroke; + + private int markerSize; + + private Color errorBarsColor; + private boolean isErrorBarsColorSeriesColor; + + private Locale locale; + private ZoneId zoneId; + private String datePattern; + private String decimalPattern; + private String xAxisDecimalPattern; + private String yAxisDecimalPattern; + private boolean xAxisLogarithmicDecadeOnly; + private boolean yAxisLogarithmicDecadeOnly; + + @Override + protected void setAllStyles() { + super.setAllStyles(); + // axes + xAxisTitleVisible = theme.isXAxisTitleVisible(); + yAxisTitleVisible = theme.isYAxisTitleVisible(); + axisTitleFont = theme.getAxisTitleFont(); + xAxisTicksVisible = theme.isXAxisTicksVisible(); + yAxisTicksVisible = theme.isYAxisTicksVisible(); + axisTickLabelsFont = theme.getAxisTickLabelsFont(); + axisTickMarkLength = theme.getAxisTickMarkLength(); + axisTickPadding = theme.getAxisTickPadding(); + axisTickMarksColor = theme.getAxisTickMarksColor(); + axisTickMarksStroke = theme.getAxisTickMarksStroke(); + axisTickLabelsColor = theme.getAxisTickLabelsColor(); + isAxisTicksLineVisible = theme.isAxisTicksLineVisible(); + isAxisTicksMarksVisible = theme.isAxisTicksMarksVisible(); + plotMargin = theme.getPlotMargin(); + axisTitlePadding = theme.getAxisTitlePadding(); + xAxisTickMarkSpacingHint = theme.getXAxisTickMarkSpacingHint(); + yAxisTickMarkSpacingHint = theme.getYAxisTickMarkSpacingHint(); + isXAxisLogarithmic = false; + isYAxisLogarithmic = false; + xAxisMin = null; + xAxisMax = null; + this.yAxisMinMap.clear(); + this.yAxisMaxMap.clear(); + isPlotGridVerticalLinesVisible = theme.isPlotGridVerticalLinesVisible(); + isPlotGridHorizontalLinesVisible = theme.isPlotGridHorizontalLinesVisible(); + isPlotTicksMarksVisible = theme.isPlotTicksMarksVisible(); + plotGridLinesColor = theme.getPlotGridLinesColor(); + plotGridLinesStroke = theme.getPlotGridLinesStroke(); + markerSize = theme.getMarkerSize(); + errorBarsColor = theme.getErrorBarsColor(); + isErrorBarsColorSeriesColor = theme.isErrorBarsColorSeriesColor(); + locale = Locale.getDefault(); + zoneId = ZoneId.of("UTC"); + datePattern = "YYYY-MM-dd"; + decimalPattern = null; + xAxisDecimalPattern = null; + yAxisDecimalPattern = null; + } + + public boolean isXAxisTitleVisible() { + return xAxisTitleVisible; + } + + /** + * Set the x-axis title visibility + * + * @param xAxisTitleVisible + */ + public void setXAxisTitleVisible(boolean xAxisTitleVisible) { + this.xAxisTitleVisible = xAxisTitleVisible; + } + + public boolean isYAxisTitleVisible() { + return yAxisTitleVisible; + } + + /** + * Set the y-axis title visibility + * + * @param yAxisTitleVisible + */ + public void setYAxisTitleVisible(boolean yAxisTitleVisible) { + this.yAxisTitleVisible = yAxisTitleVisible; + } + + /** + * Set the x- and y-axis titles visibility + * + * @param isVisible + */ + public void setAxisTitlesVisible(boolean isVisible) { + this.xAxisTitleVisible = isVisible; + this.yAxisTitleVisible = isVisible; + } + + public Font getAxisTitleFont() { + return axisTitleFont; + } + + /** + * Set the x- and y-axis title font + * + * @param axisTitleFont + */ + public void setAxisTitleFont(Font axisTitleFont) { + this.axisTitleFont = axisTitleFont; + } + + public boolean isXAxisTicksVisible() { + return xAxisTicksVisible; + } + + /** + * Set the x-axis tick marks and labels visibility + * + * @param xAxisTicksVisible + */ + public void setXAxisTicksVisible(boolean xAxisTicksVisible) { + this.xAxisTicksVisible = xAxisTicksVisible; + } + + public boolean isYAxisTicksVisible() { + return yAxisTicksVisible; + } + + /** + * Set the y-axis tick marks and labels visibility + * + * @param yAxisTicksVisible + */ + public void setYAxisTicksVisible(boolean yAxisTicksVisible) { + this.yAxisTicksVisible = yAxisTicksVisible; + } + + /** + * Set the x- and y-axis tick marks and labels visibility + * + * @param isVisible + */ + public void setAxisTicksVisible(boolean isVisible) { + this.xAxisTicksVisible = isVisible; + this.yAxisTicksVisible = isVisible; + } + + public Font getAxisTickLabelsFont() { + return axisTickLabelsFont; + } + + /** + * Set the x- and y-axis tick label font + * + * @param axisTicksFont + */ + public void setAxisTickLabelsFont(Font axisTicksFont) { + this.axisTickLabelsFont = axisTicksFont; + } + + public int getAxisTickMarkLength() { + return axisTickMarkLength; + } + + /** + * Set the axis tick mark length + * + * @param axisTickMarkLength + */ + public void setAxisTickMarkLength(int axisTickMarkLength) { + this.axisTickMarkLength = axisTickMarkLength; + } + + public int getAxisTickPadding() { + return axisTickPadding; + } + + /** + * sets the padding between the tick labels and the tick marks + * + * @param axisTickPadding + */ + public void setAxisTickPadding(int axisTickPadding) { + this.axisTickPadding = axisTickPadding; + } + + public Color getAxisTickMarksColor() { + return axisTickMarksColor; + } + + /** + * sets the axis tick mark color + * + * @param axisTickColor + */ + public void setAxisTickMarksColor(Color axisTickColor) { + this.axisTickMarksColor = axisTickColor; + } + + public Stroke getAxisTickMarksStroke() { + return axisTickMarksStroke; + } + + /** + * sets the axis tick marks Stroke + * + * @param axisTickMarksStroke + */ + public void setAxisTickMarksStroke(Stroke axisTickMarksStroke) { + this.axisTickMarksStroke = axisTickMarksStroke; + } + + public Color getAxisTickLabelsColor() { + return axisTickLabelsColor; + } + + /** + * sets the axis tick label color + * + * @param axisTickLabelsColor + */ + public void setAxisTickLabelsColor(Color axisTickLabelsColor) { + this.axisTickLabelsColor = axisTickLabelsColor; + } + + public boolean isAxisTicksLineVisible() { + return isAxisTicksLineVisible; + } + + /** + * sets the visibility of the line parallel to the plot edges that go along with the tick marks + * + * @param isAxisTicksLineVisible + */ + public void setAxisTicksLineVisible(boolean isAxisTicksLineVisible) { + this.isAxisTicksLineVisible = isAxisTicksLineVisible; + } + + public boolean isAxisTicksMarksVisible() { + return isAxisTicksMarksVisible; + } + + /** + * sets the visibility of the tick marks + * + * @param isAxisTicksMarksVisible + */ + public void setAxisTicksMarksVisible(boolean isAxisTicksMarksVisible) { + this.isAxisTicksMarksVisible = isAxisTicksMarksVisible; + } + + public int getPlotMargin() { + return plotMargin; + } + + /** + * sets the margin around the plot area + * + * @param plotMargin + */ + public void setPlotMargin(int plotMargin) { + this.plotMargin = plotMargin; + } + + public int getAxisTitlePadding() { + return axisTitlePadding; + } + + /** + * sets the padding between the axis title and the tick labels + * + * @param axisTitlePadding + */ + public void setAxisTitlePadding(int axisTitlePadding) { + this.axisTitlePadding = axisTitlePadding; + } + + public int getXAxisTickMarkSpacingHint() { + return xAxisTickMarkSpacingHint; + } + + /** + * set the spacing between tick marks for the X-Axis + * + * @param xAxisTickMarkSpacingHint + */ + public void setXAxisTickMarkSpacingHint(int xAxisTickMarkSpacingHint) { + this.xAxisTickMarkSpacingHint = xAxisTickMarkSpacingHint; + } + + public int getYAxisTickMarkSpacingHint() { + return yAxisTickMarkSpacingHint; + } + + /** + * set the spacing between tick marks for the Y-Axis + * + * @param yAxisTickMarkSpacingHint + */ + public void setYAxisTickMarkSpacingHint(int yAxisTickMarkSpacingHint) { + this.yAxisTickMarkSpacingHint = yAxisTickMarkSpacingHint; + } + + public boolean isXAxisLogarithmic() { + + return isXAxisLogarithmic; + } + + /** + * sets the X-Axis to be rendered with a logarithmic scale or not + * + * @param isXAxisLogarithmic + */ + public void setXAxisLogarithmic(boolean isXAxisLogarithmic) { + this.isXAxisLogarithmic = isXAxisLogarithmic; + } + + public boolean isYAxisLogarithmic() { + return isYAxisLogarithmic; + } + + /** + * sets the Y-Axis to be rendered with a logarithmic scale or not + * + * @param isYAxisLogarithmic + */ + public void setYAxisLogarithmic(boolean isYAxisLogarithmic) { + this.isYAxisLogarithmic = isYAxisLogarithmic; + } + + public Double getXAxisMin() { + return xAxisMin; + } + + public void setXAxisMin(double xAxisMin) { + this.xAxisMin = xAxisMin; + } + + public Double getXAxisMax() { + return xAxisMax; + } + + public void setXAxisMax(double xAxisMax) { + this.xAxisMax = xAxisMax; + } + + public Double getYAxisMin() { + return yAxisMinMap.get(null); + } + + public AxesChartStyler setYAxisMin(Double yAxisMin) { + this.yAxisMinMap.put(null, yAxisMin); + return this; + } + + public Double getYAxisMin(Integer yAxisGroup) { + return yAxisMinMap.get(yAxisGroup); + } + + public AxesChartStyler setYAxisMax(Integer yAxisGroup, Double yAxisMax) { + this.yAxisMaxMap.put(yAxisGroup, yAxisMax); + return this; + } + + public Double getYAxisMax() { + return yAxisMaxMap.get(null); + } + + public AxesChartStyler setYAxisMax(Double yAxisMax) { + this.yAxisMaxMap.put(null, yAxisMax); + return this; + } + + public Double getYAxisMax(Integer yAxisGroup) { + return yAxisMaxMap.get(yAxisGroup); + } + + public TextAlignment getXAxisLabelAlignment() { + return xAxisLabelAlignment; + } + + public void setXAxisLabelAlignment(TextAlignment xAxisLabelAlignment) { + this.xAxisLabelAlignment = xAxisLabelAlignment; + } + + public TextAlignment getYAxisLabelAlignment() { + return yAxisLabelAlignment; + } + + public void setYAxisLabelAlignment(TextAlignment yAxisLabelAlignment) { + this.yAxisLabelAlignment = yAxisLabelAlignment; + } + + public int getXAxisLabelRotation() { + return xAxisLabelRotation; + } + + public void setXAxisLabelRotation(int xAxisLabelRotation) { + this.xAxisLabelRotation = xAxisLabelRotation; + } + + public boolean isPlotGridLinesVisible() { + return isPlotGridHorizontalLinesVisible && isPlotGridVerticalLinesVisible; + } + + /** + * sets the visibility of the gridlines on the plot area + * + * @param isPlotGridLinesVisible + */ + public void setPlotGridLinesVisible(boolean isPlotGridLinesVisible) { + this.isPlotGridHorizontalLinesVisible = isPlotGridLinesVisible; + this.isPlotGridVerticalLinesVisible = isPlotGridLinesVisible; + } + + public boolean isPlotGridHorizontalLinesVisible() { + return isPlotGridHorizontalLinesVisible; + } + + /** + * sets the visibility of the horizontal gridlines on the plot area + * + * @param isPlotGridHorizontalLinesVisible + */ + public void setPlotGridHorizontalLinesVisible(boolean isPlotGridHorizontalLinesVisible) { + this.isPlotGridHorizontalLinesVisible = isPlotGridHorizontalLinesVisible; + } + + public boolean isPlotGridVerticalLinesVisible() { + return isPlotGridVerticalLinesVisible; + } + + /** + * sets the visibility of the vertical gridlines on the plot area + * + * @param isPlotGridVerticalLinesVisible + */ + public void setPlotGridVerticalLinesVisible(boolean isPlotGridVerticalLinesVisible) { + this.isPlotGridVerticalLinesVisible = isPlotGridVerticalLinesVisible; + } + + public boolean isPlotTicksMarksVisible() { + return isPlotTicksMarksVisible; + } + + /** + * sets the visibility of the ticks marks inside the plot area + * + * @param isPlotTicksMarksVisible + */ + public void setPlotTicksMarksVisible(boolean isPlotTicksMarksVisible) { + this.isPlotTicksMarksVisible = isPlotTicksMarksVisible; + } + + public Color getPlotGridLinesColor() { + return plotGridLinesColor; + } + + /** + * set the plot area's grid lines color + * + * @param plotGridLinesColor + */ + public void setPlotGridLinesColor(Color plotGridLinesColor) { + this.plotGridLinesColor = plotGridLinesColor; + } + + public Stroke getPlotGridLinesStroke() { + return plotGridLinesStroke; + } + + /** + * set the plot area's grid lines Stroke + * + * @param plotGridLinesStroke + */ + public void setPlotGridLinesStroke(Stroke plotGridLinesStroke) { + this.plotGridLinesStroke = plotGridLinesStroke; + } + + + public int getMarkerSize() { + return markerSize; + } + + /** + * Sets the size of the markers in pixels + * + * @param markerSize + */ + public void setMarkerSize(int markerSize) { + this.markerSize = markerSize; + } + + + public Color getErrorBarsColor() { + return errorBarsColor; + } + + /** + * Sets the color of the error bars + * + * @param errorBarsColor + */ + public void setErrorBarsColor(Color errorBarsColor) { + this.errorBarsColor = errorBarsColor; + } + + public boolean isErrorBarsColorSeriesColor() { + return isErrorBarsColorSeriesColor; + } + + /** + * Set true if the the error bar color should match the series color. + */ + public void setErrorBarsColorSeriesColor(boolean isErrorBarsColorSeriesColor) { + this.isErrorBarsColorSeriesColor = isErrorBarsColorSeriesColor; + } + + public Locale getLocale() { + return locale; + } + + /** + * Set the locale to use for rendering the chart + * + * @param locale - the locale to use when formatting Strings and dates for the axis tick labels + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + public ZoneId getZoneId() { + return zoneId; + } + + /** + * Set the zone ID to use for formatting time instant axis tick labels + * + * @param zoneId the zone ID to use when formatting time instants + */ + public void setZoneId(ZoneId zoneId) { + this.zoneId = zoneId; + } + + public String getDatePattern() { + return datePattern; + } + + /** + * Set the String formatter for Data x-axis + * + * @param datePattern - the pattern describing the date and time format + */ + public void setDatePattern(String datePattern) { + this.datePattern = datePattern; + } + + public String getDecimalPattern() { + return decimalPattern; + } + + /** + * Set the decimal formatter for all tick labels + * + * @param decimalPattern - the pattern describing the decimal format + */ + public void setDecimalPattern(String decimalPattern) { + this.decimalPattern = decimalPattern; + } + + public String getXAxisDecimalPattern() { + return xAxisDecimalPattern; + } + + /** + * Set the decimal formatting pattern for the X-Axis + * + * @param xAxisDecimalPattern + */ + public void setXAxisDecimalPattern(String xAxisDecimalPattern) { + this.xAxisDecimalPattern = xAxisDecimalPattern; + } + + public String getYAxisDecimalPattern() { + return yAxisDecimalPattern; + } + + /** + * Set the decimal formatting pattern for the Y-Axis + * + * @param yAxisDecimalPattern + */ + public void setYAxisDecimalPattern(String yAxisDecimalPattern) { + this.yAxisDecimalPattern = yAxisDecimalPattern; + } + + public boolean isXAxisLogarithmicDecadeOnly() { + return xAxisLogarithmicDecadeOnly; + } + + /** + * Set the decade only support for logarithmic Y-Axis + * + * @param xAxisLogarithmicDecadeOnly + */ + public AxesChartStyler setXAxisLogarithmicDecadeOnly(boolean xAxisLogarithmicDecadeOnly) { + this.xAxisLogarithmicDecadeOnly = xAxisLogarithmicDecadeOnly; + return this; + } + + public boolean isYAxisLogarithmicDecadeOnly() { + return yAxisLogarithmicDecadeOnly; + } + + /** + * Set the decade only support for logarithmic Y-Axis + * + * @param yAxisLogarithmicDecadeOnly + */ + public AxesChartStyler setYAxisLogarithmicDecadeOnly(boolean yAxisLogarithmicDecadeOnly) { + this.yAxisLogarithmicDecadeOnly = yAxisLogarithmicDecadeOnly; + return this; + } + + public TextAlignment getXAxisLabelAlignmentVertical() { + return xAxisLabelAlignmentVertical; + } + + public void setXAxisLabelAlignmentVertical(TextAlignment xAxisLabelAlignmentVertical) { + this.xAxisLabelAlignmentVertical = xAxisLabelAlignmentVertical; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/style/SeriesColorMarkerLineStyle.java b/chart/src/main/java/org/xbib/graphics/chart/style/SeriesColorMarkerLineStyle.java new file mode 100644 index 0000000..130fa8a --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/style/SeriesColorMarkerLineStyle.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.chart.style; + +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.BasicStroke; +import java.awt.Color; + +public final class SeriesColorMarkerLineStyle { + + private final Color color; + private final Theme.Series.Marker marker; + private final BasicStroke stroke; + + public SeriesColorMarkerLineStyle(Color color, Theme.Series.Marker marker, BasicStroke stroke) { + this.color = color; + this.marker = marker; + this.stroke = stroke; + } + + public Color getColor() { + return color; + } + + public Theme.Series.Marker getMarker() { + return marker; + } + + public BasicStroke getStroke() { + return stroke; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/style/SeriesColorMarkerLineStyleCycler.java b/chart/src/main/java/org/xbib/graphics/chart/style/SeriesColorMarkerLineStyleCycler.java new file mode 100644 index 0000000..ea31b41 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/style/SeriesColorMarkerLineStyleCycler.java @@ -0,0 +1,45 @@ +package org.xbib.graphics.chart.style; + +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.util.List; + +/** + * Cycles through the different colors, markers, and strokes in a predetermined way. + */ +public class SeriesColorMarkerLineStyleCycler { + + private final List seriesColorList; + private final List seriesMarkerList; + private final List seriesLineStyleList; + private int colorCounter = 0; + private int markerCounter = 0; + private int strokeCounter = 0; + + public SeriesColorMarkerLineStyleCycler(List seriesColorList, + List seriesMarkerList, + List seriesLineStyleList) { + this.seriesColorList = seriesColorList; + this.seriesMarkerList = seriesMarkerList; + this.seriesLineStyleList = seriesLineStyleList; + } + + public SeriesColorMarkerLineStyle getNextSeriesColorMarkerLineStyle() { + if (colorCounter >= seriesColorList.size()) { + colorCounter = 0; + strokeCounter++; + } + Color seriesColor = seriesColorList.get(colorCounter++); + if (strokeCounter >= seriesLineStyleList.size()) { + strokeCounter = 0; + } + BasicStroke seriesLineStyle = seriesLineStyleList.get(strokeCounter); + if (markerCounter >= seriesMarkerList.size()) { + markerCounter = 0; + } + Theme.Series.Marker marker = seriesMarkerList.get(markerCounter++); + return new SeriesColorMarkerLineStyle(seriesColor, marker, seriesLineStyle); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/style/Styler.java b/chart/src/main/java/org/xbib/graphics/chart/style/Styler.java new file mode 100644 index 0000000..af465b4 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/style/Styler.java @@ -0,0 +1,518 @@ +package org.xbib.graphics.chart.style; + +import org.xbib.graphics.chart.theme.Theme; +import org.xbib.graphics.chart.theme.DefaultTheme; +import org.xbib.graphics.chart.legend.LegendLayout; +import org.xbib.graphics.chart.legend.LegendPosition; +import org.xbib.graphics.chart.axis.YAxisPosition; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.util.HashMap; +import java.util.List; + +/** + * The styler is used to manage all things related to styling of the vast number of Chart components + */ +public abstract class Styler { + + protected Theme theme = new DefaultTheme(); + + protected boolean hasAnnotations = false; // set by subclass + + // Chart Style + private Font baseFont; + private Color chartBackgroundColor; + private Color chartFontColor; + private int chartPadding; + private List seriesColors; + private List seriesLines; + private List seriesMarkers; + // Chart Title + private Font chartTitleFont; + private boolean isChartTitleVisible; + private boolean isChartTitleBoxVisible; + private Color chartTitleBoxBackgroundColor; + private Color chartTitleBoxBorderColor; + private int chartTitlePadding; + // Chart Legend + private boolean isLegendVisible; + private Color legendBackgroundColor; + private Color legendBorderColor; + private Font legendFont; + private int legendPadding; + private int legendSeriesLineLength; + private LegendPosition legendPosition; + private LegendLayout legendLayout = LegendLayout.Vertical; + // Chart Plot Area + private Color plotBackgroundColor; + private Color plotBorderColor; + private boolean isPlotBorderVisible; + private double plotContentSize = .92; + private Font annotationsFont; + private boolean antiAlias = true; + private String decimalPattern; + private final HashMap yAxisAlignmentMap = new HashMap<>(); + + protected void setAllStyles() { + // Chart Style + chartBackgroundColor = theme.getChartBackgroundColor(); + chartFontColor = theme.getChartFontColor(); + chartPadding = theme.getChartPadding(); + seriesColors = theme.getSeriesColors(); + seriesLines = theme.getSeriesLines(); + seriesMarkers = theme.getSeriesMarkers(); + + // Chart Title + chartTitleFont = theme.getChartTitleFont(); + isChartTitleVisible = theme.isChartTitleVisible(); + isChartTitleBoxVisible = theme.isChartTitleBoxVisible(); + chartTitleBoxBackgroundColor = theme.getChartTitleBoxBackgroundColor(); + chartTitleBoxBorderColor = theme.getChartTitleBoxBorderColor(); + chartTitlePadding = theme.getChartTitlePadding(); + + // legend + isLegendVisible = theme.isLegendVisible(); + legendBackgroundColor = theme.getLegendBackgroundColor(); + legendBorderColor = theme.getLegendBorderColor(); + legendFont = theme.getLegendFont(); + legendPadding = theme.getLegendPadding(); + legendSeriesLineLength = theme.getLegendSeriesLineLength(); + legendPosition = theme.getLegendPosition(); + + // Chart Plot Area + plotBackgroundColor = theme.getPlotBackgroundColor(); + plotBorderColor = theme.getPlotBorderColor(); + isPlotBorderVisible = theme.isPlotBorderVisible(); + plotContentSize = theme.getPlotContentSize(); + + annotationsFont = theme.getAnnotationFont(); + + // Formatting + decimalPattern = null; + } + + /** + * Set the base font + * + * @param baseFont + * @return styler + */ + public Styler setBaseFont(Font baseFont) { + this.baseFont = baseFont; + return this; + } + + public Font getBaseFont() { + return baseFont; + } + + public Color getChartBackgroundColor() { + return chartBackgroundColor; + } + + /** + * Set the chart background color - the part around the edge of the chart + * + * @param color + */ + public void setChartBackgroundColor(Color color) { + this.chartBackgroundColor = color; + } + + public Color getChartFontColor() { + return chartFontColor; + } + + // Chart Style + + /** + * Set the chart font color. includes: Chart title, axes label, legend + * + * @param color + */ + public void setChartFontColor(Color color) { + this.chartFontColor = color; + } + + public int getChartPadding() { + return chartPadding; + } + + /** + * Set the chart padding + * + * @param chartPadding + */ + public void setChartPadding(int chartPadding) { + this.chartPadding = chartPadding; + } + + public List getSeriesColors() { + return seriesColors; + } + + public void setSeriesColors(List seriesColors) { + this.seriesColors = seriesColors; + } + + public List getSeriesLines() { + return seriesLines; + } + + // Chart Title + + public void setSeriesLines(List seriesLines) { + this.seriesLines = seriesLines; + } + + public List getSeriesMarkers() { + return seriesMarkers; + } + + public void setSeriesMarkers(List seriesMarkers) { + this.seriesMarkers = seriesMarkers; + } + + public Font getChartTitleFont() { + return chartTitleFont; + } + + /** + * Set the chart title font + * + * @param chartTitleFont font + */ + public void setChartTitleFont(Font chartTitleFont) { + this.chartTitleFont = chartTitleFont; + } + + public boolean isChartTitleVisible() { + return isChartTitleVisible; + } + + /** + * Set the chart title visibility + * + * @param isChartTitleVisible + */ + public void setChartTitleVisible(boolean isChartTitleVisible) { + this.isChartTitleVisible = isChartTitleVisible; + } + + public boolean isChartTitleBoxVisible() { + return isChartTitleBoxVisible; + } + + /** + * Set the chart title box visibility + * + * @param isChartTitleBoxVisible + */ + public void setChartTitleBoxVisible(boolean isChartTitleBoxVisible) { + + this.isChartTitleBoxVisible = isChartTitleBoxVisible; + } + + public Color getChartTitleBoxBackgroundColor() { + + return chartTitleBoxBackgroundColor; + } + + /** + * set the chart title box background color + * + * @param chartTitleBoxBackgroundColor + */ + public void setChartTitleBoxBackgroundColor(Color chartTitleBoxBackgroundColor) { + + this.chartTitleBoxBackgroundColor = chartTitleBoxBackgroundColor; + } + + public Color getChartTitleBoxBorderColor() { + + return chartTitleBoxBorderColor; + } + + /** + * set the chart title box border color + * + * @param chartTitleBoxBorderColor + */ + public void setChartTitleBoxBorderColor(Color chartTitleBoxBorderColor) { + + this.chartTitleBoxBorderColor = chartTitleBoxBorderColor; + } + + public int getChartTitlePadding() { + + return chartTitlePadding; + } + + /** + * set the chart title padding; the space between the chart title and the plot area + * + * @param chartTitlePadding + */ + public void setChartTitlePadding(int chartTitlePadding) { + + this.chartTitlePadding = chartTitlePadding; + } + + public Color getLegendBackgroundColor() { + + return legendBackgroundColor; + } + + /** + * Set the chart legend background color + * + * @param color + */ + public void setLegendBackgroundColor(Color color) { + + this.legendBackgroundColor = color; + } + + /** + * Set the chart legend border color + * + * @return + */ + public Color getLegendBorderColor() { + + return legendBorderColor; + } + + // Chart Legend + + public void setLegendBorderColor(Color legendBorderColor) { + + this.legendBorderColor = legendBorderColor; + } + + public Font getLegendFont() { + + return legendFont; + } + + /** + * Set the chart legend font + * + * @param font + */ + public void setLegendFont(Font font) { + + this.legendFont = font; + } + + public boolean isLegendVisible() { + + return isLegendVisible; + } + + /** + * Set the chart legend visibility + * + * @param isLegendVisible + */ + public void setLegendVisible(boolean isLegendVisible) { + + this.isLegendVisible = isLegendVisible; + } + + public int getLegendPadding() { + + return legendPadding; + } + + /** + * Set the chart legend padding + * + * @param legendPadding + */ + public void setLegendPadding(int legendPadding) { + + this.legendPadding = legendPadding; + } + + public int getLegendSeriesLineLength() { + + return legendSeriesLineLength; + } + + /** + * Set the chart legend series line length + * + * @param legendSeriesLineLength + */ + public void setLegendSeriesLineLength(int legendSeriesLineLength) { + + if (legendSeriesLineLength < 0) { + this.legendSeriesLineLength = 0; + } else { + this.legendSeriesLineLength = legendSeriesLineLength; + } + } + + public LegendPosition getLegendPosition() { + + return legendPosition; + } + + /** + * sets the legend position + * + * @param legendPosition + */ + public void setLegendPosition(LegendPosition legendPosition) { + + this.legendPosition = legendPosition; + } + + /** + * Set the legend layout + * + * @return + */ + public LegendLayout getLegendLayout() { + + return legendLayout; + } + + public void setLegendLayout(LegendLayout legendLayout) { + + this.legendLayout = legendLayout; + } + + + public Color getPlotBackgroundColor() { + + return plotBackgroundColor; + } + + /** + * set the plot area's background color + * + * @param plotBackgroundColor + */ + public void setPlotBackgroundColor(Color plotBackgroundColor) { + + this.plotBackgroundColor = plotBackgroundColor; + } + + public Color getPlotBorderColor() { + + return plotBorderColor; + } + + // Chart Plot + + /** + * set the plot area's border color + * + * @param plotBorderColor + */ + public void setPlotBorderColor(Color plotBorderColor) { + + this.plotBorderColor = plotBorderColor; + } + + public boolean isPlotBorderVisible() { + + return isPlotBorderVisible; + } + + /** + * sets the visibility of the border around the plot area + * + * @param isPlotBorderVisible + */ + public void setPlotBorderVisible(boolean isPlotBorderVisible) { + + this.isPlotBorderVisible = isPlotBorderVisible; + } + + public double getPlotContentSize() { + + return plotContentSize; + } + + /** + * Sets the content size of the plot inside the plot area of the chart. To fill the area 100%, use a value of 1.0. + * + * @param plotContentSize - Valid range is between 0 and 1. + */ + public void setPlotContentSize(double plotContentSize) { + + if (plotContentSize < 0 || plotContentSize > 1) { + throw new IllegalArgumentException("Plot content size must be tween 0 and 1"); + } + + this.plotContentSize = plotContentSize; + } + + public Boolean hasAnnotations() { + + return hasAnnotations; + } + + /** + * Sets if annotations should be added to charts. Each chart type has a different annotation type + * + * @param hasAnnotations + */ + public void setHasAnnotations(boolean hasAnnotations) { + this.hasAnnotations = hasAnnotations; + } + + public Font getAnnotationsFont() { + return annotationsFont; + } + + /** + * Sets the Font used for chart annotations + * + * @param annotationsFont + */ + public void setAnnotationsFont(Font annotationsFont) { + this.annotationsFont = annotationsFont; + } + + /** + * Set the decimal formatter for all numbers on the chart rendered as Strings + * + * @param decimalPattern - the pattern describing the decimal format + */ + public void setDecimalPattern(String decimalPattern) { + this.decimalPattern = decimalPattern; + } + + public String getDecimalPattern() { + return decimalPattern; + } + + /** + * Set the YAxis group position. + * + * @param yAxisGroup + * @param yAxisPosition + */ + public void setYAxisGroupPosition(int yAxisGroup, YAxisPosition yAxisPosition) { + yAxisAlignmentMap.put(yAxisGroup, yAxisPosition); + } + + public YAxisPosition getYAxisGroupPosistion(int yAxisGroup) { + return yAxisAlignmentMap.get(yAxisGroup); + } + + public void setAntiAlias(boolean newVal) { + antiAlias = newVal; + } + + public boolean getAntiAlias() { + return antiAlias; + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/theme/DefaultTheme.java b/chart/src/main/java/org/xbib/graphics/chart/theme/DefaultTheme.java new file mode 100644 index 0000000..bb2a30c --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/theme/DefaultTheme.java @@ -0,0 +1,313 @@ +package org.xbib.graphics.chart.theme; + +import org.xbib.graphics.chart.legend.LegendPosition; +import org.xbib.graphics.chart.pie.PieStyler.AnnotationType; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Stroke; +import java.util.List; + +public class DefaultTheme implements Theme { + + @Override + public Font getBaseFont() { + return Fonts.SANS_SERIF_PLAIN_10; + } + + @Override + public boolean isDrawAllAnnotations() { + return false; + } + + @Override + public double getDonutThickness() { + return .33; + } + + @Override + public boolean isSumVisible() { + return false; + } + + @Override + public Font getSumFont() { + return getAnnotationFont(); + } + + @Override + public Font getAnnotationFont() { + return getPieFont().deriveFont(12f); + } + + @Override + public Color getChartBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getChartFontColor() { + return Colors.BLACK; + } + + @Override + public int getChartPadding() { + return 10; + } + + @Override + public List getSeriesColors() { + return PrinterFriendly.SERIES_COLORS; + } + + @Override + public List getSeriesMarkers() { + return Default.SERIES_MARKERS; + } + + @Override + public List getSeriesLines() { + return Default.SERIES_LINES; + } + + @Override + public Font getChartTitleFont() { + return Fonts.SANS_SERIF_BOLD_14; + } + + @Override + public boolean isChartTitleVisible() { + return true; + } + + @Override + public boolean isChartTitleBoxVisible() { + return false; + } + + @Override + public Color getChartTitleBoxBackgroundColor() { + return Colors.GREY; + } + + @Override + public Color getChartTitleBoxBorderColor() { + return Colors.GREY; + } + + @Override + public int getChartTitlePadding() { + return 5; + } + + @Override + public Font getLegendFont() { + return Fonts.SANS_SERIF_PLAIN_11; + } + + @Override + public boolean isLegendVisible() { + return true; + } + + @Override + public Color getLegendBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getLegendBorderColor() { + return Colors.DARK_GREY; + } + + @Override + public int getLegendPadding() { + return 10; + } + + @Override + public int getLegendSeriesLineLength() { + return 24; + } + + @Override + public LegendPosition getLegendPosition() { + return LegendPosition.OutsideE; + } + + @Override + public boolean isXAxisTitleVisible() { + return true; + } + + @Override + public boolean isYAxisTitleVisible() { + return true; + } + + @Override + public Font getAxisTitleFont() { + return Fonts.SANS_SERIF_BOLD_12; + } + + @Override + public boolean isXAxisTicksVisible() { + return true; + } + + @Override + public boolean isYAxisTicksVisible() { + return true; + } + + @Override + public Font getAxisTickLabelsFont() { + return Fonts.SANS_SERIF_BOLD_12; + } + + @Override + public int getAxisTickMarkLength() { + return 3; + } + + @Override + public int getAxisTickPadding() { + return 4; + } + + @Override + public int getPlotMargin() { + return 4; + } + + @Override + public Color getAxisTickMarksColor() { + return Colors.DARK_GREY; + } + + @Override + public Stroke getAxisTickMarksStroke() { + return Standard.AXIS_TICKMARK; + } + + @Override + public Color getAxisTickLabelsColor() { + return Colors.BLACK; + } + + @Override + public boolean isAxisTicksLineVisible() { + return true; + } + + @Override + public boolean isAxisTicksMarksVisible() { + return true; + } + + @Override + public int getAxisTitlePadding() { + return 10; + } + + @Override + public int getXAxisTickMarkSpacingHint() { + return 74; + } + + @Override + public int getYAxisTickMarkSpacingHint() { + return 44; + } + + @Override + public boolean isPlotGridVerticalLinesVisible() { + return true; + } + + @Override + public boolean isPlotGridHorizontalLinesVisible() { + return true; + } + + @Override + public Color getPlotBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getPlotBorderColor() { + return Colors.DARK_GREY; + } + + @Override + public boolean isPlotBorderVisible() { + return true; + } + + @Override + public boolean isPlotTicksMarksVisible() { + return false; + } + + @Override + public Color getPlotGridLinesColor() { + return Colors.WHITE; + } + + @Override + public Stroke getPlotGridLinesStroke() { + return Standard.GRID_LINES; + } + + @Override + public double getPlotContentSize() { + return .92; + } + + @Override + public double getAvailableSpaceFill() { + return 0.9; + } + + @Override + public boolean isOverlapped() { + return false; + } + + @Override + public boolean isCircular() { + return true; + } + + @Override + public Font getPieFont() { + return Fonts.SANS_SERIF_PLAIN_15; + } + + @Override + public double getAnnotationDistance() { + return .67; + } + + @Override + public AnnotationType getAnnotationType() { + return AnnotationType.Percentage; + } + + @Override + public int getMarkerSize() { + return 8; + } + + @Override + public Color getErrorBarsColor() { + return Colors.BLACK; + } + + @Override + public boolean isErrorBarsColorSeriesColor() { + return false; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/theme/GGPlot2Theme.java b/chart/src/main/java/org/xbib/graphics/chart/theme/GGPlot2Theme.java new file mode 100644 index 0000000..1d22013 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/theme/GGPlot2Theme.java @@ -0,0 +1,283 @@ +package org.xbib.graphics.chart.theme; + +import org.xbib.graphics.chart.legend.LegendPosition; +import org.xbib.graphics.chart.pie.PieStyler.AnnotationType; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Stroke; +import java.util.List; + +public class GGPlot2Theme extends DefaultTheme { + + @Override + public Color getChartBackgroundColor() { + return Theme.Colors.WHITE; + } + + @Override + public Color getChartFontColor() { + return Colors.BLACK; + } + + @Override + public int getChartPadding() { + return 10; + } + + @Override + public List getSeriesMarkers() { + return GGPlot2.SERIES_MARKERS; + } + + @Override + public List getSeriesLines() { + return GGPlot2.SERIES_LINES; + } + + @Override + public List getSeriesColors() { + return GGPlot2.SERIES_COLORS; + } + + @Override + public Font getChartTitleFont() { + return Fonts.SANS_SERIF_PLAIN_14; + } + + @Override + public boolean isChartTitleVisible() { + return true; + } + + @Override + public boolean isChartTitleBoxVisible() { + return true; + } + + @Override + public Color getChartTitleBoxBackgroundColor() { + return Colors.GREY; + } + + @Override + public Color getChartTitleBoxBorderColor() { + return Colors.GREY; + } + + @Override + public int getChartTitlePadding() { + return 5; + } + + @Override + public Font getLegendFont() { + return Fonts.SANS_SERIF_PLAIN_14; + } + + @Override + public boolean isLegendVisible() { + return true; + } + + @Override + public Color getLegendBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getLegendBorderColor() { + return Colors.WHITE; + } + + @Override + public int getLegendPadding() { + return 10; + } + + @Override + public int getLegendSeriesLineLength() { + return 24; + } + + @Override + public LegendPosition getLegendPosition() { + return LegendPosition.OutsideE; + } + + @Override + public boolean isXAxisTitleVisible() { + return true; + } + + @Override + public boolean isYAxisTitleVisible() { + return true; + } + + @Override + public Font getAxisTitleFont() { + return Fonts.SANS_SERIF_PLAIN_14; + } + + @Override + public boolean isXAxisTicksVisible() { + return true; + } + + @Override + public boolean isYAxisTicksVisible() { + return true; + } + + @Override + public Font getAxisTickLabelsFont() { + return Fonts.SANS_SERIF_BOLD_13; + } + + @Override + public int getAxisTickMarkLength() { + return 8; + } + + @Override + public int getAxisTickPadding() { + return 5; + } + + @Override + public int getPlotMargin() { + return 0; + } + + @Override + public boolean isAxisTicksLineVisible() { + return false; + } + + @Override + public boolean isAxisTicksMarksVisible() { + return true; + } + + @Override + public Color getAxisTickMarksColor() { + return Colors.DARK_GREY; + } + + @Override + public Stroke getAxisTickMarksStroke() { + return GGPlot2.AXIS_TICKMARK; + } + + @Override + public Color getAxisTickLabelsColor() { + return Colors.DARK_GREY; + } + + @Override + public int getAxisTitlePadding() { + return 10; + } + + @Override + public int getXAxisTickMarkSpacingHint() { + return 74; + } + + @Override + public int getYAxisTickMarkSpacingHint() { + return 44; + } + + @Override + public boolean isPlotGridVerticalLinesVisible() { + return true; + } + + @Override + public boolean isPlotGridHorizontalLinesVisible() { + return true; + } + + @Override + public Color getPlotBackgroundColor() { + return Colors.LIGHT_GREY; + } + + @Override + public Color getPlotBorderColor() { + return Colors.WHITE; + } + + @Override + public boolean isPlotBorderVisible() { + return false; + } + + @Override + public boolean isPlotTicksMarksVisible() { + return false; + } + + @Override + public Color getPlotGridLinesColor() { + return Colors.WHITE; + } + + @Override + public Stroke getPlotGridLinesStroke() { + return GGPlot2.GRID_LINES; + } + + @Override + public double getPlotContentSize() { + return .92; + } + + @Override + public double getAvailableSpaceFill() { + return 0.9; + } + + @Override + public boolean isOverlapped() { + return false; + } + + @Override + public boolean isCircular() { + return true; + } + + @Override + public Font getPieFont() { + return Fonts.SANS_SERIF_PLAIN_15; + } + + @Override + public double getAnnotationDistance() { + return .67; + } + + @Override + public AnnotationType getAnnotationType() { + return AnnotationType.LabelAndPercentage; + } + + @Override + public int getMarkerSize() { + return 8; + } + + @Override + public Color getErrorBarsColor() { + return Colors.DARK_GREY; + } + + @Override + public boolean isErrorBarsColorSeriesColor() { + return false; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/theme/MatlabTheme.java b/chart/src/main/java/org/xbib/graphics/chart/theme/MatlabTheme.java new file mode 100644 index 0000000..eb498a9 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/theme/MatlabTheme.java @@ -0,0 +1,284 @@ +package org.xbib.graphics.chart.theme; + +import org.xbib.graphics.chart.legend.LegendPosition; +import org.xbib.graphics.chart.pie.PieStyler.AnnotationType; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Stroke; +import java.util.List; + +public class MatlabTheme extends DefaultTheme { + + @Override + public Color getChartBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getChartFontColor() { + return Colors.BLACK; + } + + @Override + public int getChartPadding() { + return 10; + } + + @Override + public List getSeriesMarkers() { + return Matlab.SERIES_MARKERS; + } + + @Override + public List getSeriesLines() { + return Matlab.SERIES_LINES; + } + + @Override + public List getSeriesColors() { + return Matlab.SERIES_COLORS; + } + + @Override + public Font getChartTitleFont() { + return Fonts.SANS_SERIF_BOLD_14; + } + + @Override + public boolean isChartTitleVisible() { + return true; + } + + @Override + public boolean isChartTitleBoxVisible() { + return false; + } + + @Override + public Color getChartTitleBoxBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getChartTitleBoxBorderColor() { + return Colors.WHITE; + } + + @Override + public int getChartTitlePadding() { + return 5; + } + + @Override + public Font getLegendFont() { + return Fonts.SANS_SERIF_PLAIN_11; + } + + @Override + public boolean isLegendVisible() { + return true; + } + + @Override + public Color getLegendBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getLegendBorderColor() { + return Colors.BLACK; + } + + @Override + public int getLegendPadding() { + return 10; + } + + @Override + public int getLegendSeriesLineLength() { + return 24; + } + + @Override + public LegendPosition getLegendPosition() { + return LegendPosition.OutsideE; + } + + @Override + public boolean isXAxisTitleVisible() { + return true; + } + + @Override + public boolean isYAxisTitleVisible() { + return true; + } + + @Override + public Font getAxisTitleFont() { + return Fonts.SANS_SERIF_PLAIN_12; + } + + @Override + public boolean isXAxisTicksVisible() { + return true; + } + + @Override + public boolean isYAxisTicksVisible() { + return true; + } + + @Override + public Font getAxisTickLabelsFont() { + return Fonts.SANS_SERIF_PLAIN_12; + } + + @Override + public int getAxisTickMarkLength() { + return 5; + } + + @Override + public int getAxisTickPadding() { + return 4; + } + + @Override + public int getPlotMargin() { + return 3; + } + + @Override + public Color getAxisTickMarksColor() { + return Colors.BLACK; + } + + @Override + public Stroke getAxisTickMarksStroke() { + return Matlab.AXIS_TICKMARK; + } + + @Override + public Color getAxisTickLabelsColor() { + return Colors.BLACK; + } + + @Override + public boolean isAxisTicksLineVisible() { + return false; + } + + @Override + public boolean isAxisTicksMarksVisible() { + return false; + } + + @Override + public int getAxisTitlePadding() { + return 10; + } + + @Override + public int getXAxisTickMarkSpacingHint() { + return 74; + } + + @Override + public int getYAxisTickMarkSpacingHint() { + return 44; + } + + @Override + public boolean isPlotGridVerticalLinesVisible() { + return true; + } + + @Override + public boolean isPlotGridHorizontalLinesVisible() { + return true; + } + + @Override + public Color getPlotBackgroundColor() { + return Colors.WHITE; + } + + @Override + public Color getPlotBorderColor() { + return Colors.BLACK; + } + + @Override + public boolean isPlotBorderVisible() { + return true; + } + + @Override + public boolean isPlotTicksMarksVisible() { + return true; + } + + @Override + public Color getPlotGridLinesColor() { + return Colors.BLACK; + } + + @Override + public Stroke getPlotGridLinesStroke() { + return Matlab.GRID_LINES; + } + + @Override + public double getPlotContentSize() { + return .92; + } + + @Override + public double getAvailableSpaceFill() { + return 0.9; + } + + @Override + public boolean isOverlapped() { + return false; + } + + @Override + public boolean isCircular() { + return true; + } + + @Override + public Font getPieFont() { + return Fonts.SANS_SERIF_PLAIN_15; + } + + @Override + public double getAnnotationDistance() { + return .67; + } + + @Override + public AnnotationType getAnnotationType() { + return AnnotationType.Label; + } + + @Override + public int getMarkerSize() { + return 8; + } + + @Override + public Color getErrorBarsColor() { + return Colors.BLACK; + } + + @Override + public boolean isErrorBarsColorSeriesColor() { + return false; + } + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/theme/Theme.java b/chart/src/main/java/org/xbib/graphics/chart/theme/Theme.java new file mode 100644 index 0000000..85776be --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/theme/Theme.java @@ -0,0 +1,431 @@ +package org.xbib.graphics.chart.theme; + +import org.xbib.graphics.chart.legend.LegendPosition; +import org.xbib.graphics.chart.pie.PieStyler.AnnotationType; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public interface Theme { + + interface Default { + + List SERIES_COLORS = Arrays.asList( + new Color(141, 211, 199), + new Color(255, 255, 179), + new Color(190, 186, 218), + new Color(251, 128, 114), + new Color(128, 177, 211), + new Color(253, 180, 98), + new Color(179, 222, 105), + new Color(252, 205, 229), + new Color(217, 217, 217), + new Color(188, 128, 189), + new Color(204, 235, 197), + new Color(255, 237, 111) + ); + + List SERIES_MARKERS = Arrays.asList( + Series.CIRCLE_MARKER, + Series.SQUARE_MARKER, + Series.DIAMOND_MARKER, + Series.TRIANGLE_UP_MARKER, + Series.TRIANGLE_DOWN_MARKER, + Series.CROSS_MARKER); + + List SERIES_LINES = Arrays.asList( + Series.SOLID_STROKE, + Series.DOT_DOT_STROKE, + Series.DASH_DASH_STROKE, + Series.DASH_DOT_STROKE); + + BasicStroke AXIS_TICKMARK = new BasicStroke(1.0f); + + BasicStroke GRID_LINES = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 5.0f}, 0.0f); + } + + interface Standard { + + Color BLUE = new Color(0, 55, 255, 180); + Color ORANGE = new Color(255, 172, 0, 180); + Color PURPLE = new Color(128, 0, 255, 180); + Color GREEN = new Color(0, 205, 0, 180); + Color RED = new Color(205, 0, 0, 180); + Color YELLOW = new Color(255, 215, 0, 180); + Color MAGENTA = new Color(255, 0, 255, 180); + Color PINK = new Color(255, 166, 201, 180); + Color LIGHT_GREY = new Color(207, 207, 207, 180); + Color CYAN = new Color(0, 255, 255, 180); + Color BROWN = new Color(102, 56, 10, 180); + Color BLACK = new Color(0, 0, 0, 180); + + List SERIES_COLORS = Arrays.asList( + BLUE, ORANGE, PURPLE, GREEN, RED, YELLOW, MAGENTA, PINK, LIGHT_GREY, CYAN, BROWN, BLACK + ); + + List SERIES_MARKERS = Arrays.asList( + Series.CIRCLE_MARKER, + Series.DIAMOND_MARKER, + Series.SQUARE_MARKER, + Series.TRIANGLE_DOWN_MARKER, + Series.TRIANGLE_UP_MARKER); + + + List SERIES_LINES = Arrays.asList( + Series.SOLID_STROKE, + Series.DASH_DOT_STROKE, + Series.DASH_DASH_STROKE, + Series.DOT_DOT_STROKE); + + BasicStroke AXIS_TICKMARK = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{3.0f, 0.0f}, 0.0f); + + BasicStroke GRID_LINES = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{3.0f, 3.0f}, 0.0f); + } + + interface GGPlot2 { + List SERIES_LINES = + Arrays.asList(Series.SOLID_STROKE, Series.DOT_DOT_STROKE, Series.DASH_DASH_STROKE); + + List SERIES_MARKERS = Arrays.asList(Series.CIRCLE_MARKER, + Series.DIAMOND_MARKER); + + Color RED = new Color(248, 118, 109, 255); + Color YELLOW_GREEN = new Color(163, 165, 0, 255); + Color GREEN = new Color(0, 191, 125, 255); + Color BLUE = new Color(0, 176, 246, 255); + Color PURPLE = new Color(231, 107, 243, 255); + + List SERIES_COLORS = Arrays.asList(RED, YELLOW_GREEN, GREEN, BLUE, PURPLE); + + BasicStroke AXIS_TICKMARK = new BasicStroke(1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{3.0f, 0.0f}, 0.0f); + + BasicStroke GRID_LINES = new BasicStroke(1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{3.0f, 0.0f}, 0.0f); + } + + interface Matlab { + + List SERIES_LINES = + Arrays.asList(Series.SOLID_STROKE/*, Series.DASH_DASH_STROKE, Series.DOT_DOT_STROKE*/); + + List SERIES_MARKERS = + Collections.singletonList(Series.NONE_MARKER); + + Color BLUE = new Color(0, 0, 255, 255); + Color GREEN = new Color(0, 128, 0, 255); + Color RED = new Color(255, 0, 0, 255); + Color TURQUOISE = new Color(0, 191, 191, 255); + Color MAGENTA = new Color(191, 0, 191, 255); + Color YELLOW = new Color(191, 191, 0, 255); + Color DARK_GREY = new Color(64, 64, 64, 255); + + List SERIES_COLORS = + Arrays.asList(BLUE, GREEN, RED, TURQUOISE, MAGENTA, YELLOW, DARK_GREY); + + BasicStroke AXIS_TICKMARK = new BasicStroke(.5f); + //new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{3.0f, 0.0f}, 0.0f); + + BasicStroke GRID_LINES = new BasicStroke(.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10.0f, new float[] {1f, 3.0f}, 0.0f); + + //new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10.0f, new float[]{1.0f, 2.0f}, 0.0f); + } + + interface PrinterFriendly { + Color RED = new Color(228, 26, 28, 180); + Color GREEN = new Color(55, 126, 184, 180); + Color BLUE = new Color(77, 175, 74, 180); + Color PURPLE = new Color(152, 78, 163, 180); + Color ORANGE = new Color(255, 127, 0, 180); + Color YELLOW = new Color(255, 255, 51, 180); + Color BROWN = new Color(166, 86, 40, 180); + Color PINK = new Color(247, 129, 191, 180); + List SERIES_COLORS = + Arrays.asList(RED, GREEN, BLUE, PURPLE, ORANGE); + } + + interface ColorBlindFriendly { + Color BLACK = new Color(0, 0, 0, 255); + Color ORANGE = new Color(230, 159, 0, 255); + Color SKY_BLUE = new Color(86, 180, 233, 255); + Color BLUISH_GREEN = new Color(0, 158, 115, 255); + Color YELLOW = new Color(240, 228, 66, 255); + Color BLUE = new Color(0, 114, 178, 255); + Color VERMILLION = new Color(213, 94, 0, 255); + Color REDDISH_PURPLE = new Color(204, 121, 167, 255); + + List SERIES_COLORS = + Arrays.asList(BLACK, ORANGE, SKY_BLUE, BLUISH_GREEN, YELLOW, BLUE, VERMILLION, REDDISH_PURPLE); + } + + interface Colors { + Color BLACK = new Color(0, 0, 0); + Color DARK_GREY = new Color(130, 130, 130); + Color GREY = new Color(210, 210, 210); + Color LIGHT_GREY = new Color(230, 230, 230); + Color WHITE = new Color(255, 255, 255); + } + + interface Fonts { + Font SANS_SERIF_PLAIN_10 = new Font(Font.SANS_SERIF, Font.PLAIN, 10); + Font SANS_SERIF_PLAIN_11 = new Font(Font.SANS_SERIF, Font.PLAIN, 11); + Font SANS_SERIF_PLAIN_12 = new Font(Font.SANS_SERIF, Font.PLAIN, 12); + Font SANS_SERIF_PLAIN_14 = new Font(Font.SANS_SERIF, Font.PLAIN, 14); + Font SANS_SERIF_PLAIN_15 = new Font(Font.SANS_SERIF, Font.PLAIN, 15); + Font SANS_SERIF_BOLD_12 = new Font(Font.SANS_SERIF, Font.BOLD, 12); + Font SANS_SERIF_BOLD_13 = new Font(Font.SANS_SERIF, Font.BOLD, 13); + Font SANS_SERIF_BOLD_14 = new Font(Font.SANS_SERIF, Font.BOLD, 14); + } + + interface Strokes { + BasicStroke TITLE = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); + BasicStroke LEGEND = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[] {3.0f, 0.0f}, 0.0f); + BasicStroke ERROR_BARS = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); + BasicStroke PIE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + } + + interface Series { + + BasicStroke NONE_STROKE = new BasicStroke(); + BasicStroke SOLID_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + BasicStroke DASH_DOT_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{3.0f, 1.0f}, 0.0f); + BasicStroke DASH_DASH_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{3.0f, 3.0f}, 0.0f); + BasicStroke DOT_DOT_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10.0f, new float[]{2.0f}, 0.0f); + + abstract class Marker { + + protected BasicStroke stroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); + + public abstract void paint(Graphics2D g, double xOffset, double yOffset, int markerSize); + + } + + class Circle extends Marker { + + @Override + public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) { + g.setStroke(stroke); + double halfSize = (double) markerSize / 2; + Shape circle = new Ellipse2D.Double(xOffset - halfSize, yOffset - halfSize, markerSize, markerSize); + g.fill(circle); + } + } + + class Cross extends Marker { + + @Override + public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) { + g.setStroke(stroke); + double halfSize = (double) markerSize / 2; + Path2D.Double path = new Path2D.Double(); + path.moveTo(xOffset - halfSize, yOffset - halfSize); + path.lineTo(xOffset + halfSize, yOffset + halfSize); + path.moveTo(xOffset - halfSize, yOffset + halfSize); + path.lineTo(xOffset + halfSize, yOffset - halfSize); + g.draw(path); + } + } + + class Diamond extends Marker { + + @Override + public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) { + g.setStroke(stroke); + double diamondHalfSize = (double) markerSize / 2 * 1.3; + Path2D.Double path = new Path2D.Double(); + path.moveTo(xOffset - diamondHalfSize, yOffset); + path.lineTo(xOffset, yOffset - diamondHalfSize); + path.lineTo(xOffset + diamondHalfSize, yOffset); + path.lineTo(xOffset, yOffset + diamondHalfSize); + path.closePath(); + g.fill(path); + } + } + + class None extends Marker { + + @Override + public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) { + // do nothing! + } + } + + class Square extends Marker { + + @Override + public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) { + g.setStroke(stroke); + double halfSize = (double) markerSize / 2; + Shape square = new Rectangle2D.Double(xOffset - halfSize, yOffset - halfSize, markerSize, markerSize); + g.fill(square); + } + } + + class TriangleDown extends Marker { + + @Override + public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) { + g.setStroke(stroke); + double halfSize = (double) markerSize / 2; + Path2D.Double path = new Path2D.Double(); + path.moveTo(xOffset - halfSize, 1 + yOffset - halfSize); + path.lineTo(xOffset, 1 + yOffset - halfSize + markerSize); + path.lineTo(xOffset - halfSize + markerSize, 1 + yOffset - halfSize); + path.closePath(); + g.fill(path); + } + } + + class TriangleUp extends Marker { + + @Override + public void paint(Graphics2D g, double xOffset, double yOffset, int markerSize) { + g.setStroke(stroke); + double halfSize = (double) markerSize / 2; + Path2D.Double path = new Path2D.Double(); + path.moveTo(xOffset - halfSize, yOffset - halfSize + markerSize - 1); + path.lineTo(xOffset - halfSize + markerSize, yOffset - halfSize + markerSize - 1); + path.lineTo(xOffset, yOffset - halfSize - 1); + path.closePath(); + g.fill(path); + } + } + + Marker NONE_MARKER = new None(); + Marker CIRCLE_MARKER = new Circle(); + Marker CROSS_MARKER = new Cross(); + Marker DIAMOND_MARKER = new Diamond(); + Marker SQUARE_MARKER = new Square(); + Marker TRIANGLE_DOWN_MARKER = new TriangleDown(); + Marker TRIANGLE_UP_MARKER = new TriangleUp(); + } + + Font getBaseFont(); + + Color getChartBackgroundColor(); + + Color getChartFontColor(); + + int getChartPadding(); + + Font getChartTitleFont(); + + boolean isChartTitleVisible(); + + boolean isChartTitleBoxVisible(); + + Color getChartTitleBoxBackgroundColor(); + + Color getChartTitleBoxBorderColor(); + + int getChartTitlePadding(); + + Font getLegendFont(); + + boolean isLegendVisible(); + + Color getLegendBackgroundColor(); + + Color getLegendBorderColor(); + + int getLegendPadding(); + + int getLegendSeriesLineLength(); + + LegendPosition getLegendPosition(); + + boolean isXAxisTitleVisible(); + + boolean isYAxisTitleVisible(); + + Font getAxisTitleFont(); + + boolean isXAxisTicksVisible(); + + boolean isYAxisTicksVisible(); + + Font getAxisTickLabelsFont(); + + int getAxisTickMarkLength(); + + int getAxisTickPadding(); + + Color getAxisTickMarksColor(); + + Stroke getAxisTickMarksStroke(); + + Color getAxisTickLabelsColor(); + + boolean isAxisTicksLineVisible(); + + boolean isAxisTicksMarksVisible(); + + int getAxisTitlePadding(); + + int getXAxisTickMarkSpacingHint(); + + int getYAxisTickMarkSpacingHint(); + + boolean isPlotGridVerticalLinesVisible(); + + boolean isPlotGridHorizontalLinesVisible(); + + Color getPlotBackgroundColor(); + + Color getPlotBorderColor(); + + boolean isPlotBorderVisible(); + + Color getPlotGridLinesColor(); + + Stroke getPlotGridLinesStroke(); + + boolean isPlotTicksMarksVisible(); + + double getPlotContentSize(); + + int getPlotMargin(); + + double getAvailableSpaceFill(); + + boolean isOverlapped(); + + boolean isCircular(); + + Font getPieFont(); + + double getAnnotationDistance(); + + AnnotationType getAnnotationType(); + + boolean isDrawAllAnnotations(); + + double getDonutThickness(); + + boolean isSumVisible(); + + Font getSumFont(); + + int getMarkerSize(); + + Color getErrorBarsColor(); + + boolean isErrorBarsColorSeriesColor(); + + Font getAnnotationFont(); + + List getSeriesMarkers(); + + List getSeriesLines(); + + List getSeriesColors(); + +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/xy/XYChart.java b/chart/src/main/java/org/xbib/graphics/chart/xy/XYChart.java new file mode 100644 index 0000000..5abb9f6 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/xy/XYChart.java @@ -0,0 +1,364 @@ +package org.xbib.graphics.chart.xy; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.axis.Axis; +import org.xbib.graphics.chart.axis.AxisPair; +import org.xbib.graphics.chart.Chart; +import org.xbib.graphics.chart.legend.MarkerLegend; +import org.xbib.graphics.chart.plot.AxesChartPlot; +import org.xbib.graphics.chart.plot.ContentPlot; +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyle; +import org.xbib.graphics.chart.style.SeriesColorMarkerLineStyleCycler; +import org.xbib.graphics.chart.theme.Theme; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import java.time.Instant; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class XYChart extends Chart { + + public XYChart(int width, int height) { + super(width, height, new XYStyler()); + axisPair = new AxisPair<>(this); + plot = new XYPlot<>(this); + legend = new MarkerLegend<>(this); + } + + public XYChart(int width, int height, Theme theme) { + this(width, height); + styler.setTheme(theme); + } + + public XYChart(XYChartBuilder chartBuilder) { + this(chartBuilder.getWidth(), chartBuilder.getHeight(), chartBuilder.getTheme()); + setTitle(chartBuilder.getTitle()); + setXAxisTitle(chartBuilder.getxAxisTitle()); + setYAxisTitle(chartBuilder.getyAxisTitle()); + } + + public XYSeries addSeries(String seriesName, List xData, List yData) { + return addSeries(seriesName, xData, yData, null, getDataType(xData)); + } + + public XYSeries addSeries(String seriesName, List xData, List yData, List errorBars) { + return addSeries(seriesName, xData, yData, errorBars, getDataType(xData)); + } + + public XYSeries addSeries(String seriesName, double[] xData, double[] yData) { + return addSeries(seriesName, xData, yData, null); + } + + public XYSeries addSeries(String seriesName, double[] xData, double[] yData, double[] errorBars) { + return addSeries(seriesName, + listFromDoubleArray(xData), + listFromDoubleArray(yData), + listFromDoubleArray(errorBars), + DataType.Number); + } + + public XYSeries addSeries(String seriesName, int[] xData, int[] yData) { + return addSeries(seriesName, xData, yData, null); + } + + public XYSeries addSeries(String seriesName, int[] xData, int[] yData, int[] errorBars) { + return addSeries(seriesName, listFromIntArray(xData), listFromIntArray(yData), + listFromIntArray(errorBars), DataType.Number); + } + + public XYSeries addSeries(String seriesName, + List xData, + List yData, + List errorBars, + DataType dataType) { + sanityCheck(seriesName, xData, yData, errorBars); + XYSeries series; + if (xData != null) { + if (xData.size() != yData.size()) { + throw new IllegalArgumentException("X and Y-Axis sizes are not the same"); + } + series = new XYSeries(seriesName, xData, yData, errorBars, dataType); + } else { + series = new XYSeries(seriesName, getGeneratedData(yData.size()), yData, errorBars, dataType); + } + seriesMap.put(seriesName, series); + return series; + } + + @Override + public void paint(Graphics2D g, int width, int height) { + setWidth(width); + setHeight(height); + for (XYSeries XYSeries : getSeriesMap().values()) { + XYSeriesRenderStyle XYSeriesRenderStyle = XYSeries.getXySeriesRenderStyle(); + if (XYSeriesRenderStyle == null) { + XYSeries.setXySeriesRenderStyle(getStyler().getDefaultSeriesRenderStyle()); + } + } + setSeriesStyles(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(styler.getChartBackgroundColor()); + Shape rect = new Rectangle2D.Double(0, 0, getWidth(), getHeight()); + g.fill(rect); + axisPair.paint(g); + plot.paint(g); + chartTitle.paint(g); + legend.paint(g); + g.dispose(); + } + + private void setSeriesStyles() { + SeriesColorMarkerLineStyleCycler seriesColorMarkerLineStyleCycler = + new SeriesColorMarkerLineStyleCycler(getStyler().getSeriesColors(), + getStyler().getSeriesMarkers(), + getStyler().getSeriesLines()); + for (XYSeries series : getSeriesMap().values()) { + SeriesColorMarkerLineStyle seriesColorMarkerLineStyle = seriesColorMarkerLineStyleCycler.getNextSeriesColorMarkerLineStyle(); + if (series.getLineStyle() == null) { + series.setLineStyle(seriesColorMarkerLineStyle.getStroke()); + } + if (series.getLineColor() == null) { + series.setLineColor(seriesColorMarkerLineStyle.getColor()); + } + if (series.getFillColor() == null) { + series.setFillColor(seriesColorMarkerLineStyle.getColor()); + } + if (series.getMarker() == null) { + series.setMarker(seriesColorMarkerLineStyle.getMarker()); + } + if (series.getMarkerColor() == null) { + series.setMarkerColor(seriesColorMarkerLineStyle.getColor()); + } + } + } + + private DataType getDataType(List data) { + if (data == null) { + return DataType.Number; + } + DataType axisType; + Iterator itr = data.iterator(); + Object dataPoint = itr.next(); + if (dataPoint instanceof Number) { + axisType = DataType.Number; + } else if (dataPoint instanceof Instant) { + axisType = DataType.Instant; + } else { + throw new IllegalArgumentException("Series data must be either Number or Instant type"); + } + return axisType; + } + + private static class XYPlot extends AxesChartPlot { + + private XYPlot(Chart chart) { + super(chart); + this.contentPlot = new ContentPlotXY<>(chart); + } + } + + private static class ContentPlotXY extends ContentPlot { + + private final ST xystyler; + + private ContentPlotXY(Chart chart) { + super(chart); + xystyler = chart.getStyler(); + } + + @Override + public void doPaint(Graphics2D g) { + double xTickSpace = xystyler.getPlotContentSize() * getBounds().getWidth(); + double xLeftMargin = ((int) getBounds().getWidth() - xTickSpace) / 2.0; + double yTickSpace = xystyler.getPlotContentSize() * getBounds().getHeight(); + double yTopMargin = ((int) getBounds().getHeight() - yTickSpace) / 2.0; + double xMin = chart.getXAxis().getMin(); + double xMax = chart.getXAxis().getMax(); + Line2D.Double line = new Line2D.Double(); + if (xystyler.isXAxisLogarithmic()) { + xMin = Math.log10(xMin); + xMax = Math.log10(xMax); + } + Map map = chart.getSeriesMap(); + for (S series : map.values()) { + if (!series.isEnabled()) { + continue; + } + Axis yAxis = chart.getYAxis(series.getYAxisGroup()); + double yMin = yAxis.getMin(); + double yMax = yAxis.getMax(); + if (xystyler.isYAxisLogarithmic()) { + yMin = Math.log10(yMin); + yMax = Math.log10(yMax); + } + Collection xData = series.getXData(); + Collection yData = series.getYData(); + double previousX = -Double.MAX_VALUE; + double previousY = -Double.MAX_VALUE; + Iterator xItr = xData.iterator(); + Iterator yItr = yData.iterator(); + Iterator ebItr = null; + Collection errorBars = series.getExtraValues(); + if (errorBars != null) { + ebItr = errorBars.iterator(); + } + Path2D.Double path = null; + while (xItr.hasNext()) { + Double x = null; + if (chart.getXAxis().getDataType() == DataType.Number) { + Number number = (Number) xItr.next(); + x = number != null ? number.doubleValue() : null; + } else if (chart.getXAxis().getDataType() == DataType.Instant) { + Instant instant = (Instant) xItr.next(); + x = instant != null ? (double) instant.toEpochMilli() : null; + } + if (xystyler.isXAxisLogarithmic()) { + x = x != null ? Math.log10(x) : null; + } + Number next = yItr.next(); + if (x == null || next == null) { + closePath(g, path, previousX, yTopMargin); + path = null; + previousX = -Double.MAX_VALUE; + previousY = -Double.MAX_VALUE; + continue; + } + double yOrig = next.doubleValue(); + double y; + if (xystyler.isYAxisLogarithmic()) { + y = Math.log10(yOrig); + } else { + y = yOrig; + } + double xTransform = xLeftMargin + ((x - xMin) / (xMax - xMin) * xTickSpace); + double yTransform = getBounds().getHeight() - (yTopMargin + (y - yMin) / (yMax - yMin) * yTickSpace); + if (Math.abs(xMax - xMin) / 5 == 0.0) { + xTransform = getBounds().getWidth() / 2.0; + } + if (Math.abs(yMax - yMin) / 5 == 0.0) { + yTransform = getBounds().getHeight() / 2.0; + } + double xOffset = getBounds().getX() + xTransform; + double yOffset = getBounds().getY() + yTransform; + boolean isSeriesLineOrArea = (XYSeriesRenderStyle.Line == series.getXySeriesRenderStyle()) || + (XYSeriesRenderStyle.Area == series.getXySeriesRenderStyle()); + boolean isSeriesStepLineOrStepArea = XYSeriesRenderStyle.Step == series.getXySeriesRenderStyle() || + XYSeriesRenderStyle.StepArea == series.getXySeriesRenderStyle(); + if (isSeriesLineOrArea || isSeriesStepLineOrStepArea) { + if (series.getLineStyle() != Theme.Series.NONE_STROKE) { + if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) { + g.setColor(series.getLineColor()); + g.setStroke(series.getLineStyle()); + if (isSeriesLineOrArea) { + line.setLine(previousX, previousY, xOffset, yOffset); + g.draw(line); + } else { + if (previousX != xOffset) { + line.setLine(previousX, previousY, xOffset, previousY); + g.draw(line); + } + if (previousY != yOffset) { + line.setLine(xOffset, previousY, xOffset, yOffset); + g.draw(line); + } + } + } + } + } + if (XYSeriesRenderStyle.Area == series.getXySeriesRenderStyle() || + XYSeriesRenderStyle.StepArea == series.getXySeriesRenderStyle()) { + if (previousX != -Double.MAX_VALUE && previousY != -Double.MAX_VALUE) { + g.setColor(series.getFillColor()); + double yBottomOfArea = getBounds().getY() + getBounds().getHeight() - yTopMargin; + if (path == null) { + path = new Path2D.Double(); + path.moveTo(previousX, yBottomOfArea); + path.lineTo(previousX, previousY); + } + if (XYSeriesRenderStyle.Area == series.getXySeriesRenderStyle()) { + path.lineTo(xOffset, yOffset); + } else { + if (previousX != xOffset) { + path.lineTo(xOffset, previousY); + } + if (previousY != yOffset) { + path.lineTo(xOffset, yOffset); + } + } + } + if (xOffset < previousX) { + throw new RuntimeException("X-Data must be in ascending order for Area Charts"); + } + } + previousX = xOffset; + previousY = yOffset; + if (series.getMarker() != null) { + g.setColor(series.getMarkerColor()); + series.getMarker().paint(g, xOffset, yOffset, xystyler.getMarkerSize()); + } + if (errorBars != null) { + double eb = ebItr.next().doubleValue(); + if (xystyler.isErrorBarsColorSeriesColor()) { + g.setColor(series.getLineColor()); + } else { + g.setColor(xystyler.getErrorBarsColor()); + } + g.setStroke(Theme.Strokes.ERROR_BARS); + double topValue; + if (xystyler.isYAxisLogarithmic()) { + topValue = yOrig + eb; + topValue = Math.log10(topValue); + } else { + topValue = y + eb; + } + double topEBTransform = getBounds().getHeight() - (yTopMargin + (topValue - yMin) / (yMax - yMin) * yTickSpace); + double topEBOffset = getBounds().getY() + topEBTransform; + double bottomValue; + if (xystyler.isYAxisLogarithmic()) { + bottomValue = yOrig - eb; + bottomValue = Math.log10(bottomValue); + } else { + bottomValue = y - eb; + } + double bottomEBTransform = getBounds().getHeight() - (yTopMargin + (bottomValue - yMin) / (yMax - yMin) * yTickSpace); + double bottomEBOffset = getBounds().getY() + bottomEBTransform; + line = new Line2D.Double(xOffset, topEBOffset, xOffset, bottomEBOffset); + g.draw(line); + line = new Line2D.Double(xOffset - 3, bottomEBOffset, xOffset + 3, bottomEBOffset); + g.draw(line); + line = new Line2D.Double(xOffset - 3, topEBOffset, xOffset + 3, topEBOffset); + g.draw(line); + } + } + g.setColor(series.getFillColor()); + closePath(g, path, previousX, yTopMargin); + } + } + } + + private void sanityCheck(String seriesName, List xData, List yData, List errorBars) { + if (seriesMap.containsKey(seriesName)) { + throw new IllegalArgumentException("Series name >" + seriesName + "< has already been used. Use unique names for each series"); + } + if (yData == null) { + throw new IllegalArgumentException("Y-Axis data cannot be null"); + } + if (yData.size() == 0) { + throw new IllegalArgumentException("Y-Axis data cannot be empty"); + } + if (xData != null && xData.size() == 0) { + throw new IllegalArgumentException("X-Axis data cannot be empty"); + } + if (errorBars != null && errorBars.size() != yData.size()) { + throw new IllegalArgumentException("Error bars and Y-Axis sizes are not the same"); + } + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/xy/XYChartBuilder.java b/chart/src/main/java/org/xbib/graphics/chart/xy/XYChartBuilder.java new file mode 100644 index 0000000..ab48cfe --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/xy/XYChartBuilder.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.chart.xy; + +import org.xbib.graphics.chart.ChartBuilder; + +public class XYChartBuilder extends ChartBuilder { + + private String xAxisTitle; + private String yAxisTitle; + + public XYChartBuilder() { + this.xAxisTitle = ""; + this.yAxisTitle = ""; + } + + public XYChartBuilder xAxisTitle(String xAxisTitle) { + this.xAxisTitle = xAxisTitle; + return this; + } + + public XYChartBuilder yAxisTitle(String yAxisTitle) { + this.yAxisTitle = yAxisTitle; + return this; + } + + public String getxAxisTitle() { + return xAxisTitle; + } + + public String getyAxisTitle() { + return yAxisTitle; + } + + @Override + public XYChart build() { + return new XYChart(this); + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/xy/XYSeries.java b/chart/src/main/java/org/xbib/graphics/chart/xy/XYSeries.java new file mode 100644 index 0000000..676f317 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/xy/XYSeries.java @@ -0,0 +1,38 @@ +package org.xbib.graphics.chart.xy; + +import org.xbib.graphics.chart.axis.DataType; +import org.xbib.graphics.chart.series.AxesChartSeriesNumericalNoErrorBars; +import org.xbib.graphics.chart.legend.LegendRenderType; + +import java.util.List; + +/** + * A Series containing X and Y data to be plotted on a Chart + */ +public class XYSeries extends AxesChartSeriesNumericalNoErrorBars { + + private XYSeriesRenderStyle xySeriesRenderStyle; + + public XYSeries(String name, + List xData, + List yData, + List errorBars, + DataType dataType) { + super(name, xData, yData, errorBars, dataType); + } + + public XYSeriesRenderStyle getXySeriesRenderStyle() { + return xySeriesRenderStyle; + } + + public void setXySeriesRenderStyle(XYSeriesRenderStyle xySeriesRenderStyle) { + this.xySeriesRenderStyle = xySeriesRenderStyle; + } + + @Override + public LegendRenderType getLegendRenderType() { + return xySeriesRenderStyle.getLegendRenderType(); + } + + +} \ No newline at end of file diff --git a/chart/src/main/java/org/xbib/graphics/chart/xy/XYSeriesRenderStyle.java b/chart/src/main/java/org/xbib/graphics/chart/xy/XYSeriesRenderStyle.java new file mode 100644 index 0000000..b65bc3b --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/xy/XYSeriesRenderStyle.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.chart.xy; + +import org.xbib.graphics.chart.legend.LegendRenderable; +import org.xbib.graphics.chart.legend.LegendRenderType; + +public enum XYSeriesRenderStyle implements LegendRenderable { + + Line(LegendRenderType.Line), + + Area(LegendRenderType.Line), + + Step(LegendRenderType.Line), + + StepArea(LegendRenderType.Line), + + Scatter(LegendRenderType.Scatter); + + private final LegendRenderType legendRenderType; + + XYSeriesRenderStyle(LegendRenderType legendRenderType) { + this.legendRenderType = legendRenderType; + } + + @Override + public LegendRenderType getLegendRenderType() { + return legendRenderType; + } +} diff --git a/chart/src/main/java/org/xbib/graphics/chart/xy/XYStyler.java b/chart/src/main/java/org/xbib/graphics/chart/xy/XYStyler.java new file mode 100644 index 0000000..e109ee3 --- /dev/null +++ b/chart/src/main/java/org/xbib/graphics/chart/xy/XYStyler.java @@ -0,0 +1,49 @@ +package org.xbib.graphics.chart.xy; + +import org.xbib.graphics.chart.style.AxesChartStyler; +import org.xbib.graphics.chart.theme.Theme; + +public class XYStyler extends AxesChartStyler { + + private XYSeriesRenderStyle xySeriesRenderStyle; + + public XYStyler() { + this.setAllStyles(); + super.setAllStyles(); + } + + @Override + protected void setAllStyles() { + xySeriesRenderStyle = XYSeriesRenderStyle.Line; // set default to line + } + + public XYSeriesRenderStyle getDefaultSeriesRenderStyle() { + return xySeriesRenderStyle; + } + + /** + * Sets the default series render style for the chart (line, scatter, area, etc.) You can override the series + * render + * style individually on each Series object. + * + * @param style style + */ + public void setDefaultSeriesRenderStyle(XYSeriesRenderStyle style) { + this.xySeriesRenderStyle = style; + } + + public Theme getTheme() { + return theme; + } + + /** + * Set the theme the styler should use + * + * @param theme theme + */ + protected void setTheme(Theme theme) { + this.theme = theme; + super.setAllStyles(); + } + +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/HistogramTest.java b/chart/src/test/java/org/xbib/graphics/chart/HistogramTest.java new file mode 100755 index 0000000..1aa3eb1 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/HistogramTest.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.chart; + +import java.util.Arrays; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import org.junit.jupiter.api.Test; + +public class HistogramTest { + + @Test + public void test1() { + Histogram histogram = new Histogram(Arrays.asList(1, 2, 3, 4, 5, 6), 2, 0, 4); + assertThat(histogram.getMax(), equalTo(4.0)); + assertThat(histogram.getMin(), equalTo(0.0)); + assertThat(histogram.getNumBins(), equalTo(2)); + assertThat(histogram.getyAxisData().get(0) + histogram.getyAxisData().get(1), equalTo(4.0)); + } + + @Test + public void testNegativeValues() { + Histogram histogram = new Histogram(Arrays.asList(-1, -2, -3, -4, -5, -6), 3); + assertThat(histogram.getMax(), equalTo(-1.0)); + assertThat(histogram.getMin(), equalTo(-6.0)); + assertThat(histogram.getNumBins(), equalTo(3)); + } + +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/MatlabTest.java b/chart/src/test/java/org/xbib/graphics/chart/MatlabTest.java new file mode 100644 index 0000000..c321f9c --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/MatlabTest.java @@ -0,0 +1,67 @@ +package org.xbib.graphics.chart; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.theme.MatlabTheme; +import org.xbib.graphics.chart.xy.XYChart; +import org.xbib.graphics.chart.xy.XYChartBuilder; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +public class MatlabTest { + + @Test + public void testMatlabInstants() throws IOException { + XYChart chart = new XYChartBuilder().width(800).height(600) + .theme(new MatlabTheme()) + .title("Matlab Theme") + .xAxisTitle("X") + .yAxisTitle("Y") + .build(); + chart.getStyler().setPlotGridLinesVisible(false); + chart.getStyler().setXAxisTickMarkSpacingHint(100); + chart.getStyler().setDatePattern("HH:mm:ss"); + List xData = new ArrayList<>(); + List y1Data = new ArrayList<>(); + List y2Data = new ArrayList<>(); + + xData.add(Instant.parse("2012-08-01T00:00:00Z")); + y1Data.add(120d); + y2Data.add(15d); + + xData.add(Instant.parse("2012-08-01T01:00:00Z")); + y1Data.add(165d); + y2Data.add(15d); + + xData.add(Instant.parse("2012-08-01T02:00:00Z")); + y1Data.add(210d); + y2Data.add(20d); + + xData.add(Instant.parse("2012-08-01T03:00:00Z")); + y1Data.add(400d); + y2Data.add(30d); + + xData.add(Instant.parse("2012-08-01T04:00:00Z")); + y1Data.add(800d); + y2Data.add(100d); + + xData.add(Instant.parse("2012-08-01T05:00:00Z")); + y1Data.add(2000d); + y2Data.add(120d); + + xData.add(Instant.parse("2012-08-01T06:00:00Z")); + y1Data.add(3000d); + y2Data.add(150d); + + chart.addSeries("downloads", xData, y1Data); + chart.addSeries("price", xData, y2Data); + + chart.write(Files.newOutputStream(Paths.get("build/matlab.svg")), + VectorGraphicsFormat.SVG ); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/demo/AreaChartTest.java b/chart/src/test/java/org/xbib/graphics/chart/demo/AreaChartTest.java new file mode 100644 index 0000000..6c4a32a --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/demo/AreaChartTest.java @@ -0,0 +1,146 @@ +package org.xbib.graphics.chart.demo; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.axis.TextAlignment; +import org.xbib.graphics.chart.theme.Theme; +import org.xbib.graphics.chart.legend.LegendPosition; +import org.xbib.graphics.chart.xy.XYChart; +import org.xbib.graphics.chart.xy.XYChartBuilder; +import org.xbib.graphics.chart.xy.XYSeries; +import org.xbib.graphics.chart.xy.XYSeriesRenderStyle; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class AreaChartTest { + + @Test + public void testAreaChart1() throws IOException { + XYChart chart = + new XYChartBuilder() + .width(800) + .height(600) + .title(getClass().getSimpleName()) + .xAxisTitle("X") + .yAxisTitle("Y") + .build(); + + chart.getStyler().setLegendPosition(LegendPosition.InsideNE); + chart.getStyler().setAxisTitlesVisible(false); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area); + chart.getStyler().setSeriesColors(Theme.PrinterFriendly.SERIES_COLORS); + + chart.addSeries("a", new double[] {0, 3, 5, 7, 9}, new double[] {-3, 5, 9, 6, 5}); + chart.addSeries("b", new double[] {0, 2, 4, 6, 9}, new double[] {-1, 6, 4, 0, 4}); + chart.addSeries("c", new double[] {0, 1, 3, 8, 9}, new double[] {-2, -1, 1, 0, 1}); + + chart.write(Files.newOutputStream(Paths.get("build/areachart1.svg")), + VectorGraphicsFormat.SVG); + } + + @Test + public void testAreaChart2() throws IOException { + XYChart chart = new XYChartBuilder() + .width(800) + .height(600) + .title(getClass().getSimpleName()) + .xAxisTitle("X") + .yAxisTitle("Y") + .build(); + + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Area); + chart.getStyler().setLegendPosition(LegendPosition.InsideNW); + List xData = new ArrayList<>(); + List yData = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + xData.add(i); + yData.add(i * i); + } + xData.add(5); + yData.add(null); + for (int i = 6; i < 10; i++) { + xData.add(i); + yData.add(i * i); + } + xData.add(10); + yData.add(null); + xData.add(11); + yData.add(100); + xData.add(12); + yData.add(90); + chart.addSeries("a", xData, yData); + + chart.write( Files.newOutputStream(Paths.get("build/areachart2.svg")), + VectorGraphicsFormat.SVG); + + } + + @Test + public void testAreaChart3() throws IOException { + XYChart chart = new XYChartBuilder().width(800).height(600).title(getClass().getSimpleName()).xAxisTitle("Age").yAxisTitle("Amount").build(); + + chart.getStyler().setLegendPosition(LegendPosition.InsideNW); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line); + chart.getStyler().setYAxisLabelAlignment(TextAlignment.Right); + chart.getStyler().setYAxisDecimalPattern("$ #,###.##"); + chart.getStyler().setPlotMargin(0); + chart.getStyler().setPlotContentSize(.95); + + double[] xAges = new double[] { 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 }; + + double[] yLiability = new double[] { 672234, 691729, 711789, 732431, 753671, 775528, 798018, 821160, 844974, 869478, 907735, 887139, 865486, + 843023, 819621, 795398, 770426, 744749, 719011, 693176, 667342, 641609, 616078, 590846, 565385, 540002, 514620, 489380, 465149, 441817, + 419513, 398465, 377991, 358784, 340920, 323724, 308114, 293097, 279356, 267008, 254873 }; + + double[] yPercentile75th = new double[] { 800000, 878736, 945583, 1004209, 1083964, 1156332, 1248041, 1340801, 1440138, 1550005, 1647728, + 1705046, 1705032, 1710672, 1700847, 1683418, 1686522, 1674901, 1680456, 1679164, 1668514, 1672860, 1673988, 1646597, 1641842, 1653758, + 1636317, 1620725, 1589985, 1586451, 1559507, 1544234, 1529700, 1507496, 1474907, 1422169, 1415079, 1346929, 1311689, 1256114, 1221034 }; + + double[] yPercentile50th = new double[] { 800000, 835286, 873456, 927048, 969305, 1030749, 1101102, 1171396, 1246486, 1329076, 1424666, 1424173, + 1421853, 1397093, 1381882, 1364562, 1360050, 1336885, 1340431, 1312217, 1288274, 1271615, 1262682, 1237287, 1211335, 1191953, 1159689, + 1117412, 1078875, 1021020, 974933, 910189, 869154, 798476, 744934, 674501, 609237, 524516, 442234, 343960, 257025 }; + + double[] yPercentile25th = new double[] { 800000, 791439, 809744, 837020, 871166, 914836, 958257, 1002955, 1054094, 1118934, 1194071, 1185041, + 1175401, 1156578, 1132121, 1094879, 1066202, 1054411, 1028619, 987730, 944977, 914929, 880687, 809330, 783318, 739751, 696201, 638242, + 565197, 496959, 421280, 358113, 276518, 195571, 109514, 13876, 29, 0, 0, 0, 0 }; + + XYSeries seriesLiability = chart.addSeries("Liability", xAges, yLiability); + seriesLiability.setXySeriesRenderStyle(XYSeriesRenderStyle.Area); + seriesLiability.setMarker(Theme.Series.NONE_MARKER); + + chart.addSeries("75th Percentile", xAges, yPercentile75th); + chart.addSeries("50th Percentile", xAges, yPercentile50th); + chart.addSeries("25th Percentile", xAges, yPercentile25th); + + chart.write(Files.newOutputStream(Paths.get("build/areachart3.svg")), + VectorGraphicsFormat.SVG); + } + + @Test + public void testAreaChart4() throws IOException { + XYChart chart = + new XYChartBuilder() + .width(800) + .height(600) + .title(getClass().getSimpleName()) + .xAxisTitle("X") + .yAxisTitle("Y") + .build(); + + chart.getStyler().setLegendPosition(LegendPosition.InsideNE); + chart.getStyler().setAxisTitlesVisible(false); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.StepArea); + + chart.addSeries("a", new double[] {0, 3, 5, 7, 9}, new double[] {-3, 5, 9, 6, 5}); + chart.addSeries("b", new double[] {0, 2, 4, 6, 9}, new double[] {-1, 6, 4, 0, 4}); + chart.addSeries("c", new double[] {0, 1, 3, 8, 9}, new double[] {-2, -1, 1, 0, 1}); + + chart.write(Files.newOutputStream(Paths.get("build/areachart4.svg")), + VectorGraphicsFormat.SVG); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/demo/BarChartTest.java b/chart/src/test/java/org/xbib/graphics/chart/demo/BarChartTest.java new file mode 100644 index 0000000..d6aa88b --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/demo/BarChartTest.java @@ -0,0 +1,134 @@ +package org.xbib.graphics.chart.demo; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.Histogram; +import org.xbib.graphics.chart.theme.GGPlot2Theme; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.category.CategoryChart; +import org.xbib.graphics.chart.category.CategoryChartBuilder; +import org.xbib.graphics.chart.category.CategorySeries; +import org.xbib.graphics.chart.category.CategorySeriesRenderStyle; +import org.xbib.graphics.chart.legend.LegendPosition; + +import java.awt.Color; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +public class BarChartTest { + + @Test + public void testBarChart1() throws IOException { + CategoryChart chart = + new CategoryChartBuilder() + .width(800) + .height(600) + .title("Score Histogram") + .xAxisTitle("Score") + .yAxisTitle("Number") + .build(); + + chart.getStyler().setLegendPosition(LegendPosition.InsideNW); + chart.getStyler().setPlotGridLinesVisible(false); + + chart.addSeries("test 1", Arrays.asList(0, 1, 2, 3, 4), Arrays.asList(4, 5, 9, 6, 5)); + + chart.write(Files.newOutputStream(Paths.get("build/barchart1.svg")), + VectorGraphicsFormat.SVG); + } + + @Test + public void testBarChart6() throws IOException { + CategoryChart chart = new CategoryChartBuilder().width(800).height(600).title("Score Histogram").xAxisTitle("Mean").yAxisTitle("Count").build(); + + chart.getStyler().setLegendPosition(LegendPosition.InsideNW); + chart.getStyler().setAvailableSpaceFill(.96); + chart.getStyler().setOverlapped(true); + + Histogram histogram1 = new Histogram(getGaussianData(10000), 20, -20, 20); + Histogram histogram2 = new Histogram(getGaussianData(5000), 20, -20, 20); + chart.addSeries("histogram 1", histogram1.getxAxisData(), histogram1.getyAxisData()); + chart.addSeries("histogram 2", histogram2.getxAxisData(), histogram2.getyAxisData()); + + chart.write(Files.newOutputStream(Paths.get("build/barchart6.svg")), + VectorGraphicsFormat.SVG); + } + + private List getGaussianData(int count) { + List data = new ArrayList<>(count); + Random r = new Random(); + for (int i = 0; i < count; i++) { + data.add(r.nextGaussian() * 10); + } + return data; + } + + @Test + public void testGGPlot1() throws IOException { + CategoryChart chart = + new CategoryChartBuilder() + .width(800) + .height(600) + .title("Temperature vs. Color") + .xAxisTitle("Color") + .yAxisTitle("Temperature") + .theme(new GGPlot2Theme()) + .build(); + + chart.getStyler().setPlotGridVerticalLinesVisible(false); + + chart.addSeries("fish", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(-40, 30, 20, 60, 60)); + chart.addSeries("worms", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(50, 10, -20, 40, 60)); + chart.addSeries("birds", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(13, 22, -23, -34, 37)); + chart.addSeries("ants", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(50, 57, -14, -20, 31)); + chart.addSeries("slugs", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(-2, 29, 49, -16, -43)); + + chart.write(Files.newOutputStream(Paths.get("build/ggplot1.svg")), + VectorGraphicsFormat.SVG); + } + + + @Test + public void testGGPlot2() throws IOException { + CategoryChart chart = + new CategoryChartBuilder() + .width(800) + .height(600) + .title("Temperature vs. Color") + .xAxisTitle("Color") + .yAxisTitle("Temperature") + .theme(new GGPlot2Theme()) + .build(); + + chart.getStyler().setPlotGridVerticalLinesVisible(false); + List categorySeries = new ArrayList<>(); + + categorySeries.add(chart.addSeries("fish", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(-40, 30, 20, 60, 60))); + categorySeries.add(chart.addSeries("worms", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(50, 10, -20, 40, 60))); + categorySeries.add(chart.addSeries("birds", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(13, 22, -23, -34, 37))); + categorySeries.add(chart.addSeries("ants", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(50, 57, -14, -20, 31))); + categorySeries.add(chart.addSeries("slugs", Arrays.asList("Blue", "Red", "Green", "Yellow", "Orange"), + Arrays.asList(-2, 29, 49, -16, -43))); + + for (CategorySeries series : categorySeries) { + series.setCategorySeriesRenderStyle(CategorySeriesRenderStyle.SteppedBar); + series.setFillColor(new Color(0, 0, 0, 0)); + } + chart.write(Files.newOutputStream(Paths.get("build/ggplot2.svg")), + VectorGraphicsFormat.SVG); + } + +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/demo/BubbleChartTest.java b/chart/src/test/java/org/xbib/graphics/chart/demo/BubbleChartTest.java new file mode 100644 index 0000000..1b5e583 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/demo/BubbleChartTest.java @@ -0,0 +1,34 @@ +package org.xbib.graphics.chart.demo; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.bubble.BubbleChart; +import org.xbib.graphics.chart.bubble.BubbleChartBuilder; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +public class BubbleChartTest { + + @Test + public void testBubble1() throws IOException { + BubbleChart chart = new BubbleChartBuilder().width(800).height(600).title("BubbleChart01").xAxisTitle("X").yAxisTitle("Y").build(); + + List xData = Arrays.asList(1.5, 2.6, 3.3, 4.9, 5.5, 6.3, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + List yData = Arrays.asList(10.0, 4.0, 7.0, 7.7, 7.0, 5.5, 10.0, 4.0, 7.0, 1.0, 7.0, 9.0); + List bubbleData = Arrays.asList(17.0, 40.0, 50.0, 51.0, 26.0, 20.0, 66.0, 35.0, 80.0, 27.0, 29.0, 44.0); + + List xData2 = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 1.5, 2.6, 3.3, 4.9, 5.5, 6.3); + List yData2 = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 10.0, 8.5, 4.0, 1.0, 4.7, 9.0); + List bubbleData2 = Arrays.asList(37.0, 35.0, 80.0, 27.0, 29.0, 44.0, 57.0, 40.0, 50.0, 33.0, 26.0, 20.0); + + chart.addSeries("A", xData, yData, bubbleData); + chart.addSeries("B", xData2, yData2, bubbleData2); + + chart.write(Files.newOutputStream(Paths.get("build/bubblechart1.svg")), + VectorGraphicsFormat.SVG); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/demo/PieChartTest.java b/chart/src/test/java/org/xbib/graphics/chart/demo/PieChartTest.java new file mode 100644 index 0000000..4d12750 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/demo/PieChartTest.java @@ -0,0 +1,41 @@ +package org.xbib.graphics.chart.demo; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.pie.PieChart; +import org.xbib.graphics.chart.pie.PieChartBuilder; + +import java.awt.Color; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +public class PieChartTest { + + @Test + public void testPieChart2() throws IOException { + PieChart chart = new PieChartBuilder() + .width(800) + .height(600) + .title(getClass().getSimpleName()) + .build(); + List sliceColors = Arrays.asList(new Color(224, 68, 14), + new Color(230, 105, 62), + new Color(236, 143, 110), + new Color(243, 180, 159), + new Color(246, 199, 182)); + chart.getStyler().setSeriesColors(sliceColors); + + chart.addSeries("Gold", 24); + chart.addSeries("Silver", 21); + chart.addSeries("Platinum", 39); + chart.addSeries("Copper", 17); + chart.addSeries("Zinc", 40); + + chart.write(Files.newOutputStream(Paths.get("build/piechart2.svg")), + VectorGraphicsFormat.SVG); + + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/demo/QuickChartTest.java b/chart/src/test/java/org/xbib/graphics/chart/demo/QuickChartTest.java new file mode 100644 index 0000000..e7eb2e0 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/demo/QuickChartTest.java @@ -0,0 +1,23 @@ +package org.xbib.graphics.chart.demo; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.QuickChart; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.xy.XYChart; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class QuickChartTest { + + @Test + public void testQuick1() throws IOException { + double[] xData = new double[] { 0.0, 1.0, 2.0 }; + double[] yData = new double[] { 2.0, 1.0, 0.0 }; + XYChart chart = QuickChart.getChart("Sample Chart", + "X", "Y", "y(x)", xData, yData); + chart.write(Files.newOutputStream(Paths.get("build/quickchart1.pdf")), + VectorGraphicsFormat.PDF); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/demo/ScatterChartTest.java b/chart/src/test/java/org/xbib/graphics/chart/demo/ScatterChartTest.java new file mode 100644 index 0000000..435270a --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/demo/ScatterChartTest.java @@ -0,0 +1,107 @@ +package org.xbib.graphics.chart.demo; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.theme.Theme; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.axis.YAxisPosition; +import org.xbib.graphics.chart.legend.LegendPosition; +import org.xbib.graphics.chart.xy.XYChart; +import org.xbib.graphics.chart.xy.XYChartBuilder; +import org.xbib.graphics.chart.xy.XYSeries; +import org.xbib.graphics.chart.xy.XYSeriesRenderStyle; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +public class ScatterChartTest { + + @Test + public void testScatterChart0() throws IOException { + XYChart chart = new XYChartBuilder().width(600).height(500).title("Gaussian Blobs").xAxisTitle("X").yAxisTitle("Y").build(); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter); + chart.getStyler().setChartTitleVisible(false); + chart.getStyler().setLegendPosition(LegendPosition.InsideSW); + chart.getStyler().setMarkerSize(16); + chart.addSeries("Gaussian Blob 1", getGaussian(1000, 1, 10), getGaussian(1000, 1, 10)); + XYSeries series = chart.addSeries("Gaussian Blob 2", getGaussian(1000, 1, 10), getGaussian(1000, 0, 5)); + series.setMarker(Theme.Series.DIAMOND_MARKER); + chart.write(Files.newOutputStream(Paths.get("build/scatterchart0.svg")), + VectorGraphicsFormat.SVG); + } + + private static final Random random = new Random(); + + private static List getGaussian(int number, double mean, double std) { + List seriesData = new LinkedList<>(); + for (int i = 0; i < number; i++) { + seriesData.add(mean + std * random.nextGaussian()); + } + return seriesData; + } + + @Test + public void testScatterChart1() throws IOException { + XYChart chart = new XYChartBuilder().width(800).height(600).build(); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter); + chart.getStyler().setChartTitleVisible(false); + chart.getStyler().setLegendVisible(false); + chart.getStyler().setMarkerSize(16); + chart.getStyler().setYAxisGroupPosition(0, YAxisPosition.Right); + List xData = new LinkedList<>(); + List yData = new LinkedList<>(); + Random random = new Random(); + int size = 1000; + for (int i = 0; i < size; i++) { + xData.add(random.nextGaussian() / 1000); + yData.add(-1000000 + random.nextGaussian()); + } + XYSeries series = chart.addSeries("Gaussian Blob", xData, yData); + series.setMarker(Theme.Series.CROSS_MARKER); + + chart.write(Files.newOutputStream(Paths.get("build/scatterchart1.svg")), + VectorGraphicsFormat.SVG); + } + + @Test + public void testScatterChart2() throws IOException { + XYChart chart = new XYChartBuilder().width(800).height(600).title("Logarithmic Data").build(); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter); + chart.getStyler().setXAxisLogarithmic(true); + chart.getStyler().setLegendPosition(LegendPosition.InsideN); + List xData = new ArrayList<>(); + List yData = new ArrayList<>(); + Random random = new Random(); + int size = 400; + for (int i = 0; i < size; i++) { + double nextRandom = random.nextDouble(); + xData.add(Math.pow(10, nextRandom * 10)); + yData.add(1000000000.0 + nextRandom); + } + chart.addSeries("logarithmic data", xData, yData); + + chart.write(Files.newOutputStream(Paths.get("build/scatterchart2.svg")), + VectorGraphicsFormat.SVG); + } + + @Test + public void testScatterChart3() throws IOException { + XYChart chart = + new XYChartBuilder() + .width(800) + .height(600) + .title("Single Point") + .xAxisTitle("X") + .yAxisTitle("Y") + .build(); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter); + chart.addSeries("single point (1,1)", new double[] {1}, new double[] {1}); + + chart.write(Files.newOutputStream(Paths.get("build/scatterchart3.svg")), + VectorGraphicsFormat.SVG); + } +} \ No newline at end of file diff --git a/chart/src/test/java/org/xbib/graphics/chart/demo/StickChartTest.java b/chart/src/test/java/org/xbib/graphics/chart/demo/StickChartTest.java new file mode 100644 index 0000000..1db9b43 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/demo/StickChartTest.java @@ -0,0 +1,34 @@ +package org.xbib.graphics.chart.demo; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.category.CategoryChart; +import org.xbib.graphics.chart.category.CategoryChartBuilder; +import org.xbib.graphics.chart.category.CategorySeriesRenderStyle; +import org.xbib.graphics.chart.legend.LegendPosition; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class StickChartTest { + + @Test + public void testStick1() throws IOException { + CategoryChart chart = new CategoryChartBuilder().width(800).height(600).title("Stick").build(); + chart.getStyler().setChartTitleVisible(true); + chart.getStyler().setLegendPosition(LegendPosition.InsideNW); + chart.getStyler().setDefaultSeriesRenderStyle(CategorySeriesRenderStyle.Stick); + List xData = new ArrayList<>(); + List yData = new ArrayList<>(); + for (int i = -3; i <= 24; i++) { + xData.add(i); + yData.add(i); + } + chart.addSeries("data", xData, yData); + chart.write(Files.newOutputStream(Paths.get("build/stickchart1.svg")), + VectorGraphicsFormat.SVG); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/GraphicsStateTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/GraphicsStateTest.java new file mode 100644 index 0000000..4ac6ae8 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/GraphicsStateTest.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.chart.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import org.junit.jupiter.api.Test; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; + +public class GraphicsStateTest { + + @Test + public void testInitialStateIsEqualToGraphics2D() { + BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) image.getGraphics(); + GraphicsState state = new GraphicsState(); + + assertEquals(state.getBackground(), g2d.getBackground()); + assertEquals(state.getColor(), g2d.getColor()); + assertEquals(state.getClip(), g2d.getClip()); + assertEquals(state.getComposite(), g2d.getComposite()); + assertEquals(state.getFont(), g2d.getFont()); + assertEquals(state.getPaint(), g2d.getPaint()); + assertEquals(state.getStroke(), g2d.getStroke()); + assertEquals(state.getTransform(), g2d.getTransform()); + } + + @Test + public void testEquals() { + GraphicsState state1 = new GraphicsState(); + state1.setBackground(Color.WHITE); + state1.setColor(Color.BLACK); + state1.setClip(new Rectangle2D.Double(0, 0, 10, 10)); + + GraphicsState state2 = new GraphicsState(); + state2.setBackground(Color.WHITE); + state2.setColor(Color.BLACK); + state2.setClip(new Rectangle2D.Double(0, 0, 10, 10)); + + assertEquals(state1, state2); + + state2.setTransform(AffineTransform.getTranslateInstance(5, 5)); + + assertFalse(state1.equals(state2)); + } + + @Test + public void testClone() throws CloneNotSupportedException { + GraphicsState state = new GraphicsState(); + state.setBackground(Color.BLUE); + state.setColor(Color.GREEN); + state.setClip(new Rectangle2D.Double(2, 3, 4, 2)); + + GraphicsState clone = (GraphicsState) state.clone(); + + assertNotSame(state, clone); + assertEquals(state, clone); + } +} + diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/TestUtils.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/TestUtils.java new file mode 100644 index 0000000..f8208f0 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/TestUtils.java @@ -0,0 +1,267 @@ +package org.xbib.graphics.chart.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class TestUtils { + + protected TestUtils() { + throw new UnsupportedOperationException(); + } + + public static void assertTemplateEquals(Template expected, Template actual) { + Iterator itExpected = expected.iterator(); + Iterator itActual = actual.iterator(); + while (itExpected.hasNext() && itActual.hasNext()) { + Object lineExpected = itExpected.next(); + Object lineActual = itActual.next(); + + if (lineExpected == null) { + continue; + } + assertTrue(lineActual instanceof String, + String.format("Line is of type %s, expected String.", lineActual.getClass())); + + if (lineExpected instanceof String) { + assertEquals(lineExpected, lineActual); + } else if (lineExpected instanceof Pattern) { + Pattern expectedPattern = (Pattern) lineExpected; + Matcher matcher = expectedPattern.matcher((String) lineActual); + assertTrue(matcher.matches(), + String.format("Line didn't match pattern.\nExpected: \"%s\"\nActual: \"%s\"", matcher.pattern(), lineActual)); + } + } + assertEquals(expected.size(), actual.size(), "Wrong number of lines in template."); + } + + private static List parseXML(String xmlString) { + XMLFragment frag; + List fragments = new LinkedList(); + int startPos = 0; + while ((frag = XMLFragment.parse(xmlString, startPos)) != null) { + fragments.add(frag); + startPos = frag.matchEnd; + } + return fragments; + } + + public static void assertXMLEquals(String expected, String actual) { + List expectedFrags = parseXML(expected); + List actualFrags = parseXML(actual); + + Iterator itExpected = expectedFrags.iterator(); + Iterator itActual = actualFrags.iterator(); + while (itExpected.hasNext() && itActual.hasNext()) { + XMLFragment expectedFrag = itExpected.next(); + XMLFragment actualFrag = itActual.next(); + assertEquals(expectedFrag, actualFrag); + } + + assertEquals(expectedFrags.size(), actualFrags.size()); + } + + @SuppressWarnings("serial") + public static class Template extends LinkedList { + public Template(Object[] lines) { + Collections.addAll(this, lines); + } + + public Template(Template[] templates) { + for (Template template : templates) { + addAll(template); + } + } + } + + public static class XMLFragment { + + private static final Pattern CDATA = Pattern.compile("\\s*"); + + private static final Pattern COMMENT = Pattern.compile("\\s*"); + + private static final Pattern TAG_BEGIN = Pattern.compile("\\s*<(/|\\?|!)?\\s*([^\\s>/\\?]+)"); + + private static final Pattern TAG_END = Pattern.compile("\\s*(/|\\?)?>"); + + private static final Pattern TAG_ATTRIBUTE = Pattern.compile("\\s*([^\\s>=]+)=(\"[^\"]*\"|'[^']*')"); + + private static final Pattern DOCTYPE_PART = Pattern.compile("\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)"); + + public final String name; + + public final FragmentType type; + + public final Map attributes; + + public final int matchStart; + + public final int matchEnd; + + public XMLFragment(String name, FragmentType type, Map attributes, + int matchStart, int matchEnd) { + this.name = name; + this.type = type; + this.attributes = Collections.unmodifiableMap( + new TreeMap(attributes)); + this.matchStart = matchStart; + this.matchEnd = matchEnd; + } + + public static XMLFragment parse(String xmlString, int matchStart) { + Map attrs = new IdentityHashMap(); + + Matcher cdataMatch = CDATA.matcher(xmlString); + cdataMatch.region(matchStart, xmlString.length()); + if (cdataMatch.lookingAt()) { + attrs.put("value", cdataMatch.group(1)); + return new XMLFragment("", FragmentType.CDATA, attrs, matchStart, cdataMatch.end()); + } + + Matcher commentMatch = COMMENT.matcher(xmlString); + commentMatch.region(matchStart, xmlString.length()); + if (commentMatch.lookingAt()) { + attrs.put("value", commentMatch.group(1).trim()); + return new XMLFragment("", FragmentType.COMMENT, attrs, matchStart, commentMatch.end()); + } + + Matcher beginMatch = TAG_BEGIN.matcher(xmlString); + beginMatch.region(matchStart, xmlString.length()); + if (!beginMatch.lookingAt()) { + return null; + } + int matchEndPrev = beginMatch.end(); + + String modifiers = beginMatch.group(1); + String name = beginMatch.group(2); + boolean endTag = "/".equals(modifiers); + boolean declarationStart = "?".equals(modifiers); + boolean doctype = "!".equals(modifiers) && "DOCTYPE".equals(name); + + if (doctype) { + int partNo = 0; + while (true) { + Matcher attrMatch = DOCTYPE_PART.matcher(xmlString); + attrMatch.region(matchEndPrev, xmlString.length()); + if (!attrMatch.lookingAt()) { + break; + } + matchEndPrev = attrMatch.end(); + + String partValue = attrMatch.group(1); + if (partValue.startsWith("\"") || partValue.startsWith("'")) { + partValue = partValue.substring(1, partValue.length() - 1); + } + + String partId = String.format("doctype %02d", partNo++); + attrs.put(partId, partValue); + } + } else { + while (true) { + Matcher attrMatch = TAG_ATTRIBUTE.matcher(xmlString); + attrMatch.region(matchEndPrev, xmlString.length()); + if (!attrMatch.lookingAt()) { + break; + } + matchEndPrev = attrMatch.end(); + + String attrName = attrMatch.group(1); + String attrValue = attrMatch.group(2); + attrValue = attrValue.substring(1, attrValue.length() - 1); + attrs.put(attrName, attrValue); + } + } + + Matcher endMatch = TAG_END.matcher(xmlString); + endMatch.region(matchEndPrev, xmlString.length()); + if (!endMatch.lookingAt()) { + throw new AssertionError(String.format("No tag end found: %s", xmlString.substring(0, matchEndPrev))); + } + matchEndPrev = endMatch.end(); + + modifiers = endMatch.group(1); + boolean emptyElement = "/".equals(modifiers); + boolean declarationEnd = "?".equals(modifiers); + + FragmentType type = FragmentType.START_TAG; + if (endTag) { + type = FragmentType.END_TAG; + } else if (emptyElement) { + type = FragmentType.EMPTY_ELEMENT; + } else if (declarationStart && declarationEnd) { + type = FragmentType.DECLARATION; + } else if (doctype) { + type = FragmentType.DOCTYPE; + } + + return new XMLFragment(name, type, attrs, matchStart, matchEndPrev); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof XMLFragment)) { + return false; + } + XMLFragment frag = (XMLFragment) o; + if (!type.equals(frag.type) || !name.equals(frag.name)) { + return false; + } + Iterator> itThis = attributes.entrySet().iterator(); + Iterator> itFrag = frag.attributes.entrySet().iterator(); + while (itThis.hasNext() && itFrag.hasNext()) { + Map.Entry attrThis = itThis.next(); + Map.Entry attrFrag = itFrag.next(); + if (!attrThis.getKey().equals(attrFrag.getKey()) || + !attrThis.getValue().equals(attrFrag.getValue())) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return type.hashCode() ^ attributes.hashCode(); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder("<"); + if (FragmentType.END_TAG.equals(type)) { + s.append("/"); + } else if (FragmentType.DECLARATION.equals(type)) { + s.append("?"); + } + + if (FragmentType.DOCTYPE.equals(type)) { + s.append("!").append(name); + for (String partValue : attributes.values()) { + s.append(" ").append(partValue); + } + } else { + s.append(name); + for (Map.Entry attr : attributes.entrySet()) { + s.append(" ").append(attr.getKey()).append("=\"").append(attr.getValue()).append("\""); + } + } + if (FragmentType.DECLARATION.equals(type)) { + s.append("?"); + } + s.append(">"); + return s.toString(); + } + + public enum FragmentType { + START_TAG, END_TAG, EMPTY_ELEMENT, CDATA, + DECLARATION, DOCTYPE, COMMENT + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/TestUtilsTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/TestUtilsTest.java new file mode 100644 index 0000000..9047d58 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/TestUtilsTest.java @@ -0,0 +1,191 @@ +package org.xbib.graphics.chart.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.TestUtils.XMLFragment; + + +public class TestUtilsTest { + + @Test + public void testParseXmlStartTag() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + + xmlString = "<" + xmlTagName + ">"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.START_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "< " + xmlTagName + " >"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.START_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlEndTag() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.END_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.END_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlEmptyElement() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + + xmlString = "<" + xmlTagName + "/>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.EMPTY_ELEMENT, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "< " + xmlTagName + " />"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.EMPTY_ELEMENT, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlCDATA() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.CDATA, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "foo bar]]>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.CDATA, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlDeclaration() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("xml", frag.name); + assertEquals(XMLFragment.FragmentType.DECLARATION, frag.type); + assertEquals("1.0", frag.attributes.get("version")); + assertEquals("UTF-8", frag.attributes.get("encoding")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlDoctype() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(XMLFragment.FragmentType.DOCTYPE, frag.type); + assertEquals("html", frag.attributes.get("doctype 00")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(XMLFragment.FragmentType.DOCTYPE, frag.type); + assertEquals("svg", frag.attributes.get("doctype 00")); + assertEquals("PUBLIC", frag.attributes.get("doctype 01")); + assertEquals("-//W3C//DTD SVG 1.1 Tiny//EN", frag.attributes.get("doctype 02")); + assertEquals("http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd", frag.attributes.get("doctype 03")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlComment() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.COMMENT, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.COMMENT, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXMLAttributesTag() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + + xmlString = "<" + xmlTagName + " foo='bar'>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals("bar", frag.attributes.get("foo")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "<" + xmlTagName + " foo=\"bar\">"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals("bar", frag.attributes.get("foo")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "<" + xmlTagName + " foo=\"bar\" baz='qux'>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals("bar", frag.attributes.get("foo")); + assertEquals("qux", frag.attributes.get("baz")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/VectorGraphics2DTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/VectorGraphics2DTest.java new file mode 100644 index 0000000..93cfd97 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/VectorGraphics2DTest.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.chart.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.CreateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Iterator; + +public class VectorGraphics2DTest { + + @Test + public void testEmptyVectorGraphics2DStartsWithCreateCommand() { + VectorGraphics2D g = new VectorGraphics2D(); + Iterable> commands = g.getCommands(); + Iterator> commandIterator = commands.iterator(); + assertTrue(commandIterator.hasNext()); + Command firstCommand = commandIterator.next(); + assertTrue(firstCommand instanceof CreateCommand); + assertEquals(g, ((CreateCommand) firstCommand).getValue()); + } + + @Test + public void testCreateEmitsCreateCommand() { + VectorGraphics2D g = new VectorGraphics2D(); + Iterable> gCommands = g.getCommands(); + Iterator> gCommandIterator = gCommands.iterator(); + CreateCommand gCreateCommand = (CreateCommand) gCommandIterator.next(); + + VectorGraphics2D g2 = (VectorGraphics2D) g.create(); + CreateCommand g2CreateCommand = null; + for (Command g2Command : g2.getCommands()) { + if (g2Command instanceof CreateCommand) { + g2CreateCommand = (CreateCommand) g2Command; + } + } + assertNotNull(g2CreateCommand); + assertNotEquals(gCreateCommand, g2CreateCommand); + assertEquals(g2, g2CreateCommand.getValue()); + } + + @Test + public void testDisposeCommandEmitted() { + VectorGraphics2D g = new VectorGraphics2D(); + g.setColor(Color.RED); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setColor(Color.BLUE); + g2.dispose(); + Iterable> commands = g.getCommands(); + Command lastCommand = null; + for (Command command : commands) { + lastCommand = command; + } + assertTrue(lastCommand instanceof DisposeCommand); + assertEquals(Color.BLUE, ((DisposeCommand) lastCommand).getValue().getColor()); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/eps/EPSProcessorTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/eps/EPSProcessorTest.java new file mode 100644 index 0000000..ee39aa3 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/eps/EPSProcessorTest.java @@ -0,0 +1,70 @@ +package org.xbib.graphics.chart.io.vector.eps; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.Document; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +import static org.xbib.graphics.chart.io.vector.TestUtils.Template; +import static org.xbib.graphics.chart.io.vector.TestUtils.assertTemplateEquals; + +public class EPSProcessorTest { + private static final String EOL = "\n"; + private static final Object[] HEADER = { + "%!PS-Adobe-3.0 EPSF-3.0", + "%%BoundingBox: 0 28 57 114", + "%%HiResBoundingBox: 0.0 28.34645669291339 56.69291338582678 113.38582677165356", + "%%LanguageLevel: 3", + "%%Pages: 1", + "%%EndComments", + "%%Page: 1 1", + "/M /moveto load def", + "/L /lineto load def", + "/C /curveto load def", + "/Z /closepath load def", + "/RL /rlineto load def", + "/rgb /setrgbcolor load def", + "/rect { /height exch def /width exch def /y exch def /x exch def x y M width 0 RL 0 height RL width neg 0 RL } bind def", + "/ellipse { /endangle exch def /startangle exch def /ry exch def /rx exch def /y exch def /x exch def /savematrix matrix currentmatrix def x y translate rx ry scale 0 0 1 startangle endangle arcn savematrix setmatrix } bind def", + "/imgdict { /datastream exch def /hasdata exch def /decodeScale exch def /bits exch def /bands exch def /imgheight exch def /imgwidth exch def << /ImageType 1 /Width imgwidth /Height imgheight /BitsPerComponent bits /Decode [bands {0 decodeScale} repeat] ", + "/ImageMatrix [imgwidth 0 0 imgheight 0 0] hasdata { /DataSource datastream } if >> } bind def", + "/latinize { /fontName exch def /fontNameNew exch def fontName findfont 0 dict copy begin /Encoding ISOLatin1Encoding def fontNameNew /FontName def currentdict end dup /FID undef fontNameNew exch definefont pop } bind def", + Pattern.compile("/\\S+?Lat /\\S+ latinize /\\S+?Lat 12.0 selectfont"), + "gsave", + "clipsave", + "/DeviceRGB setcolorspace", + "0 85.03937007874016 translate", + "2.834645669291339 -2.834645669291339 scale", + "/basematrix matrix currentmatrix def", + "%%EOF" + }; + private static final PageSize PAGE_SIZE = new PageSize(0.0, 10.0, 20.0, 30.0); + + private final EPSProcessor processor = new EPSProcessor(); + private final List> commands = new LinkedList>(); + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + private String process(Command... commands) throws IOException { + Collections.addAll(this.commands, commands); + Document processed = processor.process(this.commands, PAGE_SIZE); + processed.write(bytes); + return bytes.toString(StandardCharsets.ISO_8859_1); + } + + @Test + public void envelopeForEmptyDocument() throws IOException { + String result = process(); + Template actual = new Template(result.split(EOL)); + Template expected = new Template(HEADER); + assertTemplateEquals(expected, actual); + } + +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/AbsoluteToRelativeTransformsFilterTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/AbsoluteToRelativeTransformsFilterTest.java new file mode 100644 index 0000000..8b69958 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/AbsoluteToRelativeTransformsFilterTest.java @@ -0,0 +1,91 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.CreateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DisposeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.TransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.TranslateCommand; + +import java.awt.geom.AffineTransform; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class AbsoluteToRelativeTransformsFilterTest { + + @Test + public void testAbsoluteAndRelativeTransformsIdentical() { + AffineTransform absoluteTransform = new AffineTransform(); + absoluteTransform.rotate(42.0); + absoluteTransform.translate(4.0, 2.0); + List> commands = wrapCommands( + new SetTransformCommand(absoluteTransform) + ); + + AbsoluteToRelativeTransformsFilter filter = new AbsoluteToRelativeTransformsFilter(commands); + + filter.next(); + AffineTransform relativeTransform = ((TransformCommand) filter.next()).getValue(); + assertThat(relativeTransform, is(absoluteTransform)); + } + + @Test + public void testTranslateCorrect() { + AffineTransform absoluteTransform = new AffineTransform(); + absoluteTransform.scale(2.0, 2.0); + absoluteTransform.translate(4.2, 4.2); // (8.4, 8.4) + List> commands = wrapCommands( + new TranslateCommand(4.0, 2.0), + new SetTransformCommand(absoluteTransform) + ); + + AbsoluteToRelativeTransformsFilter filter = new AbsoluteToRelativeTransformsFilter(commands); + + TransformCommand transformCommand = null; + while (filter.hasNext()) { + Command filteredCommand = filter.next(); + if (filteredCommand instanceof TransformCommand) { + transformCommand = (TransformCommand) filteredCommand; + } + } + AffineTransform relativeTransform = transformCommand.getValue(); + assertThat(relativeTransform.getTranslateX(), is(4.4)); + assertThat(relativeTransform.getTranslateY(), is(6.4)); + } + + @Test + public void testRelativeTransformAfterDispose() { + AffineTransform absoluteTransform = new AffineTransform(); + absoluteTransform.rotate(42.0); + absoluteTransform.translate(4.0, 2.0); + List> commands = wrapCommands( + new CreateCommand(null), + new TransformCommand(absoluteTransform), + new DisposeCommand(null), + new SetTransformCommand(absoluteTransform) + ); + + AbsoluteToRelativeTransformsFilter filter = new AbsoluteToRelativeTransformsFilter(commands); + TransformCommand lastTransformCommand = null; + for (Command filteredCommand : filter) { + if (filteredCommand instanceof TransformCommand) { + lastTransformCommand = (TransformCommand) filteredCommand; + } + } + assertThat(lastTransformCommand.getValue(), is(absoluteTransform)); + } + + private List> wrapCommands(Command... commands) { + List> commandList = new ArrayList>(commands.length + 2); + commandList.add(new CreateCommand(null)); + commandList.addAll(Arrays.asList(commands)); + commandList.add(new DisposeCommand(null)); + return commandList; + } +} + diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FillPaintedShapeAsImageFilterTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FillPaintedShapeAsImageFilterTest.java new file mode 100644 index 0000000..72ba55f --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FillPaintedShapeAsImageFilterTest.java @@ -0,0 +1,54 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawImageCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.FillShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.RotateCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetPaintCommand; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.geom.Rectangle2D; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import static org.hamcrest.CoreMatchers.any; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class FillPaintedShapeAsImageFilterTest { + + @Test + public void testFillShapeReplacedWithDrawImage() { + List> commands = new LinkedList>(); + commands.add(new SetPaintCommand(new GradientPaint(0.0f, 0.0f, Color.BLACK, 100.0f, 100.0f, Color.WHITE))); + commands.add(new RotateCommand(10.0, 4.0, 2.0)); + commands.add(new FillShapeCommand(new Rectangle2D.Double(10.0, 10.0, 100.0, 100.0))); + + FillPaintedShapeAsImageFilter filter = new FillPaintedShapeAsImageFilter(commands); + + assertThat(filter, hasItem(any(DrawImageCommand.class))); + assertThat(filter, not(hasItem(any(FillShapeCommand.class)))); + } + + @Test + public void testFillShapeNotReplacedWithoutPaintCommand() { + List> commands = new LinkedList>(); + commands.add(new RotateCommand(10.0, 4.0, 2.0)); + commands.add(new FillShapeCommand(new Rectangle2D.Double(10.0, 10.0, 100.0, 100.0))); + + FillPaintedShapeAsImageFilter filter = new FillPaintedShapeAsImageFilter(commands); + + Iterator> filterIterator = filter.iterator(); + for (Command command : commands) { + assertEquals(command, filterIterator.next()); + } + assertFalse(filterIterator.hasNext()); + } +} + diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FilterTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FilterTest.java new file mode 100644 index 0000000..fbb954e --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/FilterTest.java @@ -0,0 +1,98 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetColorCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetStrokeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + + +public class FilterTest { + + @Test + public void filterNone() { + List> stream = new LinkedList>(); + stream.add(new SetColorCommand(Color.BLACK)); + stream.add(new SetStrokeCommand(new BasicStroke(1f))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + stream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + + Iterator> unfiltered = stream.iterator(); + + Filter filtered = new Filter(stream) { + @Override + protected List> filter(Command command) { + return Arrays.>asList(command); + } + }; + + while (filtered.hasNext() || unfiltered.hasNext()) { + Command expected = unfiltered.next(); + Command result = filtered.next(); + assertEquals(expected, result); + } + } + + @Test + public void filterAll() { + List> stream = new LinkedList>(); + stream.add(new SetColorCommand(Color.BLACK)); + stream.add(new SetStrokeCommand(new BasicStroke(1f))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + stream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + + Iterator> unfiltered = stream.iterator(); + + Filter filtered = new Filter(stream) { + @Override + protected List> filter(Command command) { + return null; + } + }; + assertTrue(unfiltered.hasNext()); + assertFalse(filtered.hasNext()); + } + + @Test + public void duplicate() { + List> stream = new LinkedList>(); + stream.add(new SetColorCommand(Color.BLACK)); + stream.add(new SetStrokeCommand(new BasicStroke(1f))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + stream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + + Iterator> unfiltered = stream.iterator(); + + Filter filtered = new Filter(stream) { + @Override + protected List> filter(Command command) { + return Arrays.asList(command, command); + } + }; + + while (filtered.hasNext() || unfiltered.hasNext()) { + Command expected = unfiltered.next(); + Command result1 = filtered.next(); + Command result2 = filtered.next(); + assertEquals(expected, result1); + assertEquals(expected, result2); + } + } +} + diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/GroupingFilterTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/GroupingFilterTest.java new file mode 100644 index 0000000..0f1281e --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/intermediate/filters/GroupingFilterTest.java @@ -0,0 +1,56 @@ +package org.xbib.graphics.chart.io.vector.intermediate.filters; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Group; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetColorCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetStrokeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.SetTransformCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.StateCommand; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class GroupingFilterTest { + + @Test + public void filtered() { + List> resultStream = new LinkedList>(); + resultStream.add(new SetColorCommand(Color.BLACK)); + resultStream.add(new SetStrokeCommand(new BasicStroke(1f))); + resultStream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + resultStream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + resultStream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + List> expectedStream = new LinkedList>(); + Iterator> resultCloneIterator = resultStream.iterator(); + Group group1 = new Group(); + group1.add(resultCloneIterator.next()); + group1.add(resultCloneIterator.next()); + expectedStream.add(group1); + expectedStream.add(resultCloneIterator.next()); + Group group2 = new Group(); + group2.add(resultCloneIterator.next()); + expectedStream.add(group2); + expectedStream.add(resultCloneIterator.next()); + Iterator> expectedIterator = expectedStream.iterator(); + Filter resultIterator = new GroupingFilter(resultStream) { + @Override + protected boolean isGrouped(Command command) { + return command instanceof StateCommand; + } + }; + while (resultIterator.hasNext() || expectedIterator.hasNext()) { + Command result = resultIterator.next(); + Command expected = expectedIterator.next(); + assertEquals(expected, result); + } + } +} + diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/pdf/PDFProcessorTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/pdf/PDFProcessorTest.java new file mode 100644 index 0000000..85fd19f --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/pdf/PDFProcessorTest.java @@ -0,0 +1,114 @@ +package org.xbib.graphics.chart.io.vector.pdf; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.Document; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +import static org.xbib.graphics.chart.io.vector.TestUtils.Template; +import static org.xbib.graphics.chart.io.vector.TestUtils.assertTemplateEquals; + +public class PDFProcessorTest { + private static final String EOL = "\n"; + private static final String HEADER = "%PDF-1.4"; + private static final String FOOTER = "%%EOF"; + private static final PageSize PAGE_SIZE = new PageSize(0.0, 10.0, 20.0, 30.0); + + private final PDFProcessor processor = new PDFProcessor(); + private final List> commands = new LinkedList>(); + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + private String process(Command... commands) throws IOException { + Collections.addAll(this.commands, commands); + Document processed = processor.process(this.commands, PAGE_SIZE); + processed.write(bytes); + return bytes.toString(StandardCharsets.ISO_8859_1); + } + + @Test + public void envelopeForEmptyDocument() throws IOException { + String result = process(); + Template actual = new Template(result.split(EOL)); + Template expected = new Template(new Object[]{ + HEADER, + "1 0 obj", + "<<", + "/Type /Catalog", + "/Pages 2 0 R", + ">>", + "endobj", + "2 0 obj", + "<<", + "/Type /Pages", + "/Kids [3 0 R]", + "/Count 1", + ">>", + "endobj", + "3 0 obj", + "<<", + "/Type /Page", + "/Parent 2 0 R", + "/MediaBox [0 28.34645669291339 56.69291338582678 85.03937007874016]", + "/Contents 4 0 R", + "/Resources 6 0 R", + ">>", + "endobj", + "4 0 obj", + "<<", + "/Length 5 0 R", + ">>", + "stream", + "q", + "1 1 1 rg 1 1 1 RG", + "2.834645669291339 0 0 -2.834645669291339 0 85.03937007874016 cm", + "/Fnt0 12.0 Tf", + "Q", + "endstream", + "endobj", + "5 0 obj", + "100", + "endobj", + "6 0 obj", + "<<", + "/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]", + "/Font <<", + "/Fnt0 <<", + "/Type /Font", + "/Subtype /TrueType", + "/Encoding /WinAnsiEncoding", + Pattern.compile("/BaseFont /\\S+"), + ">>", + ">>", + ">>", + "endobj", + "xref", + "0 7", + "0000000000 65535 f ", + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + "trailer", + "<<", + "/Size 7", + "/Root 1 0 R", + ">>", + "startxref", + Pattern.compile("[1-9]\\d*"), + FOOTER + }); + + assertTemplateEquals(expected, actual); + } +} + diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/svg/SVGProcessorTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/svg/SVGProcessorTest.java new file mode 100644 index 0000000..309ec35 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/svg/SVGProcessorTest.java @@ -0,0 +1,70 @@ +package org.xbib.graphics.chart.io.vector.svg; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.chart.io.vector.Document; +import org.xbib.graphics.chart.io.vector.intermediate.commands.Command; +import org.xbib.graphics.chart.io.vector.intermediate.commands.DrawShapeCommand; +import org.xbib.graphics.chart.io.vector.intermediate.commands.FillShapeCommand; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import java.awt.geom.Rectangle2D; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import static org.xbib.graphics.chart.io.vector.TestUtils.assertXMLEquals; + +public class SVGProcessorTest { + private static final String EOL = "\n"; + private static final String HEADER = + "" + EOL + + "" + EOL + + "" + EOL; + private static final String FOOTER = ""; + private static final PageSize PAGE_SIZE = new PageSize(0.0, 10.0, 20.0, 30.0); + + private final SVGProcessor processor = new SVGProcessor(); + private final List> commands = new LinkedList>(); + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + private String process(Command... commands) throws IOException { + Collections.addAll(this.commands, commands); + Document processed = processor.process(this.commands, PAGE_SIZE); + processed.write(bytes); + return bytes.toString(StandardCharsets.UTF_8); + } + + @Test + public void envelopeForEmptyDocument() throws Exception { + String result = process(); + String expected = HEADER.replaceAll(">$", "/>"); + assertXMLEquals(expected, result); + } + + @Test + public void drawShapeBlack() throws Exception { + String result = process( + new DrawShapeCommand(new Rectangle2D.Double(1, 2, 3, 4)) + ); + String expected = + HEADER + EOL + + " " + EOL + + FOOTER; + assertXMLEquals(expected, result); + } + + @Test + public void fillShapeBlack() throws Exception { + String result = process( + new FillShapeCommand(new Rectangle2D.Double(1, 2, 3, 4)) + ); + String expected = + HEADER + EOL + + " " + EOL + + FOOTER; + assertXMLEquals(expected, result); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/ASCII85EncodeStreamTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/ASCII85EncodeStreamTest.java new file mode 100644 index 0000000..3cbff85 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/ASCII85EncodeStreamTest.java @@ -0,0 +1,53 @@ +package org.xbib.graphics.chart.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class ASCII85EncodeStreamTest { + + private static void assertEncodedString(String expected, String input) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(input.getBytes()); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + OutputStream encodeStream = new ASCII85EncodeStream(outStream); + byte[] buffer = new byte[1024]; + for (int count = inputStream.read(buffer); count >= 0; count = inputStream.read(buffer)) { + encodeStream.write(buffer, 0, count); + } + encodeStream.close(); + String encoded = outStream.toString(StandardCharsets.ISO_8859_1); + assertEquals(expected, encoded); + } + + @Test + public void testEncoding() throws IOException { + String input = + "Man is distinguished, not only by his reason, but by this singular passion " + + "from other animals, which is a lust of the mind, that by a perseverance of " + + "delight in the continued and indefatigable generation of knowledge, exceeds " + + "the short vehemence of any carnal pleasure."; + + String expected = + "9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKFCj@.4Gp$d7F!,L7@<6@)/0JDEF@3BB/F*&OCAfu2/AKYi(DIb:@FD,*)" + + "+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIal(DIduD.RTpAKYo'+CT/5+Cei#" + + "DII?(E,9)oF*2M7/c~>"; + + assertEncodedString(expected, input); + } + + @Test + public void testPadding() throws IOException { + assertEncodedString("/c~>", "."); + } + + @Test + public void testEmpty() throws IOException { + assertEncodedString("~>", ""); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/Base64EncodeStreamTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/Base64EncodeStreamTest.java new file mode 100644 index 0000000..4c1ac19 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/Base64EncodeStreamTest.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.chart.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class Base64EncodeStreamTest { + + private static void assertEncodedString(String expected, String input) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(input.getBytes()); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + OutputStream encodeStream = new Base64EncodeStream(outStream); + byte[] buffer = new byte[1024]; + for (int count = inputStream.read(buffer); count >= 0; count = inputStream.read(buffer)) { + encodeStream.write(buffer, 0, count); + } + encodeStream.close(); + String encoded = outStream.toString(StandardCharsets.ISO_8859_1); + assertEquals(expected, encoded); + } + + @Test + public void testEncoding() throws IOException { + String input = + "Man is distinguished, not only by his reason, but by this singular passion " + + "from other animals, which is a lust of the mind, that by a perseverance of " + + "delight in the continued and indefatigable generation of knowledge, exceeds " + + "the short vehemence of any carnal pleasure."; + + String expected = + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz" + + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg" + + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo" + + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="; + + assertEncodedString(expected, input); + } + + @Test + public void testPadding() throws IOException { + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3VyZS4=", "any carnal pleasure."); + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3VyZQ==", "any carnal pleasure"); + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3Vy", "any carnal pleasur"); + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3U=", "any carnal pleasu"); + assertEncodedString("YW55IGNhcm5hbCBwbGVhcw==", "any carnal pleas"); + + assertEncodedString("cGxlYXN1cmUu", "pleasure."); + assertEncodedString("bGVhc3VyZS4=", "leasure."); + assertEncodedString("ZWFzdXJlLg==", "easure."); + assertEncodedString("YXN1cmUu", "asure."); + assertEncodedString("c3VyZS4=", "sure."); + } + + @Test + public void testEmpty() throws IOException { + assertEncodedString("", ""); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/DataUtilsTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/DataUtilsTest.java new file mode 100644 index 0000000..8591fb8 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/DataUtilsTest.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.chart.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +public class DataUtilsTest { + + @Test + public void stripTrailingSpaces() { + String result = DataUtils.stripTrailing(" foo bar! ", " "); + String expected = " foo bar!"; + assertEquals(expected, result); + } + + @Test + public void stripTrailingSpacesInMultilineString() { + String result = DataUtils.stripTrailing(" foo bar! \n ", " "); + String expected = " foo bar! \n"; + assertEquals(expected, result); + } + + @Test + public void stripComplexSubstring() { + String result = DataUtils.stripTrailing("+bar foo+bar+bar+bar", "+bar"); + String expected = "+bar foo"; + assertEquals(expected, result); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/GraphicsUtilsTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/GraphicsUtilsTest.java new file mode 100644 index 0000000..c1dd08c --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/util/GraphicsUtilsTest.java @@ -0,0 +1,142 @@ +package org.xbib.graphics.chart.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import java.awt.Font; +import java.awt.Image; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Toolkit; +import java.awt.geom.Arc2D; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.QuadCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.FilteredImageSource; +import java.awt.image.RGBImageFilter; +import java.lang.reflect.InvocationTargetException; + +/** + * On Linux, the package msttcorefonts need to be installed + */ +public class GraphicsUtilsTest { + private static final double DELTA = 1e-15; + + private static void assertShapeEquals(Shape expected, Shape actual) { + if ((expected instanceof Line2D) && (actual instanceof Line2D)) { + assertEquals(((Line2D) expected).getP1(), ((Line2D) actual).getP1()); + assertEquals(((Line2D) expected).getP2(), ((Line2D) actual).getP2()); + } else if ((expected instanceof Polygon) && (actual instanceof Polygon)) { + int n = ((Polygon) actual).npoints; + assertEquals(((Polygon) expected).npoints, n); + if (n > 0) { + assertArrayEquals(((Polygon) expected).xpoints, ((Polygon) actual).xpoints); + assertArrayEquals(((Polygon) expected).ypoints, ((Polygon) actual).ypoints); + } + } else if ((expected instanceof QuadCurve2D) && (actual instanceof QuadCurve2D)) { + assertEquals(((QuadCurve2D) expected).getP1(), ((QuadCurve2D) actual).getP1()); + assertEquals(((QuadCurve2D) expected).getCtrlPt(), ((QuadCurve2D) actual).getCtrlPt()); + assertEquals(((QuadCurve2D) expected).getP2(), ((QuadCurve2D) actual).getP2()); + } else if ((expected instanceof CubicCurve2D) && (actual instanceof CubicCurve2D)) { + assertEquals(((CubicCurve2D) expected).getP1(), ((CubicCurve2D) actual).getP1()); + assertEquals(((CubicCurve2D) expected).getCtrlP1(), ((CubicCurve2D) actual).getCtrlP1()); + assertEquals(((CubicCurve2D) expected).getCtrlP2(), ((CubicCurve2D) actual).getCtrlP2()); + assertEquals(((CubicCurve2D) expected).getP2(), ((CubicCurve2D) actual).getP2()); + } else if ((expected instanceof Path2D) && (actual instanceof Path2D)) { + PathIterator itExpected = expected.getPathIterator(null); + PathIterator itActual = actual.getPathIterator(null); + double[] segmentExpected = new double[6]; + double[] segmentActual = new double[6]; + for (; !itExpected.isDone() || !itActual.isDone(); itExpected.next(), itActual.next()) { + assertEquals(itExpected.getWindingRule(), itActual.getWindingRule()); + itExpected.currentSegment(segmentExpected); + itActual.currentSegment(segmentActual); + assertArrayEquals(segmentExpected, segmentActual, DELTA); + } + } else { + assertEquals(expected, actual); + } + } + + @Test + public void testToBufferedImage() { + Image[] images = { + new BufferedImage(320, 240, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB), + Toolkit.getDefaultToolkit().createImage(new FilteredImageSource( + new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB).getSource(), + new RGBImageFilter() { + @Override + public int filterRGB(int x, int y, int rgb) { + return rgb & 0xff; + } + } + )) + }; + + for (Image image : images) { + BufferedImage bimage = GraphicsUtils.toBufferedImage(image); + assertNotNull(bimage); + assertEquals(BufferedImage.class, bimage.getClass()); + assertEquals(image.getWidth(null), bimage.getWidth()); + assertEquals(image.getHeight(null), bimage.getHeight()); + } + } + + @Test + public void testHasAlpha() { + Image image; + image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_ARGB); + assertTrue(GraphicsUtils.hasAlpha(image)); + image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB); + assertFalse(GraphicsUtils.hasAlpha(image)); + } + + @Test + public void testPhysicalFont() { + Font font = new Font("Monospaced", Font.PLAIN, 12); + assertNotSame(font, GraphicsUtils.getPhysicalFont(font)); + } + + @Test + public void testCloneShape() + throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + Class[] shapeClasses = { + Line2D.Float.class, + Line2D.Double.class, + Rectangle.class, + Rectangle2D.Float.class, + Rectangle2D.Double.class, + RoundRectangle2D.Float.class, + RoundRectangle2D.Double.class, + Ellipse2D.Float.class, + Ellipse2D.Double.class, + Arc2D.Float.class, + Arc2D.Double.class, + Polygon.class, + CubicCurve2D.Float.class, + CubicCurve2D.Double.class, + QuadCurve2D.Float.class, + QuadCurve2D.Double.class, + Path2D.Float.class, + Path2D.Double.class + }; + for (Class shapeClass : shapeClasses) { + Shape shape = (Shape) shapeClass.getDeclaredConstructor().newInstance(); + Shape clone = GraphicsUtils.clone(shape); + assertNotNull(clone); + assertShapeEquals(shape, clone); + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/AbstractTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/AbstractTest.java new file mode 100644 index 0000000..75c86a8 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/AbstractTest.java @@ -0,0 +1,170 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import org.xbib.graphics.chart.io.vector.EPSGraphics2D; +import org.xbib.graphics.chart.io.vector.PDFGraphics2D; +import org.xbib.graphics.chart.io.vector.SVGGraphics2D; +import org.xbib.graphics.chart.io.vector.util.PageSize; + +import javax.imageio.ImageIO; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public abstract class AbstractTest { + + private final PageSize pageSize; + + private final BufferedImage reference; + + private final EPSGraphics2D epsGraphics; + + private final PDFGraphics2D pdfGraphics; + + private final SVGGraphics2D svgGraphics; + + public AbstractTest() { + int width = 150; + int height = 150; + pageSize = new PageSize(0.0, 0.0, width, height); + epsGraphics = new EPSGraphics2D(0, 0, width, height); + draw(epsGraphics); + pdfGraphics = new PDFGraphics2D(0, 0, width, height); + draw(pdfGraphics); + svgGraphics = new SVGGraphics2D(0, 0, width, height); + draw(svgGraphics); + reference = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D referenceGraphics = reference.createGraphics(); + referenceGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + referenceGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + referenceGraphics.setBackground(new Color(1f, 1f, 1f, 0f)); + referenceGraphics.clearRect(0, 0, reference.getWidth(), reference.getHeight()); + referenceGraphics.setColor(Color.BLACK); + draw(referenceGraphics); + try { + File referenceImage = File.createTempFile(getClass().getName() + ".reference", ".png"); + referenceImage.deleteOnExit(); + ImageIO.write(reference, "png", referenceImage); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public abstract void draw(Graphics2D g); + + public PageSize getPageSize() { + return pageSize; + } + + public BufferedImage getReference() { + return reference; + } + + public InputStream getEPS() { + return new ByteArrayInputStream(epsGraphics.getBytes()); + } + + public InputStream getPDF() { + return new ByteArrayInputStream(pdfGraphics.getBytes()); + } + + public InputStream getSVG() { + return new ByteArrayInputStream(svgGraphics.getBytes()); + } + + /*public BufferedImage getRasterizedEPS() throws GhostscriptException, IOException { + if (rasterizedEPS != null) { + return rasterizedEPS; + } + + File epsInputFile = File.createTempFile(getClass().getName() + ".testEPS", ".eps"); + epsInputFile.deleteOnExit(); + OutputStream epsInput = new FileOutputStream(epsInputFile); + epsInput.write(epsGraphics.getBytes()); + epsInput.close(); + + File pngOutputFile = File.createTempFile(getClass().getName() + ".testEPS", "png"); + pngOutputFile.deleteOnExit(); + Ghostscript gs = Ghostscript.getInstance(); + gs.initialize(new String[]{ + "-dBATCH", + "-dQUIET", + "-dNOPAUSE", + "-dSAFER", + String.format("-g%dx%d", Math.round(getPageSize().width), Math.round(getPageSize().height)), + "-dGraphicsAlphaBits=4", + "-dAlignToPixels=0", + "-dEPSCrop", + "-dPSFitPage", + "-sDEVICE=pngalpha", + "-sOutputFile=" + pngOutputFile.toString(), + epsInputFile.toString() + }); + gs.exit(); + rasterizedEPS = ImageIO.read(pngOutputFile); + return rasterizedEPS; + }*/ + + /*public BufferedImage getRasterizedPDF() throws GhostscriptException, IOException { + if (rasterizedPDF != null) { + return rasterizedPDF; + } + + File pdfInputFile = File.createTempFile(getClass().getName() + ".testPDF", ".pdf"); + pdfInputFile.deleteOnExit(); + OutputStream pdfInput = new FileOutputStream(pdfInputFile); + pdfInput.write(pdfGraphics.getBytes()); + pdfInput.close(); + + File pngOutputFile = File.createTempFile(getClass().getName() + ".testPDF", "png"); + pngOutputFile.deleteOnExit(); + Ghostscript gs = Ghostscript.getInstance(); + gs.initialize(new String[]{ + "-dBATCH", + "-dQUIET", + "-dNOPAUSE", + "-dSAFER", + String.format("-g%dx%d", Math.round(getPageSize().width), Math.round(getPageSize().height)), + "-dGraphicsAlphaBits=4", + // TODO: More robust settings for gs? DPI value is estimated. + "-r25", + "-dAlignToPixels=0", + "-dPDFFitPage", + "-sDEVICE=pngalpha", + "-sOutputFile=" + pngOutputFile.toString(), + pdfInputFile.toString() + }); + gs.exit(); + rasterizedPDF = ImageIO.read(pngOutputFile); + return rasterizedPDF; + }*/ + + /*public BufferedImage getRasterizedSVG() throws TranscoderException { + if (rasterizedSVG != null) { + return rasterizedSVG; + } + + rasterizedSVG = new BufferedImage( + (int) Math.round(getPageSize().width), (int) Math.round(getPageSize().height), + BufferedImage.TYPE_INT_ARGB); + + ImageTranscoder transcoder = new ImageTranscoder() { + @Override + public BufferedImage createImage(int width, int height) { + return rasterizedSVG; + } + + @Override + public void writeImage(BufferedImage bufferedImage, TranscoderOutput transcoderOutput) throws TranscoderException { + } + }; + + transcoder.transcode(new TranscoderInput(getSVG()), null); + + return rasterizedSVG; + }*/ +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/CharacterTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/CharacterTest.java new file mode 100644 index 0000000..aecd35a --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/CharacterTest.java @@ -0,0 +1,40 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Graphics2D; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class CharacterTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + double w = getPageSize().width; + double h = getPageSize().height; + Charset latin1 = StandardCharsets.ISO_8859_1; + CharsetEncoder latin1Encoder = latin1.newEncoder(); + List charactersInCharset = new ArrayList<>(); + for (char char_ = Character.MIN_VALUE; char_ < Character.MAX_VALUE; char_++) { + String javaString = String.valueOf(char_); + if (latin1Encoder.canEncode(char_)) { + charactersInCharset.add(javaString); + } + } + final int colCount = (int) Math.ceil(Math.sqrt(charactersInCharset.size())); + final int rowCount = colCount; + double tileWidth = w / colCount; + double tileHeight = h / rowCount; + int charIndex = 0; + for (double y = 0.0; y < h; y += tileHeight) { + for (double x = 0.0; x < w; x += tileWidth) { + String c = charactersInCharset.get(charIndex); + double tileCenterX = x + tileWidth / 2.0; + double tileCenterY = y + tileHeight / 2.0; + g.drawString(c, (float) tileCenterX, (float) tileCenterY); + charIndex++; + } + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ClippingTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ClippingTest.java new file mode 100644 index 0000000..8807512 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ClippingTest.java @@ -0,0 +1,33 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; + +public class ClippingTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + double w = getPageSize().width; + double h = getPageSize().height; + + AffineTransform txOrig = g.getTransform(); + g.translate(w / 2.0, h / 2.0); + + g.setClip(new Ellipse2D.Double(-0.6 * w / 2.0, -h / 2.0, 0.6 * w, h)); + for (double x = -w / 2.0; x < w / 2.0; x += 4.0) { + g.draw(new Line2D.Double(x, -h / 2.0, x, h / 2.0)); + } + + g.rotate(Math.toRadians(-90.0)); + g.clip(new Ellipse2D.Double(-0.6 * w / 2.0, -h / 2.0, 0.6 * w, h)); + for (double x = -h / 2.0; x < h / 2.0; x += 4.0) { + g.draw(new Line2D.Double(x, -w / 2.0, x, w / 2.0)); + } + + g.setTransform(txOrig); + g.setClip(null); + g.draw(new Line2D.Double(0.0, 0.0, w, h)); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ColorTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ColorTest.java new file mode 100644 index 0000000..b54f923 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ColorTest.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +public class ColorTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + final float wPage = (float) getPageSize().width; + final float hPage = (float) getPageSize().height; + final float wTile = Math.min(wPage / 15f, hPage / 15f); + final float hTile = wTile; + float w = wPage - wTile; + float h = hPage - hTile; + for (float y = (hPage - h) / 2f; y < h; y += hTile) { + float yRel = y / h; + for (float x = (wPage - w) / 2f; x < w; x += wTile) { + float xRel = x / w; + Color c = Color.getHSBColor(yRel, 1f, 1f); + int alpha = 255 - (int) (xRel * 255f); + g.setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha)); + g.fill(new Rectangle2D.Float(x, y, wTile, hTile)); + } + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/EmptyFileTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/EmptyFileTest.java new file mode 100644 index 0000000..503f42b --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/EmptyFileTest.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Graphics2D; + +public class EmptyFileTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/FontTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/FontTest.java new file mode 100644 index 0000000..ef690ee --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/FontTest.java @@ -0,0 +1,47 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import org.xbib.graphics.chart.io.vector.GraphicsState; + +import java.awt.Font; +import java.awt.Graphics2D; + +public class FontTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + final int tileCountH = 4; + final int tileCountV = 8; + final double wTile = getPageSize().width / tileCountH; + final double hTile = getPageSize().height / tileCountV; + final double xOrigin = (getPageSize().width - tileCountH * wTile) / 2.0; + final double yOrigin = (getPageSize().height - tileCountV * hTile) / 2.0; + double x = xOrigin; + double y = yOrigin; + + final float[] sizes = { + GraphicsState.DEFAULT_FONT.getSize2D(), GraphicsState.DEFAULT_FONT.getSize2D() / 2f + }; + final String[] names = { + GraphicsState.DEFAULT_FONT.getName(), Font.SERIF, Font.MONOSPACED, "Monospaced" + }; + final int[] styles = { + Font.PLAIN, Font.ITALIC, Font.BOLD, Font.BOLD | Font.ITALIC + }; + + for (float size : sizes) { + for (String name : names) { + for (int style : styles) { + Font font = new Font(name, style, 10).deriveFont(size); + g.setFont(font); + g.drawString("vg2d", (float) x, (float) y); + + x += wTile; + if (x >= tileCountH * wTile) { + x = xOrigin; + y += hTile; + } + } + } + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ImageTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ImageTest.java new file mode 100644 index 0000000..03ba67a --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ImageTest.java @@ -0,0 +1,29 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; + +public class ImageTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + // Draw an image + BufferedImage image = new BufferedImage(4, 3, BufferedImage.TYPE_INT_ARGB); + Graphics2D gImage = (Graphics2D) image.getGraphics(); + gImage.setPaint(new GradientPaint( + new Point2D.Double(0.0, 0.0), Color.RED, + new Point2D.Double(3.0, 2.0), Color.BLUE) + ); + gImage.fill(new Rectangle2D.Double(0.0, 0.0, 4.0, 3.0)); + + g.drawImage(image, 0, 0, (int) getPageSize().width, (int) (0.5 * getPageSize().height), null); + + g.rotate(-10.0 / 180.0 * Math.PI, 2.0, 1.5); + g.drawImage(image, (int) (0.1 * getPageSize().width), (int) (0.6 * getPageSize().height), + (int) (0.33 * getPageSize().width), (int) (0.33 * getPageSize().height), null); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/PaintTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/PaintTest.java new file mode 100644 index 0000000..ca1a6e3 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/PaintTest.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + +public class PaintTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + // Draw multiple rotated rectangles + final int steps = 25; + final int cols = 5; + final int rows = steps / cols; + final double tileWidth = getPageSize().width / cols; + final double tileHeight = getPageSize().height / rows; + g.translate(tileWidth / 2, tileHeight / 2); + + final double rectWidth = tileWidth * 0.8; + final double rectHeight = tileHeight * 0.8; + Rectangle2D rect = new Rectangle2D.Double(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight); + g.setPaint(new GradientPaint(0f, (float) (-rectHeight / 2), Color.RED, 0f, (float) (rectHeight / 2), Color.BLUE)); + for (int i = 0; i < steps; i++) { + AffineTransform txOld = g.getTransform(); + AffineTransform tx = new AffineTransform(txOld); + int col = i % 5; + int row = i / 5; + tx.translate(col * tileWidth, row * tileHeight); + tx.rotate(i * Math.toRadians(360.0 / steps)); + g.setTransform(tx); + g.fill(rect); + g.setTransform(txOld); + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ShapesTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ShapesTest.java new file mode 100644 index 0000000..40314ee --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/ShapesTest.java @@ -0,0 +1,127 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.QuadCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; + +public class ShapesTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + final int tileCountH = 4; + final int tileCountV = 6; + final double wTile = getPageSize().width / tileCountH; + final double hTile = getPageSize().height / tileCountV; + final double xOrigin = (getPageSize().width - tileCountH * wTile) / 2.0; + final double yOrigin = (getPageSize().height - tileCountV * hTile) / 2.0; + double x = xOrigin; + double y = yOrigin; + + g.draw(new Line2D.Double(x, y, x + 0.8 * wTile, y + 0.6 * hTile)); + x += wTile; + g.draw(new QuadCurve2D.Double(x, y, x + 0.8 * wTile, y, x + 0.8 * wTile, y + 0.6 * hTile)); + x += wTile; + g.draw(new CubicCurve2D.Double(x, y, x + 0.8 * wTile, y, x, y + 0.6 * hTile, x + 0.8 * wTile, y + 0.6 * hTile)); + + x = xOrigin; + y += hTile; + g.fill(new Rectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + g.draw(new Rectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + + g.fill(new RoundRectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 0.2 * wTile, 0.2 * hTile)); + x += wTile; + g.draw(new RoundRectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 0.2 * wTile, 0.2 * hTile)); + x += wTile; + + + x = xOrigin; + y += hTile; + g.fill(new Ellipse2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + g.draw(new Ellipse2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + + g.fill(new Polygon( + new int[]{(int) (x), (int) (x + 0.8 * wTile / 2.0), (int) (x + 0.8 * wTile)}, + new int[]{(int) (y + 0.6 * hTile), (int) (y), (int) (y + 0.6 * hTile)}, + 3 + )); + x += wTile; + g.draw(new Polygon( + new int[]{(int) (x), (int) (x + 0.8 * wTile / 2.0), (int) (x + 0.8 * wTile)}, + new int[]{(int) (y + 0.6 * hTile), (int) (y), (int) (y + 0.6 * hTile)}, + 3 + )); + + + x = xOrigin; + y += hTile; + g.fill(new Arc2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 110, 320, Arc2D.PIE)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 110, 320, Arc2D.PIE)); + x += wTile; + g.fill(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.CHORD)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.CHORD)); + + + x = xOrigin; + y += hTile; + g.fill(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.OPEN)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.OPEN)); + x += wTile; + g.fill(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.PIE)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.PIE)); + + + x = xOrigin; + y += hTile; + + final Path2D path1 = new Path2D.Double(); + path1.moveTo(0.00, 0.00); + path1.lineTo(0.33, 1.00); + path1.lineTo(0.67, 0.00); + path1.quadTo(0.33, 0.00, 0.33, 0.50); + path1.quadTo(0.33, 1.00, 0.67, 1.00); + path1.quadTo(1.00, 1.00, 1.00, 0.50); + path1.lineTo(0.67, 0.50); + path1.transform(AffineTransform.getScaleInstance(0.8 * wTile, 0.6 * hTile)); + + path1.transform(AffineTransform.getTranslateInstance(x, y)); + g.fill(path1); + x += wTile; + path1.transform(AffineTransform.getTranslateInstance(wTile, 0.0)); + g.draw(path1); + x += wTile; + + final Path2D path2 = new Path2D.Double(); + path2.moveTo(0.0, 0.4); + path2.curveTo(0.0, 0.3, 0.0, 0.0, 0.2, 0.0); + path2.curveTo(0.3, 0.0, 0.4, 0.1, 0.4, 0.3); + path2.curveTo(0.4, 0.5, 0.2, 0.8, 0.0, 1.0); + path2.lineTo(0.6, 1.0); + path2.lineTo(0.6, 0.0); + path2.curveTo(0.8, 0.0, 1.0, 0.2, 1.0, 0.5); + path2.curveTo(1.0, 0.6, 1.0, 0.8, 0.9, 0.9); + path2.closePath(); + path2.transform(AffineTransform.getScaleInstance(0.8 * wTile, 0.6 * hTile)); + + path2.transform(AffineTransform.getTranslateInstance(x, y)); + g.fill(path2); + x += wTile; + path2.transform(AffineTransform.getTranslateInstance(wTile, 0.0)); + g.draw(path2); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/StrokeTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/StrokeTest.java new file mode 100644 index 0000000..3455ab1 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/StrokeTest.java @@ -0,0 +1,90 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.BasicStroke; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; + +public class StrokeTest extends AbstractTest { + + private static final Stroke[] strokes = { + // Width + new BasicStroke(0.0f), + new BasicStroke(0.5f), + new BasicStroke(1.0f), + new BasicStroke(2.0f), + // Cap + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER), + new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER), + new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER), + null, + // Join + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND), + null, + // Miter limit + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 3f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f), + // Dash pattern + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{1f}, 0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{1f, 1f}, 0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f, 1f}, 0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f, 4f, 1f}, 0f), + // Dash phase + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 0.5f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 1.0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 1.5f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 2.5f), + }; + + @Override + public void draw(Graphics2D g) { + final int tileCountH = 4; + final int tileCountV = 6; + final double wTile = getPageSize().width / tileCountH; + final double hTile = getPageSize().height / tileCountV; + final double xOrigin = (getPageSize().width - tileCountH * wTile) / 2.0; + final double yOrigin = (getPageSize().height - tileCountV * hTile) / 2.0; + + final Path2D path = new Path2D.Double(); + path.moveTo(0.00, 0.00); + path.lineTo(0.33, 1.00); + path.lineTo(0.67, 0.00); + path.quadTo(0.33, 0.00, 0.33, 0.50); + path.quadTo(0.33, 1.00, 0.67, 1.00); + path.quadTo(1.00, 1.00, 1.00, 0.50); + path.lineTo(0.67, 0.50); + path.moveTo(1.0, 0.4); + path.curveTo(1.0, 0.3, 1.0, 0.0, 1.2, 0.0); + path.curveTo(1.3, 0.0, 1.4, 0.1, 1.4, 0.3); + path.curveTo(1.4, 0.5, 1.2, 0.8, 1.0, 1.0); + path.lineTo(1.6, 1.0); + path.lineTo(1.6, 0.0); + path.curveTo(1.8, 0.0, 2.0, 0.2, 2.0, 0.5); + path.curveTo(2.0, 0.6, 2.0, 0.8, 1.9, 0.9); + + path.transform(AffineTransform.getScaleInstance(0.8 * wTile / 2.0, 0.6 * hTile)); + + double x = xOrigin; + double y = yOrigin; + for (Stroke stroke : strokes) { + if (stroke != null) { + Path2D p = new Path2D.Double(path); + p.transform(AffineTransform.getTranslateInstance(x, y)); + + g.setStroke(stroke); + g.draw(p); + } + + x += wTile; + if (x >= tileCountH * wTile) { + x = xOrigin; + y += hTile; + } + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/SwingExportTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/SwingExportTest.java new file mode 100644 index 0000000..f34ce73 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/SwingExportTest.java @@ -0,0 +1,24 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JSlider; +import java.awt.BorderLayout; +import java.awt.Graphics2D; + +public class SwingExportTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + JFrame frame = new JFrame(); + frame.getContentPane().add(new JButton("Hello Swing!"), BorderLayout.CENTER); + frame.getContentPane().add(new JSlider(), BorderLayout.NORTH); + frame.setSize(200, 250); + + //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + frame.setVisible(true); + frame.printAll(g); + frame.setVisible(false); + frame.dispose(); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/TestBrowser.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/TestBrowser.java new file mode 100644 index 0000000..7917948 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/TestBrowser.java @@ -0,0 +1,263 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.ListSelectionModel; +import javax.swing.WindowConstants; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("serial") +public class TestBrowser extends JFrame { + + private final List testCases; + + private final ImageComparisonPanel imageComparisonPanel; + + private AbstractTest testCase; + + public TestBrowser() { + super("Test browser"); + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setSize(1024, 768); + testCases = new ArrayList<>(); + testCases.add(new ColorTest()); + testCases.add(new StrokeTest()); + testCases.add(new ShapesTest()); + testCases.add(new FontTest()); + testCases.add(new CharacterTest()); + testCases.add(new EmptyFileTest()); + testCases.add(new ImageTest()); + testCases.add(new ClippingTest()); + testCases.add(new PaintTest()); + testCases.add(new SwingExportTest()); + testCases.add(new TransformTest()); + final JList testList = new JList<>(testCases.toArray()); + testList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + testList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + String testName = value.getClass().getSimpleName(); + return super.getListCellRendererComponent(list, testName, index, isSelected, cellHasFocus); + } + }); + testList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + int index = testList.getSelectedIndex(); + if (index < 0) { + return; + } + AbstractTest test = testCases.get(index); + testCase = test; + try { + setTestCase(test); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + }); + getContentPane().add(testList, BorderLayout.WEST); + + JPanel configurableImageComparisonPanel = new JPanel(new BorderLayout()); + getContentPane().add(configurableImageComparisonPanel, BorderLayout.CENTER); + + ImageFormat startingImageFormat = ImageFormat.EPS; + JComboBox imageFormatSelector = new JComboBox<>(ImageFormat.values()); + configurableImageComparisonPanel.add(imageFormatSelector, BorderLayout.NORTH); + imageFormatSelector.setSelectedItem(startingImageFormat); + imageFormatSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent itemEvent) { + ImageFormat format = (ImageFormat) itemEvent.getItem(); + imageComparisonPanel.setImageFormat(format); + + AbstractTest test = getTestCase(); + if (test != null) { + try { + setTestCase(test); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }); + + imageComparisonPanel = new ImageComparisonPanel(startingImageFormat); + configurableImageComparisonPanel.add(imageComparisonPanel, BorderLayout.CENTER); + } + + public static void main(String[] args) { + new TestBrowser().setVisible(true); + } + + public AbstractTest getTestCase() { + return testCase; + } + + public void setTestCase(AbstractTest test) throws IOException { + BufferedImage reference = test.getReference(); + imageComparisonPanel.setLeftComponent(new ImageDisplayPanel(reference, null)); + //ImageDisplayPanel imageDisplayPanel; + switch (imageComparisonPanel.getImageFormat()) { + case EPS: + //imageDisplayPanel = new ImageDisplayPanel(test.getRasterizedEPS(), test.getEPS()); + //imageComparisonPanel.setRightComponent(imageDisplayPanel); + break; + case PDF: + //imageDisplayPanel = new ImageDisplayPanel(test.getRasterizedPDF(), test.getPDF()); + //imageComparisonPanel.setRightComponent(imageDisplayPanel); + break; + case SVG: + //imageDisplayPanel = new ImageDisplayPanel(test.getRasterizedSVG(), test.getSVG()); + //imageComparisonPanel.setRightComponent(imageDisplayPanel); + break; + default: + throw new IllegalArgumentException("Unknown image format: " + imageComparisonPanel.getImageFormat()); + } + } + + private enum ImageFormat { + EPS("EPS"), + PDF("PDF"), + SVG("SVG"); + + private final String name; + + ImageFormat(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + private static class ImageComparisonPanel extends Box { + + private final Box leftPanel; + + private final Box rightPanel; + + private ImageFormat imageFormat; + + private JComponent leftComponent; + + private JComponent rightComponent; + + public ImageComparisonPanel(ImageFormat imageFormat) { + super(BoxLayout.PAGE_AXIS); + + this.imageFormat = imageFormat; + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + splitPane.setResizeWeight(0.5); + add(splitPane); + + leftPanel = new Box(BoxLayout.PAGE_AXIS); + leftPanel.add(new JLabel("Graphics2D")); + splitPane.setTopComponent(leftPanel); + + rightPanel = new Box(BoxLayout.PAGE_AXIS); + rightPanel.add(new JLabel(imageFormat.getName())); + splitPane.setBottomComponent(rightPanel); + } + + public void setLeftComponent(JComponent leftComponent) { + if (this.leftComponent != null) { + leftPanel.remove(this.leftComponent); + } + this.leftComponent = leftComponent; + leftPanel.add(leftComponent); + leftPanel.revalidate(); + leftPanel.repaint(); + } + + public void setRightComponent(JComponent rightComponent) { + if (this.rightComponent != null) { + rightPanel.remove(this.rightComponent); + } + this.rightComponent = rightComponent; + rightPanel.add(rightComponent); + rightPanel.revalidate(); + rightPanel.repaint(); + } + + public ImageFormat getImageFormat() { + return imageFormat; + } + + public void setImageFormat(ImageFormat imageFormat) { + this.imageFormat = imageFormat; + JLabel imageFormatLabel = (JLabel) rightPanel.getComponent(0); + imageFormatLabel.setText(imageFormat.getName()); + imageFormatLabel.repaint(); + } + } + + private static class ImageDisplayPanel extends JPanel { + private final InputStream imageData; + + public ImageDisplayPanel(BufferedImage renderedImage, InputStream imageData) { + super(new BorderLayout()); + this.imageData = imageData; + + JLabel imageLabel = new JLabel(new ImageIcon(renderedImage)); + add(imageLabel, BorderLayout.CENTER); + + JButton saveToFileButton = new JButton("Save as..."); + if (imageData == null) { + saveToFileButton.setEnabled(false); + } + saveToFileButton.addActionListener(e -> { + JFileChooser saveFileDialog = new JFileChooser(); + saveFileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY); + saveFileDialog.setMultiSelectionEnabled(false); + int userChoice = saveFileDialog.showSaveDialog(ImageDisplayPanel.this); + if (userChoice != JFileChooser.APPROVE_OPTION) { + return; + } + + File dest = saveFileDialog.getSelectedFile(); + FileOutputStream destStream = null; + try { + destStream = new FileOutputStream(dest); + int imageDataChunk; + while ((imageDataChunk = ImageDisplayPanel.this.imageData.read()) != -1) { + destStream.write(imageDataChunk); + } + } catch (IOException e1) { + e1.printStackTrace(); + } finally { + if (destStream != null) { + try { + destStream.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + }); + add(saveToFileButton, BorderLayout.SOUTH); + } + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/TransformTest.java b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/TransformTest.java new file mode 100644 index 0000000..4bc25b5 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/io/vector/visual/TransformTest.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.chart.io.vector.visual; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + +public class TransformTest extends AbstractTest { + + @Override + public void draw(Graphics2D g) { + final int rowCount = 2; + final int colCount = 4; + double wTile = getPageSize().width / colCount; + double hTile = wTile; + + g.translate(0.5 * wTile, 0.5 * hTile); + AffineTransform txOrig = g.getTransform(); + + Shape s = new Rectangle2D.Double(0.0, 0.0, 0.5 * wTile, 0.75 * hTile); + + // Row 1 + + g.draw(s); + + g.translate(wTile, 0.0); + g.draw(s); + + g.translate(wTile, 0.0); + { + Graphics2D g2 = (Graphics2D) g.create(); + g2.scale(0.5, 0.5); + g2.draw(s); + g2.dispose(); + } + + g.translate(wTile, 0.0); + { + Graphics2D g2 = (Graphics2D) g.create(); + g2.rotate(Math.toRadians(30.0)); + g2.draw(s); + g2.dispose(); + } + + // Row 2 + + g.setTransform(txOrig); + g.translate(0.0, hTile); + + g.shear(0.5, 0.0); + g.draw(s); + g.shear(-0.5, 0.0); + g.translate(wTile, 0.0); + + g.shear(0.0, 0.5); + g.draw(s); + g.shear(0.0, -0.5); + } +} diff --git a/chart/src/test/java/org/xbib/graphics/chart/swing/SwingWrapper.java b/chart/src/test/java/org/xbib/graphics/chart/swing/SwingWrapper.java new file mode 100644 index 0000000..2543eb0 --- /dev/null +++ b/chart/src/test/java/org/xbib/graphics/chart/swing/SwingWrapper.java @@ -0,0 +1,282 @@ +package org.xbib.graphics.chart.swing; + +import org.xbib.graphics.chart.io.BitmapFormat; +import org.xbib.graphics.chart.io.VectorGraphicsFormat; +import org.xbib.graphics.chart.Chart; + +import javax.swing.AbstractAction; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; +import javax.swing.filechooser.FileFilter; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridLayout; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +/** + * A convenience class used to display a Chart in a Swing application. + */ +public class SwingWrapper { + + private String windowTitle = "Chart"; + + private List> charts = new ArrayList<>(); + + private int numRows; + + private int numColumns; + + public SwingWrapper(Chart chart) { + this.charts.add(chart); + } + + public SwingWrapper(List> charts) { + this.charts = charts; + this.numRows = (int) (Math.sqrt(charts.size()) + .5); + this.numColumns = (int) ((double) charts.size() / this.numRows + 1); + } + + public SwingWrapper(List> charts, int numRows, int numColumns) { + this.charts = charts; + this.numRows = numRows; + this.numColumns = numColumns; + } + + public JFrame displayChart(String windowTitle) { + this.windowTitle = windowTitle; + return displayChart(); + } + + public JFrame displayChart() { + final JFrame frame = new JFrame(windowTitle); + SwingUtilities.invokeLater(() -> { + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + JPanel chartPanel = new ChartPanel<>(charts.get(0)); + frame.add(chartPanel); + frame.pack(); + frame.setVisible(true); + }); + + return frame; + } + + public JFrame displayChartMatrix(String windowTitle) { + this.windowTitle = windowTitle; + return displayChartMatrix(); + } + + private JFrame displayChartMatrix() { + final JFrame frame = new JFrame(windowTitle); + SwingUtilities.invokeLater(() -> { + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.getContentPane().setLayout(new GridLayout(numRows, numColumns)); + for (Chart chart : charts) { + if (chart != null) { + JPanel chartPanel = new ChartPanel<>(chart); + frame.add(chartPanel); + } else { + JPanel chartPanel = new JPanel(); + frame.getContentPane().add(chartPanel); + } + } + frame.pack(); + frame.setVisible(true); + }); + return frame; + } + + @SuppressWarnings("serial") + private static class ChartPanel> extends JPanel { + + private final T chart; + + private final Dimension preferredSize; + + private String saveAsString = "Save As..."; + + public ChartPanel(T chart) { + this.chart = chart; + preferredSize = new Dimension(chart.getWidth(), chart.getHeight()); + // Right-click listener for saving chart + this.addMouseListener(new PopUpMenuClickListener()); + // Control+S key listener for saving chart + KeyStroke ctrlS = KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()); + this.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(ctrlS, "save"); + this.getActionMap().put("save", new SaveAction()); + } + + public void setSaveAsString(String saveAsString) { + this.saveAsString = saveAsString; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g.create(); + chart.paint(g2d, getWidth(), getHeight()); + g2d.dispose(); + } + + public T getChart() { + return this.chart; + } + + @Override + public Dimension getPreferredSize() { + return this.preferredSize; + } + + void showSaveAsDialog() { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.addChoosableFileFilter(new SuffixSaveFilter("jpg")); + FileFilter pngFileFilter = new SuffixSaveFilter("png"); + fileChooser.addChoosableFileFilter(pngFileFilter); + fileChooser.addChoosableFileFilter(new SuffixSaveFilter("bmp")); + fileChooser.addChoosableFileFilter(new SuffixSaveFilter("gif")); + fileChooser.addChoosableFileFilter(new SuffixSaveFilter("svg")); + fileChooser.addChoosableFileFilter(new SuffixSaveFilter("eps")); + fileChooser.addChoosableFileFilter(new SuffixSaveFilter("pdf")); + fileChooser.setAcceptAllFileFilterUsed(false); + fileChooser.setFileFilter(pngFileFilter); + if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { + + if (fileChooser.getSelectedFile() != null) { + File theFileToSave = fileChooser.getSelectedFile(); + try { + OutputStream outputStream = Files.newOutputStream(theFileToSave.toPath()); + if (fileChooser.getFileFilter() == null) { + chart.saveBitmap(outputStream, BitmapFormat.PNG); + } else if (fileChooser.getFileFilter().getDescription().equals("*.jpg,*.JPG")) { + chart.saveJPGWithQuality(outputStream, 1.0f); + } else if (fileChooser.getFileFilter().getDescription().equals("*.png,*.PNG")) { + chart.saveBitmap(outputStream, BitmapFormat.PNG); + } else if (fileChooser.getFileFilter().getDescription().equals("*.bmp,*.BMP")) { + chart.saveBitmap(outputStream, BitmapFormat.BMP); + } else if (fileChooser.getFileFilter().getDescription().equals("*.gif,*.GIF")) { + chart.saveBitmap(outputStream, BitmapFormat.GIF); + } else if (fileChooser.getFileFilter().getDescription().equals("*.svg,*.SVG")) { + chart.write(outputStream, VectorGraphicsFormat.SVG); + } else if (fileChooser.getFileFilter().getDescription().equals("*.eps,*.EPS")) { + chart.write(outputStream, VectorGraphicsFormat.EPS); + } else if (fileChooser.getFileFilter().getDescription().equals("*.pdf,*.PDF")) { + chart.write(outputStream, VectorGraphicsFormat.PDF); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + } + + class SaveAction extends AbstractAction { + + public SaveAction() { + super("save"); + } + + @Override + public void actionPerformed(ActionEvent e) { + showSaveAsDialog(); + } + } + + class SuffixSaveFilter extends FileFilter { + + private final String suffix; + + public SuffixSaveFilter(String suffix) { + this.suffix = suffix; + } + + @Override + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + String s = f.getName(); + return s.endsWith("." + suffix) || s.endsWith("." + suffix.toUpperCase()); + } + + @Override + public String getDescription() { + return "*." + suffix + ",*." + suffix.toUpperCase(); + } + } + + class PopUpMenuClickListener extends MouseAdapter { + + @Override + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + doPop(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + doPop(e); + } + } + + private void doPop(MouseEvent e) { + ChartPanelPopupMenu menu = new ChartPanelPopupMenu(); + menu.show(e.getComponent(), e.getX(), e.getY()); + } + } + + class ChartPanelPopupMenu extends JPopupMenu { + + JMenuItem saveAsMenuItem; + + ChartPanelPopupMenu() { + saveAsMenuItem = new JMenuItem(saveAsString); + saveAsMenuItem.addMouseListener(new MouseListener() { + + @Override + public void mouseReleased(MouseEvent e) { + showSaveAsDialog(); + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseClicked(MouseEvent e) { + } + }); + add(saveAsMenuItem); + } + } + } + + +} diff --git a/gradle.properties b/gradle.properties index 75b39d6..b6622fd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,8 @@ -group = org.xbib +group = org.xbib.graphics name = graphics -version = 0.0.1 +version = 3.0.0 +gradle.wrapper.version = 6.6.1 +pdfbox.version = 2.0.19 +zxing.version = 3.3.1 +reflections.version = 0.9.11 diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle new file mode 100644 index 0000000..c983c98 --- /dev/null +++ b/gradle/compile/java.gradle @@ -0,0 +1,44 @@ + +apply plugin: 'java-library' + +java { + modularity.inferModulePath.set(true) +} + +compileJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +compileTestJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +jar { + manifest { + attributes('Implementation-Version': project.version) + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier 'sources' + from sourceSets.main.allSource + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier 'javadoc' +} + +artifacts { + archives sourcesJar, javadocJar +} + +tasks.withType(JavaCompile) { + options.compilerArgs << '-Xlint:all' +} + +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') +} diff --git a/gradle/documentation/asciidoc.gradle b/gradle/documentation/asciidoc.gradle new file mode 100644 index 0000000..87ba22e --- /dev/null +++ b/gradle/documentation/asciidoc.gradle @@ -0,0 +1,55 @@ +apply plugin: 'org.xbib.gradle.plugin.asciidoctor' + +configurations { + asciidoclet +} + +dependencies { + asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}" +} + + +asciidoctor { + backends 'html5' + outputDir = file("${rootProject.projectDir}/docs") + separateOutputDirs = false + attributes 'source-highlighter': 'coderay', + idprefix: '', + idseparator: '-', + toc: 'left', + doctype: 'book', + icons: 'font', + encoding: 'utf-8', + sectlink: true, + sectanchors: true, + linkattrs: true, + imagesdir: 'img', + stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css" +} + + +/*javadoc { +options.docletpath = configurations.asciidoclet.files.asType(List) +options.doclet = 'org.asciidoctor.Asciidoclet' +//options.overview = "src/docs/asciidoclet/overview.adoc" +options.addStringOption "-base-dir", "${projectDir}" +options.addStringOption "-attribute", + "name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}" +configure(options) { + noTimestamp = true +} +}*/ + + +/*javadoc { + options.docletpath = configurations.asciidoclet.files.asType(List) + options.doclet = 'org.asciidoctor.Asciidoclet' + options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc" + options.addStringOption "-base-dir", "${projectDir}" + options.addStringOption "-attribute", + "name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}" + options.destinationDirectory(file("${projectDir}/docs/javadoc")) + configure(options) { + noTimestamp = true + } +}*/ diff --git a/gradle/ext.gradle b/gradle/ext.gradle deleted file mode 100644 index 567648e..0000000 --- a/gradle/ext.gradle +++ /dev/null @@ -1,8 +0,0 @@ -ext { - user = 'xbib' - projectName = 'graphics' - projectDescription = 'More graphics classes for Java' - scmUrl = 'https://github.com/xbib/graphics' - scmConnection = 'scm:git:git://github.com/xbib/graphics.git' - scmDeveloperConnection = 'scm:git:git://github.com/xbib/graphics.git' -} diff --git a/gradle/ide/idea.gradle b/gradle/ide/idea.gradle new file mode 100644 index 0000000..64e2167 --- /dev/null +++ b/gradle/ide/idea.gradle @@ -0,0 +1,13 @@ +apply plugin: 'idea' + +idea { + module { + outputDir file('build/classes/java/main') + testOutputDir file('build/classes/java/test') + } +} + +if (project.convention.findPlugin(JavaPluginConvention)) { + //sourceSets.main.output.classesDirs = file("build/classes/java/main") + //sourceSets.test.output.classesDirs = file("build/classes/java/test") +} diff --git a/gradle/publish.gradle b/gradle/publish.gradle deleted file mode 100644 index dd4f6ee..0000000 --- a/gradle/publish.gradle +++ /dev/null @@ -1,70 +0,0 @@ - -task xbibUpload(type: Upload, dependsOn: build) { - configuration = configurations.archives - uploadDescriptor = true - repositories { - if (project.hasProperty('xbibUsername')) { - mavenDeployer { - configuration = configurations.wagon - repository(url: uri('sftp://xbib.org/repository')) { - authentication(userName: xbibUsername, privateKey: xbibPrivateKey) - } - } - } - } -} - -task sonatypeUpload(type: Upload, dependsOn: build) { - configuration = configurations.archives - uploadDescriptor = true - repositories { - if (project.hasProperty('ossrhUsername')) { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - repository(url: uri(ossrhReleaseUrl)) { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - snapshotRepository(url: uri(ossrhSnapshotUrl)) { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - pom.project { - groupId project.group - artifactId project.name - version project.version - name project.name - description projectDescription - packaging 'jar' - inceptionYear '2018' - url scmUrl - organization { - name 'xbib' - url 'http://xbib.org' - } - developers { - developer { - id user - name 'Jörg Prante' - email 'joergprante@gmail.com' - url 'https://github.com/jprante' - } - } - scm { - url scmUrl - connection scmConnection - developerConnection scmDeveloperConnection - } - licenses { - license { - name 'The GNU General Public License (GPL)' - url 'http://www.gnu.org/licenses/old-licenses/gpl-2.0.html' - } - } - } - } - } - } -} - -nexusStaging { - packageGroup = "org.xbib" -} diff --git a/gradle/publishing/publication.gradle b/gradle/publishing/publication.gradle new file mode 100644 index 0000000..a0f826e --- /dev/null +++ b/gradle/publishing/publication.gradle @@ -0,0 +1,66 @@ +import java.time.Duration + +apply plugin: "de.marcphilipp.nexus-publish" + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact sourcesJar + artifact javadocJar + pom { + name = project.name + description = rootProject.ext.description + url = rootProject.ext.url + inceptionYear = rootProject.ext.inceptionYear + packaging = 'jar' + organization { + name = 'xbib' + url = 'https://xbib.org' + } + developers { + developer { + id = 'jprante' + name = 'Jörg Prante' + email = 'joergprante@gmail.com' + url = 'https://github.com/jprante' + } + } + scm { + url = rootProject.ext.scmUrl + connection = rootProject.ext.scmConnection + developerConnection = rootProject.ext.scmDeveloperConnection + } + issueManagement { + system = rootProject.ext.issueManagementSystem + url = rootProject.ext.issueManagementUrl + } + licenses { + license { + name = rootProject.ext.licenseName + url = rootProject.ext.licenseUrl + distribution = 'repo' + } + } + } + } + } +} + +if (project.hasProperty("signing.keyId")) { + apply plugin: 'signing' + signing { + sign publishing.publications.mavenJava + } +} + +nexusPublishing { + repositories { + sonatype { + username = project.property('ossrhUsername') + password = project.property('ossrhPassword') + packageGroup = "org.xbib" + } + } + clientTimeout = Duration.ofSeconds(600) +} diff --git a/gradle/publishing/sonatype.gradle b/gradle/publishing/sonatype.gradle new file mode 100644 index 0000000..e1813f3 --- /dev/null +++ b/gradle/publishing/sonatype.gradle @@ -0,0 +1,11 @@ + +if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) { + + apply plugin: 'io.codearte.nexus-staging' + + nexusStaging { + username = project.property('ossrhUsername') + password = project.property('ossrhPassword') + packageGroup = "org.xbib" + } +} diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle new file mode 100644 index 0000000..188ea0d --- /dev/null +++ b/gradle/test/junit5.gradle @@ -0,0 +1,28 @@ + +def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.7.0' +def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2' + +dependencies { + testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}" + testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}" + testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" +} + +test { + useJUnitPlatform() + failFast = false + testLogging { + events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' + showStandardStreams = true + } + afterSuite { desc, result -> + if (!desc.parent) { + println "\nTest result: ${result.resultType}" + println "Test summary: ${result.testCount} tests, " + + "${result.successfulTestCount} succeeded, " + + "${result.failedTestCount} failed, " + + "${result.skippedTestCount} skipped" + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..33682bb --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/io-vector/NOTICE.txt b/io-vector/NOTICE.txt new file mode 100644 index 0000000..e729ca7 --- /dev/null +++ b/io-vector/NOTICE.txt @@ -0,0 +1,5 @@ +This work is based on VectorGraphics2D by Erich Seifert + +https://github.com/eseifert/vectorgraphics2d + +Lesser GNU Public License (LGPL 3) diff --git a/io-vector/build/docs/javadoc/allclasses-index.html b/io-vector/build/docs/javadoc/allclasses-index.html new file mode 100644 index 0000000..eb207b4 --- /dev/null +++ b/io-vector/build/docs/javadoc/allclasses-index.html @@ -0,0 +1,372 @@ + + + + + +All Classes (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

All Classes

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassDescription
AbsoluteToRelativeTransformsFilter 
AffineTransformCommand 
AlphaToMaskOp 
ASCII85EncodeStream 
Base64EncodeStream 
Command<T> 
CreateCommand 
DataUtils +
Abstract class that contains utility functions for working with data + collections like maps or lists.
+
DisposeCommand 
DrawImageCommand 
DrawShapeCommand 
DrawStringCommand 
EPSGraphics2D +
Graphics2D implementation that saves all operations to a string + in the Encapsulated PostScript® (EPS) format.
+
EPSProcessor 
EPSProcessorResult 
FillPaintedShapeAsImageFilter 
FillShapeCommand 
Filter 
FlateEncodeStream 
FormattingWriter 
GeneratedPayload 
GraphicsState 
GraphicsUtils +
Abstract class that contains utility functions for working with graphics.
+
Group 
GroupingFilter 
ImageDataStream 
ImageDataStream.Interleaving 
LineWrapOutputStream 
OptimizeFilter 
PageSize 
Payload 
PDFGraphics2D +
Graphics2D implementation that saves all operations to a string + in the Portable Document Format (PDF).
+
PDFObject 
PDFProcessor 
PDFProcessorResult 
Processor 
ProcessorResult 
Resources 
RotateCommand 
ScaleCommand 
SetBackgroundCommand 
SetClipCommand 
SetColorCommand 
SetCompositeCommand 
SetFontCommand 
SetHintCommand 
SetPaintCommand 
SetStrokeCommand 
SetTransformCommand 
SetXORModeCommand 
ShearCommand 
SizePayload 
StateChangeGroupingFilter 
StateCommand<T> 
SVGGraphics2D +
Graphics2D implementation that saves all operations to a string + in the Scaled Vector Graphics (SVG) format.
+
SVGProcessor 
SVGProcessorResult 
TransformCommand 
TranslateCommand 
VectorGraphics2D +
Base for classes that want to implement vector export.
+
VectorGraphicsFormat 
VectorHints 
VectorHints.Key 
VectorHints.Value 
+
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/allpackages-index.html b/io-vector/build/docs/javadoc/allpackages-index.html new file mode 100644 index 0000000..1a65515 --- /dev/null +++ b/io-vector/build/docs/javadoc/allpackages-index.html @@ -0,0 +1,120 @@ + + + + + +All Packages (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

All Packages

+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/constant-values.html b/io-vector/build/docs/javadoc/constant-values.html new file mode 100644 index 0000000..c898561 --- /dev/null +++ b/io-vector/build/docs/javadoc/constant-values.html @@ -0,0 +1,111 @@ + + + + + +Constant Field Values (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Constant Field Values

+
+

Contents

+ +
+
+
+

org.xbib.*

+
    +
  • +
    + + + + + + + + + + + + + + + + +
    org.xbib.graphics.io.vector.util.LineWrapOutputStream
    Modifier and TypeConstant FieldValue
    public static final java.lang.StringSTANDARD_EOL"\r\n"
    +
    +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/deprecated-list.html b/io-vector/build/docs/javadoc/deprecated-list.html new file mode 100644 index 0000000..3cbf57f --- /dev/null +++ b/io-vector/build/docs/javadoc/deprecated-list.html @@ -0,0 +1,80 @@ + + + + + +Deprecated List (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Deprecated API

+

Contents

+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/element-list b/io-vector/build/docs/javadoc/element-list new file mode 100644 index 0000000..a1bcee9 --- /dev/null +++ b/io-vector/build/docs/javadoc/element-list @@ -0,0 +1,8 @@ +module:org.xbib.graphics.io.vector +org.xbib.graphics.io.vector +org.xbib.graphics.io.vector.commands +org.xbib.graphics.io.vector.eps +org.xbib.graphics.io.vector.filters +org.xbib.graphics.io.vector.pdf +org.xbib.graphics.io.vector.svg +org.xbib.graphics.io.vector.util diff --git a/io-vector/build/docs/javadoc/help-doc.html b/io-vector/build/docs/javadoc/help-doc.html new file mode 100644 index 0000000..08411e4 --- /dev/null +++ b/io-vector/build/docs/javadoc/help-doc.html @@ -0,0 +1,185 @@ + + + + + +API Help (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

How This API Document Is Organized

+
This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.
+
+
+

Module

+

Each module has a page that contains a list of its packages, dependencies on other modules, and services, with a summary for each. These pages may contain three categories:

+
    +
  • Packages
  • +
  • Modules
  • +
  • Services
  • +
+
+
+

Package

+

Each package has a page that contains a list of its classes and interfaces, with a summary for each. These pages may contain six categories:

+
    +
  • Interfaces
  • +
  • Classes
  • +
  • Enums
  • +
  • Exceptions
  • +
  • Errors
  • +
  • Annotation Types
  • +
+
+
+

Class or Interface

+

Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

+
    +
  • Class Inheritance Diagram
  • +
  • Direct Subclasses
  • +
  • All Known Subinterfaces
  • +
  • All Known Implementing Classes
  • +
  • Class or Interface Declaration
  • +
  • Class or Interface Description
  • +
+
+
    +
  • Nested Class Summary
  • +
  • Field Summary
  • +
  • Property Summary
  • +
  • Constructor Summary
  • +
  • Method Summary
  • +
+
+
    +
  • Field Details
  • +
  • Property Details
  • +
  • Constructor Details
  • +
  • Method Details
  • +
+

The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.

+
+
+

Annotation Type

+

Each annotation type has its own separate page with the following sections:

+
    +
  • Annotation Type Declaration
  • +
  • Annotation Type Description
  • +
  • Required Element Summary
  • +
  • Optional Element Summary
  • +
  • Element Details
  • +
+
+
+

Enum

+

Each enum has its own separate page with the following sections:

+
    +
  • Enum Declaration
  • +
  • Enum Description
  • +
  • Enum Constant Summary
  • +
  • Enum Constant Details
  • +
+
+
+

Tree (Class Hierarchy)

+

There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. Classes are organized by inheritance structure starting with java.lang.Object. Interfaces do not inherit from java.lang.Object.

+
    +
  • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
  • +
  • When viewing a particular package, class or interface page, clicking on "Tree" displays the hierarchy for only that package.
  • +
+
+
+

Deprecated API

+

The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to shortcomings, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.

+
+
+

Index

+

The Index contains an alphabetic index of all classes, interfaces, constructors, methods, and fields, as well as lists of all packages and all classes.

+
+
+

Serialized Form

+

Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to those who implement rather than use the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See Also" section of the class description.

+
+
+

Constant Field Values

+

The Constant Field Values page lists the static final fields and their values.

+
+
+

Search

+

You can search for definitions of modules, packages, types, fields, methods, system properties and other terms defined in the API, using some or all of the name, optionally using "camel-case" abbreviations. For example:

+
    +
  • j.l.obj will match "java.lang.Object"
  • +
  • InpStr will match "java.io.InputStream"
  • +
  • HM.cK will match "java.util.HashMap.containsKey(Object)"
  • +
+

Refer to the Javadoc Search Specification for a full description of search features.

+
+
+This help file applies to API documentation generated by the standard doclet.
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/index-all.html b/io-vector/build/docs/javadoc/index-all.html new file mode 100644 index 0000000..892b78b --- /dev/null +++ b/io-vector/build/docs/javadoc/index-all.html @@ -0,0 +1,1081 @@ + + + + + +Index (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Index

+
+A B C D E F G H I J K L M N O P R S T U V W 
All Classes|All Packages +

A

+
+
A3 - Static variable in class org.xbib.graphics.io.vector.PageSize
+
 
+
A4 - Static variable in class org.xbib.graphics.io.vector.PageSize
+
 
+
A5 - Static variable in class org.xbib.graphics.io.vector.PageSize
+
 
+
AbsoluteToRelativeTransformsFilter - Class in org.xbib.graphics.io.vector.filters
+
 
+
AbsoluteToRelativeTransformsFilter(Iterable<Command<?>>) - Constructor for class org.xbib.graphics.io.vector.filters.AbsoluteToRelativeTransformsFilter
+
 
+
add(Command<?>) - Method in class org.xbib.graphics.io.vector.commands.Group
+
 
+
addFilter(Class<? extends FilterOutputStream>) - Method in class org.xbib.graphics.io.vector.pdf.Payload
+
 
+
addRenderingHints(Map<?, ?>) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
AffineTransformCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
AffineTransformCommand(AffineTransform) - Constructor for class org.xbib.graphics.io.vector.commands.AffineTransformCommand
+
 
+
ALPHA_ONLY - org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving
+
 
+
AlphaToMaskOp - Class in org.xbib.graphics.io.vector.util
+
 
+
AlphaToMaskOp() - Constructor for class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
AlphaToMaskOp(boolean) - Constructor for class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
ASCII85EncodeStream - Class in org.xbib.graphics.io.vector.util
+
 
+
ASCII85EncodeStream(OutputStream) - Constructor for class org.xbib.graphics.io.vector.util.ASCII85EncodeStream
+
 
+
ASCII85EncodeStream(OutputStream, String, String) - Constructor for class org.xbib.graphics.io.vector.util.ASCII85EncodeStream
+
 
+
asList(double[]) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Converts an array of double numbers to a list of Doubles.
+
+
asList(float[]) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Converts an array of float numbers to a list of Floats.
+
+
+

B

+
+
Base64EncodeStream - Class in org.xbib.graphics.io.vector.util
+
 
+
Base64EncodeStream(OutputStream) - Constructor for class org.xbib.graphics.io.vector.util.Base64EncodeStream
+
 
+
+

C

+
+
clearRect(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
clip(Shape) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
clipRect(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
clone() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
clone() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
clone(Shape) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
 
+
close() - Method in class org.xbib.graphics.io.vector.eps.EPSProcessorResult
+
 
+
close() - Method in class org.xbib.graphics.io.vector.pdf.Payload
+
 
+
close() - Method in class org.xbib.graphics.io.vector.pdf.PDFProcessorResult
+
 
+
close() - Method in interface org.xbib.graphics.io.vector.ProcessorResult
+
 
+
close() - Method in class org.xbib.graphics.io.vector.svg.SVGProcessorResult
+
 
+
close() - Method in class org.xbib.graphics.io.vector.util.ASCII85EncodeStream
+
 
+
close() - Method in class org.xbib.graphics.io.vector.util.Base64EncodeStream
+
 
+
close() - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
Command<T> - Class in org.xbib.graphics.io.vector
+
 
+
Command(T) - Constructor for class org.xbib.graphics.io.vector.Command
+
 
+
copyArea(int, int, int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
create() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
CreateCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
CreateCommand(VectorGraphics2D) - Constructor for class org.xbib.graphics.io.vector.commands.CreateCommand
+
 
+
createCompatibleDestImage(BufferedImage, ColorModel) - Method in class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
+

D

+
+
DataUtils - Class in org.xbib.graphics.io.vector.util
+
+
Abstract class that contains utility functions for working with data + collections like maps or lists.
+
+
DataUtils() - Constructor for class org.xbib.graphics.io.vector.util.DataUtils
+
+
Default constructor that prevents creation of class.
+
+
DEFAULT_BACKGROUND - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default background color.
+
+
DEFAULT_CLIP - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default clipping shape.
+
+
DEFAULT_COLOR - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default color.
+
+
DEFAULT_COMPOSITE - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default composite mode.
+
+
DEFAULT_FONT - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default font.
+
+
DEFAULT_PAINT - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default paint.
+
+
DEFAULT_STROKE - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default stroke.
+
+
DEFAULT_TRANSFORM - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default transformation.
+
+
DEFAULT_XOR_MODE - Static variable in class org.xbib.graphics.io.vector.GraphicsState
+
+
Default XOR mode.
+
+
dict - Variable in class org.xbib.graphics.io.vector.pdf.PDFObject
+
 
+
dispose() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
DisposeCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
DisposeCommand(VectorGraphics2D) - Constructor for class org.xbib.graphics.io.vector.commands.DisposeCommand
+
 
+
draw(Shape) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawArc(int, int, int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawGlyphVector(GlyphVector, float, float) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(BufferedImage, BufferedImageOp, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(Image, int, int, int, int, int, int, int, int, Color, ImageObserver) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(Image, int, int, int, int, int, int, int, int, ImageObserver) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(Image, int, int, int, int, Color, ImageObserver) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(Image, int, int, int, int, ImageObserver) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(Image, int, int, Color, ImageObserver) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(Image, int, int, ImageObserver) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawImage(Image, AffineTransform, ImageObserver) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
DrawImageCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
DrawImageCommand(Image, int, int, double, double, double, double) - Constructor for class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
drawLine(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawOval(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawPolygon(int[], int[], int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawPolygon(Polygon) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawPolyline(int[], int[], int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawRect(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawRenderableImage(RenderableImage, AffineTransform) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawRenderedImage(RenderedImage, AffineTransform) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawRoundRect(int, int, int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
DrawShapeCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
DrawShapeCommand(Shape) - Constructor for class org.xbib.graphics.io.vector.commands.DrawShapeCommand
+
 
+
drawString(String, float, float) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawString(String, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawString(AttributedCharacterIterator, float, float) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
drawString(AttributedCharacterIterator, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
DrawStringCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
DrawStringCommand(String, double, double) - Constructor for class org.xbib.graphics.io.vector.commands.DrawStringCommand
+
 
+
+

E

+
+
EPS - org.xbib.graphics.io.vector.VectorGraphicsFormat
+
 
+
EPSGraphics2D - Class in org.xbib.graphics.io.vector.eps
+
+
Graphics2D implementation that saves all operations to a string + in the Encapsulated PostScript® (EPS) format.
+
+
EPSGraphics2D(double, double, double, double) - Constructor for class org.xbib.graphics.io.vector.eps.EPSGraphics2D
+
+
Initializes a new VectorGraphics2D pipeline for translating Graphics2D + commands to EPS data.
+
+
EPSProcessor - Class in org.xbib.graphics.io.vector.eps
+
 
+
EPSProcessor() - Constructor for class org.xbib.graphics.io.vector.eps.EPSProcessor
+
 
+
EPSProcessorResult - Class in org.xbib.graphics.io.vector.eps
+
 
+
EPSProcessorResult(PageSize) - Constructor for class org.xbib.graphics.io.vector.eps.EPSProcessorResult
+
 
+
equals(Shape, Shape) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
 
+
equals(Object) - Method in class org.xbib.graphics.io.vector.Command
+
 
+
equals(Object) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
+

F

+
+
fill(Shape) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
fillArc(int, int, int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
fillOval(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
FillPaintedShapeAsImageFilter - Class in org.xbib.graphics.io.vector.filters
+
 
+
FillPaintedShapeAsImageFilter(Iterable<Command<?>>) - Constructor for class org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter
+
 
+
fillPolygon(int[], int[], int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
fillPolygon(Polygon) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
fillRect(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
fillRoundRect(int, int, int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
FillShapeCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
FillShapeCommand(Shape) - Constructor for class org.xbib.graphics.io.vector.commands.FillShapeCommand
+
 
+
filter(BufferedImage, BufferedImage) - Method in class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
filter(Command<?>) - Method in class org.xbib.graphics.io.vector.filters.AbsoluteToRelativeTransformsFilter
+
 
+
filter(Command<?>) - Method in class org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter
+
 
+
filter(Command<?>) - Method in class org.xbib.graphics.io.vector.filters.Filter
+
 
+
filter(Command<?>) - Method in class org.xbib.graphics.io.vector.filters.GroupingFilter
+
 
+
filter(Command<?>) - Method in class org.xbib.graphics.io.vector.filters.OptimizeFilter
+
 
+
Filter - Class in org.xbib.graphics.io.vector.filters
+
 
+
Filter(Iterable<Command<?>>) - Constructor for class org.xbib.graphics.io.vector.filters.Filter
+
 
+
FlateEncodeStream - Class in org.xbib.graphics.io.vector.util
+
 
+
FlateEncodeStream(OutputStream) - Constructor for class org.xbib.graphics.io.vector.util.FlateEncodeStream
+
 
+
flush() - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
format(Number) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Returns a formatted string of the specified number.
+
+
format(Object) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Returns a formatted string of the specified object.
+
+
format(String, Object...) - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
FormattingWriter - Class in org.xbib.graphics.io.vector.util
+
 
+
FormattingWriter(OutputStream, String, String) - Constructor for class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
+

G

+
+
GeneratedPayload - Class in org.xbib.graphics.io.vector.pdf
+
 
+
GeneratedPayload(boolean) - Constructor for class org.xbib.graphics.io.vector.pdf.GeneratedPayload
+
 
+
generatePayload() - Method in class org.xbib.graphics.io.vector.pdf.GeneratedPayload
+
 
+
generatePayload() - Method in class org.xbib.graphics.io.vector.pdf.SizePayload
+
 
+
getAlphaImage(BufferedImage) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
 
+
getBackground() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getBackground() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getBounds2D(BufferedImage) - Method in class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
getBytes() - Method in class org.xbib.graphics.io.vector.pdf.GeneratedPayload
+
 
+
getBytes() - Method in class org.xbib.graphics.io.vector.pdf.Payload
+
 
+
getBytes() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getCenterX() - Method in class org.xbib.graphics.io.vector.commands.RotateCommand
+
 
+
getCenterY() - Method in class org.xbib.graphics.io.vector.commands.RotateCommand
+
 
+
getClip() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getClip() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getClipBounds() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getColor() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getColor() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getCommands() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getComposite() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getComposite() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getDeltaX() - Method in class org.xbib.graphics.io.vector.commands.TranslateCommand
+
 
+
getDeltaY() - Method in class org.xbib.graphics.io.vector.commands.TranslateCommand
+
 
+
getDeviceConfiguration() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getFont() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getFont() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getFontMetrics(Font) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getFontRenderContext() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getHeight() - Method in class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
getHeight() - Method in class org.xbib.graphics.io.vector.PageSize
+
 
+
getHints() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getId() - Method in class org.xbib.graphics.io.vector.util.VectorHints.Value
+
 
+
getId(Font) - Method in class org.xbib.graphics.io.vector.pdf.Resources
+
 
+
getId(Double) - Method in class org.xbib.graphics.io.vector.pdf.Resources
+
 
+
getId(PDFObject) - Method in class org.xbib.graphics.io.vector.pdf.Resources
+
 
+
getImage() - Method in class org.xbib.graphics.io.vector.util.ImageDataStream
+
 
+
getImageHeight() - Method in class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
getImageWidth() - Method in class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
getIndex() - Method in class org.xbib.graphics.io.vector.util.VectorHints.Key
+
 
+
getIndex() - Method in class org.xbib.graphics.io.vector.util.VectorHints.Value
+
 
+
getInterleaving() - Method in class org.xbib.graphics.io.vector.util.ImageDataStream
+
 
+
getKey() - Method in class org.xbib.graphics.io.vector.commands.SetHintCommand
+
 
+
getLandscape() - Method in class org.xbib.graphics.io.vector.PageSize
+
 
+
getPageSize() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getPaint() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getPaint() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getPhysicalFont(Font) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
 
+
getPhysicalFont(Font, String) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
+
Try to guess physical font from the properties of a logical font, like + "Dialog", "Serif", "Monospaced" etc.
+
+
getPoint2D(Point2D, Point2D) - Method in class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
getPortrait() - Method in class org.xbib.graphics.io.vector.PageSize
+
 
+
getRenderingHint(RenderingHints.Key) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getRenderingHints() - Method in class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
getRenderingHints() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getScaleX() - Method in class org.xbib.graphics.io.vector.commands.ScaleCommand
+
 
+
getScaleY() - Method in class org.xbib.graphics.io.vector.commands.ScaleCommand
+
 
+
getShearX() - Method in class org.xbib.graphics.io.vector.commands.ShearCommand
+
 
+
getShearY() - Method in class org.xbib.graphics.io.vector.commands.ShearCommand
+
 
+
getStroke() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getStroke() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getTheta() - Method in class org.xbib.graphics.io.vector.commands.RotateCommand
+
 
+
getTransform() - Method in class org.xbib.graphics.io.vector.commands.TransformCommand
+
 
+
getTransform() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getTransform() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getValue() - Method in class org.xbib.graphics.io.vector.Command
+
 
+
getWidth() - Method in class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
getWidth() - Method in class org.xbib.graphics.io.vector.PageSize
+
 
+
getX() - Method in class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
getX() - Method in class org.xbib.graphics.io.vector.commands.DrawStringCommand
+
 
+
getX() - Method in class org.xbib.graphics.io.vector.PageSize
+
 
+
getXorMode() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
getXORMode() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
getY() - Method in class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
getY() - Method in class org.xbib.graphics.io.vector.commands.DrawStringCommand
+
 
+
getY() - Method in class org.xbib.graphics.io.vector.PageSize
+
 
+
GraphicsState - Class in org.xbib.graphics.io.vector
+
 
+
GraphicsState() - Constructor for class org.xbib.graphics.io.vector.GraphicsState
+
 
+
GraphicsUtils - Class in org.xbib.graphics.io.vector.util
+
+
Abstract class that contains utility functions for working with graphics.
+
+
GraphicsUtils() - Constructor for class org.xbib.graphics.io.vector.util.GraphicsUtils
+
+
Default constructor that prevents creation of class.
+
+
Group - Class in org.xbib.graphics.io.vector.commands
+
 
+
Group() - Constructor for class org.xbib.graphics.io.vector.commands.Group
+
 
+
GroupingFilter - Class in org.xbib.graphics.io.vector.filters
+
 
+
GroupingFilter(Iterable<Command<?>>) - Constructor for class org.xbib.graphics.io.vector.filters.GroupingFilter
+
 
+
+

H

+
+
handle(Command<?>) - Method in class org.xbib.graphics.io.vector.eps.EPSProcessorResult
+
 
+
handle(Command<?>) - Method in class org.xbib.graphics.io.vector.pdf.PDFProcessorResult
+
 
+
handle(Command<?>) - Method in interface org.xbib.graphics.io.vector.ProcessorResult
+
 
+
handle(Command<?>) - Method in class org.xbib.graphics.io.vector.svg.SVGProcessorResult
+
 
+
hasAlpha(Image) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
+
This method returns true if the specified image has the + possibility to store transparent pixels.
+
+
hashCode() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
hasNext() - Method in class org.xbib.graphics.io.vector.filters.Filter
+
 
+
hasNext() - Method in class org.xbib.graphics.io.vector.filters.GroupingFilter
+
 
+
hasNext() - Method in class org.xbib.graphics.io.vector.filters.OptimizeFilter
+
 
+
hit(Rectangle, Shape, boolean) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
+

I

+
+
id - Variable in class org.xbib.graphics.io.vector.pdf.PDFObject
+
 
+
ImageDataStream - Class in org.xbib.graphics.io.vector.util
+
 
+
ImageDataStream(BufferedImage, ImageDataStream.Interleaving) - Constructor for class org.xbib.graphics.io.vector.util.ImageDataStream
+
 
+
ImageDataStream.Interleaving - Enum in org.xbib.graphics.io.vector.util
+
 
+
isCompatibleKey(RenderingHints.Key) - Method in class org.xbib.graphics.io.vector.util.VectorHints.Value
+
 
+
isCompatibleValue(Object) - Method in class org.xbib.graphics.io.vector.util.VectorHints.Key
+
 
+
isDefault() - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
isDisposed() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
isGrouped(Command<?>) - Method in class org.xbib.graphics.io.vector.filters.GroupingFilter
+
 
+
isGrouped(Command<?>) - Method in class org.xbib.graphics.io.vector.filters.StateChangeGroupingFilter
+
 
+
isInverted() - Method in class org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
 
+
isStream() - Method in class org.xbib.graphics.io.vector.pdf.Payload
+
 
+
iterator() - Method in class org.xbib.graphics.io.vector.filters.Filter
+
 
+
+

J

+
+
join(String, double[]) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Returns a string containing all double numbers concatenated by a + specified separator.
+
+
join(String, float[]) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Returns a string containing all float numbers concatenated by a + specified separator.
+
+
join(String, Object[]) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Returns a string containing all elements concatenated by a specified + separator.
+
+
join(String, List<?>) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Returns a string containing all elements concatenated by a specified + separator.
+
+
+

K

+
+
Key(int, String) - Constructor for class org.xbib.graphics.io.vector.util.VectorHints.Key
+
 
+
KEY_EXPORT - Static variable in class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
KEY_TEXT - Static variable in class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
+

L

+
+
LEDGER - Static variable in class org.xbib.graphics.io.vector.PageSize
+
 
+
LEGAL - Static variable in class org.xbib.graphics.io.vector.PageSize
+
 
+
LETTER - Static variable in class org.xbib.graphics.io.vector.PageSize
+
 
+
LineWrapOutputStream - Class in org.xbib.graphics.io.vector.util
+
 
+
LineWrapOutputStream(OutputStream, int) - Constructor for class org.xbib.graphics.io.vector.util.LineWrapOutputStream
+
 
+
LineWrapOutputStream(OutputStream, int, String) - Constructor for class org.xbib.graphics.io.vector.util.LineWrapOutputStream
+
 
+
+

M

+
+
map(K[], V[]) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Creates a mapping from two arrays, one with keys, one with values.
+
+
max(int...) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Returns the largest of all specified values.
+
+
+

N

+
+
next() - Method in class org.xbib.graphics.io.vector.filters.AbsoluteToRelativeTransformsFilter
+
 
+
next() - Method in class org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter
+
 
+
next() - Method in class org.xbib.graphics.io.vector.filters.Filter
+
 
+
next() - Method in class org.xbib.graphics.io.vector.filters.GroupingFilter
+
 
+
next() - Method in class org.xbib.graphics.io.vector.filters.OptimizeFilter
+
 
+
+

O

+
+
OptimizeFilter - Class in org.xbib.graphics.io.vector.filters
+
 
+
OptimizeFilter(Iterable<Command<?>>) - Constructor for class org.xbib.graphics.io.vector.filters.OptimizeFilter
+
 
+
org.xbib.graphics.io.vector - module org.xbib.graphics.io.vector
+
 
+
org.xbib.graphics.io.vector - package org.xbib.graphics.io.vector
+
 
+
org.xbib.graphics.io.vector.commands - package org.xbib.graphics.io.vector.commands
+
 
+
org.xbib.graphics.io.vector.eps - package org.xbib.graphics.io.vector.eps
+
 
+
org.xbib.graphics.io.vector.filters - package org.xbib.graphics.io.vector.filters
+
 
+
org.xbib.graphics.io.vector.pdf - package org.xbib.graphics.io.vector.pdf
+
 
+
org.xbib.graphics.io.vector.svg - package org.xbib.graphics.io.vector.svg
+
 
+
org.xbib.graphics.io.vector.util - package org.xbib.graphics.io.vector.util
+
 
+
+

P

+
+
PageSize - Class in org.xbib.graphics.io.vector
+
 
+
PageSize(double, double) - Constructor for class org.xbib.graphics.io.vector.PageSize
+
 
+
PageSize(double, double, double, double) - Constructor for class org.xbib.graphics.io.vector.PageSize
+
 
+
PageSize(Rectangle2D) - Constructor for class org.xbib.graphics.io.vector.PageSize
+
 
+
payload - Variable in class org.xbib.graphics.io.vector.pdf.PDFObject
+
 
+
Payload - Class in org.xbib.graphics.io.vector.pdf
+
 
+
Payload(boolean) - Constructor for class org.xbib.graphics.io.vector.pdf.Payload
+
 
+
PDF - org.xbib.graphics.io.vector.VectorGraphicsFormat
+
 
+
PDFGraphics2D - Class in org.xbib.graphics.io.vector.pdf
+
+
Graphics2D implementation that saves all operations to a string + in the Portable Document Format (PDF).
+
+
PDFGraphics2D(double, double, double, double) - Constructor for class org.xbib.graphics.io.vector.pdf.PDFGraphics2D
+
+
Initializes a new VectorGraphics2D pipeline for translating Graphics2D + commands to PDF data.
+
+
PDFObject - Class in org.xbib.graphics.io.vector.pdf
+
 
+
PDFObject(int, int, Map<String, Object>, Payload) - Constructor for class org.xbib.graphics.io.vector.pdf.PDFObject
+
 
+
PDFProcessor - Class in org.xbib.graphics.io.vector.pdf
+
 
+
PDFProcessor() - Constructor for class org.xbib.graphics.io.vector.pdf.PDFProcessor
+
 
+
PDFProcessor(boolean) - Constructor for class org.xbib.graphics.io.vector.pdf.PDFProcessor
+
 
+
PDFProcessorResult - Class in org.xbib.graphics.io.vector.pdf
+
 
+
PDFProcessorResult(PageSize) - Constructor for class org.xbib.graphics.io.vector.pdf.PDFProcessorResult
+
 
+
process(Iterable<Command<?>>, PageSize) - Method in class org.xbib.graphics.io.vector.eps.EPSProcessor
+
 
+
process(Iterable<Command<?>>, PageSize) - Method in class org.xbib.graphics.io.vector.pdf.PDFProcessor
+
 
+
process(Iterable<Command<?>>, PageSize) - Method in interface org.xbib.graphics.io.vector.Processor
+
 
+
process(Iterable<Command<?>>, PageSize) - Method in class org.xbib.graphics.io.vector.svg.SVGProcessor
+
 
+
Processor - Interface in org.xbib.graphics.io.vector
+
 
+
ProcessorResult - Interface in org.xbib.graphics.io.vector
+
 
+
+

R

+
+
read() - Method in class org.xbib.graphics.io.vector.util.ImageDataStream
+
 
+
remove() - Method in class org.xbib.graphics.io.vector.filters.Filter
+
 
+
Resources - Class in org.xbib.graphics.io.vector.pdf
+
 
+
Resources(int, int) - Constructor for class org.xbib.graphics.io.vector.pdf.Resources
+
 
+
rotate(double) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
rotate(double, double, double) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
RotateCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
RotateCommand(double, double, double) - Constructor for class org.xbib.graphics.io.vector.commands.RotateCommand
+
 
+
ROW - org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving
+
 
+
+

S

+
+
SAMPLE - org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving
+
 
+
scale(double, double) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
ScaleCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
ScaleCommand(double, double) - Constructor for class org.xbib.graphics.io.vector.commands.ScaleCommand
+
 
+
setBackground(Color) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setBackground(Color) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetBackgroundCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetBackgroundCommand(Color) - Constructor for class org.xbib.graphics.io.vector.commands.SetBackgroundCommand
+
 
+
setClip(int, int, int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
setClip(Shape) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setClip(Shape) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetClipCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetClipCommand(Shape) - Constructor for class org.xbib.graphics.io.vector.commands.SetClipCommand
+
 
+
setColor(Color) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setColor(Color) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetColorCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetColorCommand(Color) - Constructor for class org.xbib.graphics.io.vector.commands.SetColorCommand
+
 
+
setComposite(Composite) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setComposite(Composite) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetCompositeCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetCompositeCommand(Composite) - Constructor for class org.xbib.graphics.io.vector.commands.SetCompositeCommand
+
 
+
setCompressed(boolean) - Method in class org.xbib.graphics.io.vector.pdf.PDFProcessorResult
+
 
+
setFont(Font) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setFont(Font) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetFontCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetFontCommand(Font) - Constructor for class org.xbib.graphics.io.vector.commands.SetFontCommand
+
 
+
SetHintCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetHintCommand(Object, Object) - Constructor for class org.xbib.graphics.io.vector.commands.SetHintCommand
+
 
+
setPaint(Paint) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setPaint(Paint) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetPaintCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetPaintCommand(Paint) - Constructor for class org.xbib.graphics.io.vector.commands.SetPaintCommand
+
 
+
setPaintMode() - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
setRenderingHint(RenderingHints.Key, Object) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
setRenderingHints(Map<?, ?>) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
setStroke(Stroke) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setStroke(Stroke) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetStrokeCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetStrokeCommand(Stroke) - Constructor for class org.xbib.graphics.io.vector.commands.SetStrokeCommand
+
 
+
setTransform(AffineTransform) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setTransform(AffineTransform) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetTransformCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetTransformCommand(AffineTransform) - Constructor for class org.xbib.graphics.io.vector.commands.SetTransformCommand
+
 
+
setXorMode(Color) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
setXORMode(Color) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
SetXORModeCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
SetXORModeCommand(Color) - Constructor for class org.xbib.graphics.io.vector.commands.SetXORModeCommand
+
 
+
shear(double, double) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
ShearCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
ShearCommand(double, double) - Constructor for class org.xbib.graphics.io.vector.commands.ShearCommand
+
 
+
SizePayload - Class in org.xbib.graphics.io.vector.pdf
+
 
+
SizePayload(PDFObject, String, boolean) - Constructor for class org.xbib.graphics.io.vector.pdf.SizePayload
+
 
+
STANDARD_EOL - Static variable in class org.xbib.graphics.io.vector.util.LineWrapOutputStream
+
 
+
StateChangeGroupingFilter - Class in org.xbib.graphics.io.vector.filters
+
 
+
StateChangeGroupingFilter(Iterable<Command<?>>) - Constructor for class org.xbib.graphics.io.vector.filters.StateChangeGroupingFilter
+
 
+
StateCommand<T> - Class in org.xbib.graphics.io.vector.commands
+
 
+
StateCommand(T) - Constructor for class org.xbib.graphics.io.vector.commands.StateCommand
+
 
+
stripTrailing(String, String) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Removes the specified trailing pattern from a string.
+
+
SVG - org.xbib.graphics.io.vector.VectorGraphicsFormat
+
 
+
SVGGraphics2D - Class in org.xbib.graphics.io.vector.svg
+
+
Graphics2D implementation that saves all operations to a string + in the Scaled Vector Graphics (SVG) format.
+
+
SVGGraphics2D(double, double, double, double) - Constructor for class org.xbib.graphics.io.vector.svg.SVGGraphics2D
+
+
Initializes a new VectorGraphics2D pipeline for translating Graphics2D + commands to SVG data.
+
+
SVGProcessor - Class in org.xbib.graphics.io.vector.svg
+
 
+
SVGProcessor() - Constructor for class org.xbib.graphics.io.vector.svg.SVGProcessor
+
 
+
SVGProcessorResult - Class in org.xbib.graphics.io.vector.svg
+
 
+
SVGProcessorResult(PageSize) - Constructor for class org.xbib.graphics.io.vector.svg.SVGProcessorResult
+
 
+
+

T

+
+
TABLOID - Static variable in class org.xbib.graphics.io.vector.PageSize
+
 
+
tell() - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
toBufferedImage(Image) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
+
This method returns a buffered image with the contents of an image.
+
+
toBufferedImage(RenderedImage) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
+
Converts an arbitrary image to a BufferedImage.
+
+
toString() - Method in class org.xbib.graphics.io.vector.Command
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.commands.DrawImageCommand
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.commands.DrawStringCommand
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.commands.RotateCommand
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.commands.ScaleCommand
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.commands.SetHintCommand
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.commands.ShearCommand
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.commands.TranslateCommand
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.svg.SVGProcessorResult
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.util.VectorHints.Key
+
 
+
toString() - Method in class org.xbib.graphics.io.vector.util.VectorHints.Value
+
 
+
toString(PDFObject) - Static method in class org.xbib.graphics.io.vector.pdf.PDFProcessorResult
+
 
+
transfer(InputStream, OutputStream, int) - Static method in class org.xbib.graphics.io.vector.util.DataUtils
+
+
Copies data from an input stream to an output stream using a buffer of + specified size.
+
+
transform(AffineTransform) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
TransformCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
TransformCommand(AffineTransform) - Constructor for class org.xbib.graphics.io.vector.commands.TransformCommand
+
 
+
transformShape(Shape) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
translate(double, double) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
translate(int, int) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
TranslateCommand - Class in org.xbib.graphics.io.vector.commands
+
 
+
TranslateCommand(double, double) - Constructor for class org.xbib.graphics.io.vector.commands.TranslateCommand
+
 
+
+

U

+
+
untransformShape(Shape) - Method in class org.xbib.graphics.io.vector.GraphicsState
+
 
+
usesAlpha(Image) - Static method in class org.xbib.graphics.io.vector.util.GraphicsUtils
+
+
This method returns true if the specified image has at least one + pixel that is not fully opaque.
+
+
+

V

+
+
Value(VectorHints.Key, int, String) - Constructor for class org.xbib.graphics.io.vector.util.VectorHints.Value
+
 
+
VALUE_EXPORT_QUALITY - Static variable in class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
VALUE_EXPORT_READABILITY - Static variable in class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
VALUE_EXPORT_SIZE - Static variable in class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
VALUE_TEXT_DEFAULT - Static variable in class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
VALUE_TEXT_VECTOR - Static variable in class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
valueOf(String) - Static method in enum org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.xbib.graphics.io.vector.VectorGraphicsFormat
+
+
Returns the enum constant of this type with the specified name.
+
+
values() - Static method in enum org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.xbib.graphics.io.vector.VectorGraphicsFormat
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
VectorGraphics2D - Class in org.xbib.graphics.io.vector
+
+
Base for classes that want to implement vector export.
+
+
VectorGraphics2D() - Constructor for class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
VectorGraphics2D(Processor, PageSize) - Constructor for class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
VectorGraphicsFormat - Enum in org.xbib.graphics.io.vector
+
 
+
VectorHints - Class in org.xbib.graphics.io.vector.util
+
 
+
VectorHints() - Constructor for class org.xbib.graphics.io.vector.util.VectorHints
+
 
+
VectorHints.Key - Class in org.xbib.graphics.io.vector.util
+
 
+
VectorHints.Value - Class in org.xbib.graphics.io.vector.util
+
 
+
version - Variable in class org.xbib.graphics.io.vector.pdf.PDFObject
+
 
+
+

W

+
+
WITHOUT_ALPHA - org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving
+
 
+
write(int) - Method in class org.xbib.graphics.io.vector.pdf.GeneratedPayload
+
 
+
write(int) - Method in class org.xbib.graphics.io.vector.pdf.Payload
+
 
+
write(int) - Method in class org.xbib.graphics.io.vector.util.ASCII85EncodeStream
+
 
+
write(int) - Method in class org.xbib.graphics.io.vector.util.Base64EncodeStream
+
 
+
write(int) - Method in class org.xbib.graphics.io.vector.util.LineWrapOutputStream
+
 
+
write(OutputStream) - Method in class org.xbib.graphics.io.vector.eps.EPSProcessorResult
+
 
+
write(OutputStream) - Method in class org.xbib.graphics.io.vector.pdf.PDFProcessorResult
+
 
+
write(OutputStream) - Method in interface org.xbib.graphics.io.vector.ProcessorResult
+
 
+
write(OutputStream) - Method in class org.xbib.graphics.io.vector.svg.SVGProcessorResult
+
 
+
write(Number) - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
write(String) - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
writeln() - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
writeln(Number) - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
writeln(String) - Method in class org.xbib.graphics.io.vector.util.FormattingWriter
+
 
+
writeTo(OutputStream) - Method in class org.xbib.graphics.io.vector.VectorGraphics2D
+
 
+
+A B C D E F G H I J K L M N O P R S T U V W 
All Classes|All Packages
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/index.html b/io-vector/build/docs/javadoc/index.html new file mode 100644 index 0000000..49ed4e7 --- /dev/null +++ b/io-vector/build/docs/javadoc/index.html @@ -0,0 +1,25 @@ + + + + + +io-vector 3.0.0 API + + + + + + + + + +
+ +

org.xbib.graphics.io.vector/module-summary.html

+
+ + diff --git a/io-vector/build/docs/javadoc/jquery-ui.overrides.css b/io-vector/build/docs/javadoc/jquery-ui.overrides.css new file mode 100644 index 0000000..f89acb6 --- /dev/null +++ b/io-vector/build/docs/javadoc/jquery-ui.overrides.css @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + /* Overrides the color of selection used in jQuery UI */ + background: #F8981D; +} diff --git a/io-vector/build/docs/javadoc/member-search-index.js b/io-vector/build/docs/javadoc/member-search-index.js new file mode 100644 index 0000000..966ff1d --- /dev/null +++ b/io-vector/build/docs/javadoc/member-search-index.js @@ -0,0 +1 @@ +memberSearchIndex = [{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"A3"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"A4"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"A5"},{"p":"org.xbib.graphics.io.vector.filters","c":"AbsoluteToRelativeTransformsFilter","l":"AbsoluteToRelativeTransformsFilter(Iterable>)","u":"%3Cinit%3E(java.lang.Iterable)"},{"p":"org.xbib.graphics.io.vector.commands","c":"Group","l":"add(Command)","u":"add(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Payload","l":"addFilter(Class)","u":"addFilter(java.lang.Class)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"addRenderingHints(Map)","u":"addRenderingHints(java.util.Map)"},{"p":"org.xbib.graphics.io.vector.commands","c":"AffineTransformCommand","l":"AffineTransformCommand(AffineTransform)","u":"%3Cinit%3E(java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream.Interleaving","l":"ALPHA_ONLY"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"AlphaToMaskOp()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"AlphaToMaskOp(boolean)","u":"%3Cinit%3E(boolean)"},{"p":"org.xbib.graphics.io.vector.util","c":"ASCII85EncodeStream","l":"ASCII85EncodeStream(OutputStream)","u":"%3Cinit%3E(java.io.OutputStream)"},{"p":"org.xbib.graphics.io.vector.util","c":"ASCII85EncodeStream","l":"ASCII85EncodeStream(OutputStream, String, String)","u":"%3Cinit%3E(java.io.OutputStream,java.lang.String,java.lang.String)"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"asList(double[])"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"asList(float[])"},{"p":"org.xbib.graphics.io.vector.util","c":"Base64EncodeStream","l":"Base64EncodeStream(OutputStream)","u":"%3Cinit%3E(java.io.OutputStream)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"clearRect(int, int, int, int)","u":"clearRect(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"clip(Shape)","u":"clip(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"clipRect(int, int, int, int)","u":"clipRect(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"clone()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"clone()"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"clone(Shape)","u":"clone(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"ProcessorResult","l":"close()"},{"p":"org.xbib.graphics.io.vector.eps","c":"EPSProcessorResult","l":"close()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessorResult","l":"close()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Payload","l":"close()"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGProcessorResult","l":"close()"},{"p":"org.xbib.graphics.io.vector.util","c":"ASCII85EncodeStream","l":"close()"},{"p":"org.xbib.graphics.io.vector.util","c":"Base64EncodeStream","l":"close()"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"close()"},{"p":"org.xbib.graphics.io.vector","c":"Command","l":"Command(T)","u":"%3Cinit%3E(T)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"copyArea(int, int, int, int, int, int)","u":"copyArea(int,int,int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"create()"},{"p":"org.xbib.graphics.io.vector.commands","c":"CreateCommand","l":"CreateCommand(VectorGraphics2D)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.VectorGraphics2D)"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"createCompatibleDestImage(BufferedImage, ColorModel)","u":"createCompatibleDestImage(java.awt.image.BufferedImage,java.awt.image.ColorModel)"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"DataUtils()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_BACKGROUND"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_CLIP"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_COLOR"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_COMPOSITE"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_FONT"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_PAINT"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_STROKE"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_TRANSFORM"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"DEFAULT_XOR_MODE"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFObject","l":"dict"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"dispose()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DisposeCommand","l":"DisposeCommand(VectorGraphics2D)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.VectorGraphics2D)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"draw(Shape)","u":"draw(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawArc(int, int, int, int, int, int)","u":"drawArc(int,int,int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawGlyphVector(GlyphVector, float, float)","u":"drawGlyphVector(java.awt.font.GlyphVector,float,float)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(BufferedImage, BufferedImageOp, int, int)","u":"drawImage(java.awt.image.BufferedImage,java.awt.image.BufferedImageOp,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(Image, AffineTransform, ImageObserver)","u":"drawImage(java.awt.Image,java.awt.geom.AffineTransform,java.awt.image.ImageObserver)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(Image, int, int, Color, ImageObserver)","u":"drawImage(java.awt.Image,int,int,java.awt.Color,java.awt.image.ImageObserver)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(Image, int, int, ImageObserver)","u":"drawImage(java.awt.Image,int,int,java.awt.image.ImageObserver)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(Image, int, int, int, int, Color, ImageObserver)","u":"drawImage(java.awt.Image,int,int,int,int,java.awt.Color,java.awt.image.ImageObserver)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(Image, int, int, int, int, ImageObserver)","u":"drawImage(java.awt.Image,int,int,int,int,java.awt.image.ImageObserver)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(Image, int, int, int, int, int, int, int, int, Color, ImageObserver)","u":"drawImage(java.awt.Image,int,int,int,int,int,int,int,int,java.awt.Color,java.awt.image.ImageObserver)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawImage(Image, int, int, int, int, int, int, int, int, ImageObserver)","u":"drawImage(java.awt.Image,int,int,int,int,int,int,int,int,java.awt.image.ImageObserver)"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"DrawImageCommand(Image, int, int, double, double, double, double)","u":"%3Cinit%3E(java.awt.Image,int,int,double,double,double,double)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawLine(int, int, int, int)","u":"drawLine(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawOval(int, int, int, int)","u":"drawOval(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawPolygon(int[], int[], int)","u":"drawPolygon(int[],int[],int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawPolygon(Polygon)","u":"drawPolygon(java.awt.Polygon)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawPolyline(int[], int[], int)","u":"drawPolyline(int[],int[],int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawRect(int, int, int, int)","u":"drawRect(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawRenderableImage(RenderableImage, AffineTransform)","u":"drawRenderableImage(java.awt.image.renderable.RenderableImage,java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawRenderedImage(RenderedImage, AffineTransform)","u":"drawRenderedImage(java.awt.image.RenderedImage,java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawRoundRect(int, int, int, int, int, int)","u":"drawRoundRect(int,int,int,int,int,int)"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawShapeCommand","l":"DrawShapeCommand(Shape)","u":"%3Cinit%3E(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawString(AttributedCharacterIterator, float, float)","u":"drawString(java.text.AttributedCharacterIterator,float,float)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawString(AttributedCharacterIterator, int, int)","u":"drawString(java.text.AttributedCharacterIterator,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawString(String, float, float)","u":"drawString(java.lang.String,float,float)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"drawString(String, int, int)","u":"drawString(java.lang.String,int,int)"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawStringCommand","l":"DrawStringCommand(String, double, double)","u":"%3Cinit%3E(java.lang.String,double,double)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphicsFormat","l":"EPS"},{"p":"org.xbib.graphics.io.vector.eps","c":"EPSGraphics2D","l":"EPSGraphics2D(double, double, double, double)","u":"%3Cinit%3E(double,double,double,double)"},{"p":"org.xbib.graphics.io.vector.eps","c":"EPSProcessor","l":"EPSProcessor()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.eps","c":"EPSProcessorResult","l":"EPSProcessorResult(PageSize)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector","c":"Command","l":"equals(Object)","u":"equals(java.lang.Object)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"equals(Object)","u":"equals(java.lang.Object)"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"equals(Shape, Shape)","u":"equals(java.awt.Shape,java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"fill(Shape)","u":"fill(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"fillArc(int, int, int, int, int, int)","u":"fillArc(int,int,int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"fillOval(int, int, int, int)","u":"fillOval(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector.filters","c":"FillPaintedShapeAsImageFilter","l":"FillPaintedShapeAsImageFilter(Iterable>)","u":"%3Cinit%3E(java.lang.Iterable)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"fillPolygon(int[], int[], int)","u":"fillPolygon(int[],int[],int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"fillPolygon(Polygon)","u":"fillPolygon(java.awt.Polygon)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"fillRect(int, int, int, int)","u":"fillRect(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"fillRoundRect(int, int, int, int, int, int)","u":"fillRoundRect(int,int,int,int,int,int)"},{"p":"org.xbib.graphics.io.vector.commands","c":"FillShapeCommand","l":"FillShapeCommand(Shape)","u":"%3Cinit%3E(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"filter(BufferedImage, BufferedImage)","u":"filter(java.awt.image.BufferedImage,java.awt.image.BufferedImage)"},{"p":"org.xbib.graphics.io.vector.filters","c":"AbsoluteToRelativeTransformsFilter","l":"filter(Command)","u":"filter(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.filters","c":"FillPaintedShapeAsImageFilter","l":"filter(Command)","u":"filter(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.filters","c":"Filter","l":"filter(Command)","u":"filter(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.filters","c":"GroupingFilter","l":"filter(Command)","u":"filter(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.filters","c":"OptimizeFilter","l":"filter(Command)","u":"filter(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.filters","c":"Filter","l":"Filter(Iterable>)","u":"%3Cinit%3E(java.lang.Iterable)"},{"p":"org.xbib.graphics.io.vector.util","c":"FlateEncodeStream","l":"FlateEncodeStream(OutputStream)","u":"%3Cinit%3E(java.io.OutputStream)"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"flush()"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"format(Number)","u":"format(java.lang.Number)"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"format(Object)","u":"format(java.lang.Object)"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"format(String, Object...)","u":"format(java.lang.String,java.lang.Object...)"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"FormattingWriter(OutputStream, String, String)","u":"%3Cinit%3E(java.io.OutputStream,java.lang.String,java.lang.String)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"GeneratedPayload","l":"GeneratedPayload(boolean)","u":"%3Cinit%3E(boolean)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"GeneratedPayload","l":"generatePayload()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"SizePayload","l":"generatePayload()"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"getAlphaImage(BufferedImage)","u":"getAlphaImage(java.awt.image.BufferedImage)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getBackground()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getBackground()"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"getBounds2D(BufferedImage)","u":"getBounds2D(java.awt.image.BufferedImage)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getBytes()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"GeneratedPayload","l":"getBytes()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Payload","l":"getBytes()"},{"p":"org.xbib.graphics.io.vector.commands","c":"RotateCommand","l":"getCenterX()"},{"p":"org.xbib.graphics.io.vector.commands","c":"RotateCommand","l":"getCenterY()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getClip()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getClip()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getClipBounds()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getColor()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getColor()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getCommands()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getComposite()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getComposite()"},{"p":"org.xbib.graphics.io.vector.commands","c":"TranslateCommand","l":"getDeltaX()"},{"p":"org.xbib.graphics.io.vector.commands","c":"TranslateCommand","l":"getDeltaY()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getDeviceConfiguration()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getFont()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getFont()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getFontMetrics(Font)","u":"getFontMetrics(java.awt.Font)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getFontRenderContext()"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"getHeight()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"getHeight()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getHints()"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Value","l":"getId()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Resources","l":"getId(Double)","u":"getId(java.lang.Double)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Resources","l":"getId(Font)","u":"getId(java.awt.Font)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Resources","l":"getId(PDFObject)","u":"getId(org.xbib.graphics.io.vector.pdf.PDFObject)"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream","l":"getImage()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"getImageHeight()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"getImageWidth()"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Key","l":"getIndex()"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Value","l":"getIndex()"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream","l":"getInterleaving()"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetHintCommand","l":"getKey()"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"getLandscape()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getPageSize()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getPaint()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getPaint()"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"getPhysicalFont(Font)","u":"getPhysicalFont(java.awt.Font)"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"getPhysicalFont(Font, String)","u":"getPhysicalFont(java.awt.Font,java.lang.String)"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"getPoint2D(Point2D, Point2D)","u":"getPoint2D(java.awt.geom.Point2D,java.awt.geom.Point2D)"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"getPortrait()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getRenderingHint(RenderingHints.Key)","u":"getRenderingHint(java.awt.RenderingHints.Key)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getRenderingHints()"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"getRenderingHints()"},{"p":"org.xbib.graphics.io.vector.commands","c":"ScaleCommand","l":"getScaleX()"},{"p":"org.xbib.graphics.io.vector.commands","c":"ScaleCommand","l":"getScaleY()"},{"p":"org.xbib.graphics.io.vector.commands","c":"ShearCommand","l":"getShearX()"},{"p":"org.xbib.graphics.io.vector.commands","c":"ShearCommand","l":"getShearY()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getStroke()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getStroke()"},{"p":"org.xbib.graphics.io.vector.commands","c":"RotateCommand","l":"getTheta()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getTransform()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getTransform()"},{"p":"org.xbib.graphics.io.vector.commands","c":"TransformCommand","l":"getTransform()"},{"p":"org.xbib.graphics.io.vector","c":"Command","l":"getValue()"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"getWidth()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"getWidth()"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"getX()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"getX()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawStringCommand","l":"getX()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"getXorMode()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"getXORMode()"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"getY()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"getY()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawStringCommand","l":"getY()"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"GraphicsState()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"GraphicsUtils()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.commands","c":"Group","l":"Group()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.filters","c":"GroupingFilter","l":"GroupingFilter(Iterable>)","u":"%3Cinit%3E(java.lang.Iterable)"},{"p":"org.xbib.graphics.io.vector","c":"ProcessorResult","l":"handle(Command)","u":"handle(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.eps","c":"EPSProcessorResult","l":"handle(Command)","u":"handle(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessorResult","l":"handle(Command)","u":"handle(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGProcessorResult","l":"handle(Command)","u":"handle(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"hasAlpha(Image)","u":"hasAlpha(java.awt.Image)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"hashCode()"},{"p":"org.xbib.graphics.io.vector.filters","c":"Filter","l":"hasNext()"},{"p":"org.xbib.graphics.io.vector.filters","c":"GroupingFilter","l":"hasNext()"},{"p":"org.xbib.graphics.io.vector.filters","c":"OptimizeFilter","l":"hasNext()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"hit(Rectangle, Shape, boolean)","u":"hit(java.awt.Rectangle,java.awt.Shape,boolean)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFObject","l":"id"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream","l":"ImageDataStream(BufferedImage, ImageDataStream.Interleaving)","u":"%3Cinit%3E(java.awt.image.BufferedImage,org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving)"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Value","l":"isCompatibleKey(RenderingHints.Key)","u":"isCompatibleKey(java.awt.RenderingHints.Key)"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Key","l":"isCompatibleValue(Object)","u":"isCompatibleValue(java.lang.Object)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"isDefault()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"isDisposed()"},{"p":"org.xbib.graphics.io.vector.filters","c":"GroupingFilter","l":"isGrouped(Command)","u":"isGrouped(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.filters","c":"StateChangeGroupingFilter","l":"isGrouped(Command)","u":"isGrouped(org.xbib.graphics.io.vector.Command)"},{"p":"org.xbib.graphics.io.vector.util","c":"AlphaToMaskOp","l":"isInverted()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Payload","l":"isStream()"},{"p":"org.xbib.graphics.io.vector.filters","c":"Filter","l":"iterator()"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"join(String, double[])","u":"join(java.lang.String,double[])"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"join(String, float[])","u":"join(java.lang.String,float[])"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"join(String, List)","u":"join(java.lang.String,java.util.List)"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"join(String, Object[])","u":"join(java.lang.String,java.lang.Object[])"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"KEY_EXPORT"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"KEY_TEXT"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Key","l":"Key(int, String)","u":"%3Cinit%3E(int,java.lang.String)"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"LEDGER"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"LEGAL"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"LETTER"},{"p":"org.xbib.graphics.io.vector.util","c":"LineWrapOutputStream","l":"LineWrapOutputStream(OutputStream, int)","u":"%3Cinit%3E(java.io.OutputStream,int)"},{"p":"org.xbib.graphics.io.vector.util","c":"LineWrapOutputStream","l":"LineWrapOutputStream(OutputStream, int, String)","u":"%3Cinit%3E(java.io.OutputStream,int,java.lang.String)"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"map(K[], V[])","u":"map(K[],V[])"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"max(int...)"},{"p":"org.xbib.graphics.io.vector.filters","c":"AbsoluteToRelativeTransformsFilter","l":"next()"},{"p":"org.xbib.graphics.io.vector.filters","c":"FillPaintedShapeAsImageFilter","l":"next()"},{"p":"org.xbib.graphics.io.vector.filters","c":"Filter","l":"next()"},{"p":"org.xbib.graphics.io.vector.filters","c":"GroupingFilter","l":"next()"},{"p":"org.xbib.graphics.io.vector.filters","c":"OptimizeFilter","l":"next()"},{"p":"org.xbib.graphics.io.vector.filters","c":"OptimizeFilter","l":"OptimizeFilter(Iterable>)","u":"%3Cinit%3E(java.lang.Iterable)"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"PageSize(double, double)","u":"%3Cinit%3E(double,double)"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"PageSize(double, double, double, double)","u":"%3Cinit%3E(double,double,double,double)"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"PageSize(Rectangle2D)","u":"%3Cinit%3E(java.awt.geom.Rectangle2D)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFObject","l":"payload"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Payload","l":"Payload(boolean)","u":"%3Cinit%3E(boolean)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphicsFormat","l":"PDF"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFGraphics2D","l":"PDFGraphics2D(double, double, double, double)","u":"%3Cinit%3E(double,double,double,double)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFObject","l":"PDFObject(int, int, Map, Payload)","u":"%3Cinit%3E(int,int,java.util.Map,org.xbib.graphics.io.vector.pdf.Payload)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessor","l":"PDFProcessor()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessor","l":"PDFProcessor(boolean)","u":"%3Cinit%3E(boolean)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessorResult","l":"PDFProcessorResult(PageSize)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector","c":"Processor","l":"process(Iterable>, PageSize)","u":"process(java.lang.Iterable,org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector.eps","c":"EPSProcessor","l":"process(Iterable>, PageSize)","u":"process(java.lang.Iterable,org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessor","l":"process(Iterable>, PageSize)","u":"process(java.lang.Iterable,org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGProcessor","l":"process(Iterable>, PageSize)","u":"process(java.lang.Iterable,org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream","l":"read()"},{"p":"org.xbib.graphics.io.vector.filters","c":"Filter","l":"remove()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Resources","l":"Resources(int, int)","u":"%3Cinit%3E(int,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"rotate(double)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"rotate(double, double, double)","u":"rotate(double,double,double)"},{"p":"org.xbib.graphics.io.vector.commands","c":"RotateCommand","l":"RotateCommand(double, double, double)","u":"%3Cinit%3E(double,double,double)"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream.Interleaving","l":"ROW"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream.Interleaving","l":"SAMPLE"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"scale(double, double)","u":"scale(double,double)"},{"p":"org.xbib.graphics.io.vector.commands","c":"ScaleCommand","l":"ScaleCommand(double, double)","u":"%3Cinit%3E(double,double)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setBackground(Color)","u":"setBackground(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setBackground(Color)","u":"setBackground(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetBackgroundCommand","l":"SetBackgroundCommand(Color)","u":"%3Cinit%3E(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setClip(int, int, int, int)","u":"setClip(int,int,int,int)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setClip(Shape)","u":"setClip(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setClip(Shape)","u":"setClip(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetClipCommand","l":"SetClipCommand(Shape)","u":"%3Cinit%3E(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setColor(Color)","u":"setColor(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setColor(Color)","u":"setColor(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetColorCommand","l":"SetColorCommand(Color)","u":"%3Cinit%3E(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setComposite(Composite)","u":"setComposite(java.awt.Composite)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setComposite(Composite)","u":"setComposite(java.awt.Composite)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetCompositeCommand","l":"SetCompositeCommand(Composite)","u":"%3Cinit%3E(java.awt.Composite)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessorResult","l":"setCompressed(boolean)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setFont(Font)","u":"setFont(java.awt.Font)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setFont(Font)","u":"setFont(java.awt.Font)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetFontCommand","l":"SetFontCommand(Font)","u":"%3Cinit%3E(java.awt.Font)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetHintCommand","l":"SetHintCommand(Object, Object)","u":"%3Cinit%3E(java.lang.Object,java.lang.Object)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setPaint(Paint)","u":"setPaint(java.awt.Paint)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setPaint(Paint)","u":"setPaint(java.awt.Paint)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetPaintCommand","l":"SetPaintCommand(Paint)","u":"%3Cinit%3E(java.awt.Paint)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setPaintMode()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setRenderingHint(RenderingHints.Key, Object)","u":"setRenderingHint(java.awt.RenderingHints.Key,java.lang.Object)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setRenderingHints(Map)","u":"setRenderingHints(java.util.Map)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setStroke(Stroke)","u":"setStroke(java.awt.Stroke)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setStroke(Stroke)","u":"setStroke(java.awt.Stroke)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetStrokeCommand","l":"SetStrokeCommand(Stroke)","u":"%3Cinit%3E(java.awt.Stroke)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setTransform(AffineTransform)","u":"setTransform(java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setTransform(AffineTransform)","u":"setTransform(java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetTransformCommand","l":"SetTransformCommand(AffineTransform)","u":"%3Cinit%3E(java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"setXorMode(Color)","u":"setXorMode(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"setXORMode(Color)","u":"setXORMode(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetXORModeCommand","l":"SetXORModeCommand(Color)","u":"%3Cinit%3E(java.awt.Color)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"shear(double, double)","u":"shear(double,double)"},{"p":"org.xbib.graphics.io.vector.commands","c":"ShearCommand","l":"ShearCommand(double, double)","u":"%3Cinit%3E(double,double)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"SizePayload","l":"SizePayload(PDFObject, String, boolean)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.pdf.PDFObject,java.lang.String,boolean)"},{"p":"org.xbib.graphics.io.vector.util","c":"LineWrapOutputStream","l":"STANDARD_EOL"},{"p":"org.xbib.graphics.io.vector.filters","c":"StateChangeGroupingFilter","l":"StateChangeGroupingFilter(Iterable>)","u":"%3Cinit%3E(java.lang.Iterable)"},{"p":"org.xbib.graphics.io.vector.commands","c":"StateCommand","l":"StateCommand(T)","u":"%3Cinit%3E(T)"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"stripTrailing(String, String)","u":"stripTrailing(java.lang.String,java.lang.String)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphicsFormat","l":"SVG"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGGraphics2D","l":"SVGGraphics2D(double, double, double, double)","u":"%3Cinit%3E(double,double,double,double)"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGProcessor","l":"SVGProcessor()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGProcessorResult","l":"SVGProcessorResult(PageSize)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector","c":"PageSize","l":"TABLOID"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"tell()"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"toBufferedImage(Image)","u":"toBufferedImage(java.awt.Image)"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"toBufferedImage(RenderedImage)","u":"toBufferedImage(java.awt.image.RenderedImage)"},{"p":"org.xbib.graphics.io.vector","c":"Command","l":"toString()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawImageCommand","l":"toString()"},{"p":"org.xbib.graphics.io.vector.commands","c":"DrawStringCommand","l":"toString()"},{"p":"org.xbib.graphics.io.vector.commands","c":"RotateCommand","l":"toString()"},{"p":"org.xbib.graphics.io.vector.commands","c":"ScaleCommand","l":"toString()"},{"p":"org.xbib.graphics.io.vector.commands","c":"SetHintCommand","l":"toString()"},{"p":"org.xbib.graphics.io.vector.commands","c":"ShearCommand","l":"toString()"},{"p":"org.xbib.graphics.io.vector.commands","c":"TranslateCommand","l":"toString()"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGProcessorResult","l":"toString()"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Key","l":"toString()"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Value","l":"toString()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessorResult","l":"toString(PDFObject)","u":"toString(org.xbib.graphics.io.vector.pdf.PDFObject)"},{"p":"org.xbib.graphics.io.vector.util","c":"DataUtils","l":"transfer(InputStream, OutputStream, int)","u":"transfer(java.io.InputStream,java.io.OutputStream,int)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"transform(AffineTransform)","u":"transform(java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector.commands","c":"TransformCommand","l":"TransformCommand(AffineTransform)","u":"%3Cinit%3E(java.awt.geom.AffineTransform)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"transformShape(Shape)","u":"transformShape(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"translate(double, double)","u":"translate(double,double)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"translate(int, int)","u":"translate(int,int)"},{"p":"org.xbib.graphics.io.vector.commands","c":"TranslateCommand","l":"TranslateCommand(double, double)","u":"%3Cinit%3E(double,double)"},{"p":"org.xbib.graphics.io.vector","c":"GraphicsState","l":"untransformShape(Shape)","u":"untransformShape(java.awt.Shape)"},{"p":"org.xbib.graphics.io.vector.util","c":"GraphicsUtils","l":"usesAlpha(Image)","u":"usesAlpha(java.awt.Image)"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"VALUE_EXPORT_QUALITY"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"VALUE_EXPORT_READABILITY"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"VALUE_EXPORT_SIZE"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"VALUE_TEXT_DEFAULT"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"VALUE_TEXT_VECTOR"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints.Value","l":"Value(VectorHints.Key, int, String)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.util.VectorHints.Key,int,java.lang.String)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphicsFormat","l":"valueOf(String)","u":"valueOf(java.lang.String)"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream.Interleaving","l":"valueOf(String)","u":"valueOf(java.lang.String)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphicsFormat","l":"values()"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream.Interleaving","l":"values()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"VectorGraphics2D()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"VectorGraphics2D(Processor, PageSize)","u":"%3Cinit%3E(org.xbib.graphics.io.vector.Processor,org.xbib.graphics.io.vector.PageSize)"},{"p":"org.xbib.graphics.io.vector.util","c":"VectorHints","l":"VectorHints()","u":"%3Cinit%3E()"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFObject","l":"version"},{"p":"org.xbib.graphics.io.vector.util","c":"ImageDataStream.Interleaving","l":"WITHOUT_ALPHA"},{"p":"org.xbib.graphics.io.vector.pdf","c":"GeneratedPayload","l":"write(int)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"Payload","l":"write(int)"},{"p":"org.xbib.graphics.io.vector.util","c":"ASCII85EncodeStream","l":"write(int)"},{"p":"org.xbib.graphics.io.vector.util","c":"Base64EncodeStream","l":"write(int)"},{"p":"org.xbib.graphics.io.vector.util","c":"LineWrapOutputStream","l":"write(int)"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"write(Number)","u":"write(java.lang.Number)"},{"p":"org.xbib.graphics.io.vector","c":"ProcessorResult","l":"write(OutputStream)","u":"write(java.io.OutputStream)"},{"p":"org.xbib.graphics.io.vector.eps","c":"EPSProcessorResult","l":"write(OutputStream)","u":"write(java.io.OutputStream)"},{"p":"org.xbib.graphics.io.vector.pdf","c":"PDFProcessorResult","l":"write(OutputStream)","u":"write(java.io.OutputStream)"},{"p":"org.xbib.graphics.io.vector.svg","c":"SVGProcessorResult","l":"write(OutputStream)","u":"write(java.io.OutputStream)"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"write(String)","u":"write(java.lang.String)"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"writeln()"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"writeln(Number)","u":"writeln(java.lang.Number)"},{"p":"org.xbib.graphics.io.vector.util","c":"FormattingWriter","l":"writeln(String)","u":"writeln(java.lang.String)"},{"p":"org.xbib.graphics.io.vector","c":"VectorGraphics2D","l":"writeTo(OutputStream)","u":"writeTo(java.io.OutputStream)"}];updateSearchResults(); \ No newline at end of file diff --git a/io-vector/build/docs/javadoc/module-search-index.js b/io-vector/build/docs/javadoc/module-search-index.js new file mode 100644 index 0000000..932a926 --- /dev/null +++ b/io-vector/build/docs/javadoc/module-search-index.js @@ -0,0 +1 @@ +moduleSearchIndex = [{"l":"org.xbib.graphics.io.vector"}];updateSearchResults(); \ No newline at end of file diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/module-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/module-summary.html new file mode 100644 index 0000000..005cdf4 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/module-summary.html @@ -0,0 +1,138 @@ + + + + + +org.xbib.graphics.io.vector (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Module org.xbib.graphics.io.vector

+
+
+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/Command.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/Command.html new file mode 100644 index 0000000..976f26d --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/Command.html @@ -0,0 +1,252 @@ + + + + + +Command (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class Command<T>

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T>
+
+
+
+
Direct Known Subclasses:
+
DrawImageCommand, DrawShapeCommand, DrawStringCommand, FillShapeCommand, Group, StateCommand
+
+
+
public abstract class Command<T>
+extends java.lang.Object
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Command​(T value) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    booleanequals​(java.lang.Object obj) 
    TgetValue() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Command

      +
      public Command​(T value)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getValue

      +
      public T getValue()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class java.lang.Object
      +
      +
      +
    • +
    • +
      +

      equals

      +
      public boolean equals​(java.lang.Object obj)
      +
      +
      Overrides:
      +
      equals in class java.lang.Object
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/GraphicsState.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/GraphicsState.html new file mode 100644 index 0000000..bd03826 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/GraphicsState.html @@ -0,0 +1,656 @@ + + + + + +GraphicsState (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class GraphicsState

+
+
java.lang.Object +
org.xbib.graphics.io.vector.GraphicsState
+
+
+
+
All Implemented Interfaces:
+
java.lang.Cloneable
+
+
+
public class GraphicsState
+extends java.lang.Object
+implements java.lang.Cloneable
+
+
+
    + +
  • +
    +

    Field Summary

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Fields
    Modifier and TypeFieldDescription
    static java.awt.ColorDEFAULT_BACKGROUND +
    Default background color.
    +
    static java.awt.ShapeDEFAULT_CLIP +
    Default clipping shape.
    +
    static java.awt.ColorDEFAULT_COLOR +
    Default color.
    +
    static java.awt.CompositeDEFAULT_COMPOSITE +
    Default composite mode.
    +
    static java.awt.FontDEFAULT_FONT +
    Default font.
    +
    static java.awt.ColorDEFAULT_PAINT +
    Default paint.
    +
    static java.awt.StrokeDEFAULT_STROKE +
    Default stroke.
    +
    static java.awt.geom.AffineTransformDEFAULT_TRANSFORM +
    Default transformation.
    +
    static java.awt.ColorDEFAULT_XOR_MODE +
    Default XOR mode.
    +
    +
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    GraphicsState() 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    java.lang.Objectclone() 
    booleanequals​(java.lang.Object obj) 
    java.awt.ColorgetBackground() 
    java.awt.ShapegetClip() 
    java.awt.ColorgetColor() 
    java.awt.CompositegetComposite() 
    java.awt.FontgetFont() 
    java.awt.RenderingHintsgetHints() 
    java.awt.PaintgetPaint() 
    java.awt.StrokegetStroke() 
    java.awt.geom.AffineTransformgetTransform() 
    java.awt.ColorgetXorMode() 
    inthashCode() 
    booleanisDefault() 
    voidsetBackground​(java.awt.Color background) 
    voidsetClip​(java.awt.Shape clip) 
    voidsetColor​(java.awt.Color color) 
    voidsetComposite​(java.awt.Composite composite) 
    voidsetFont​(java.awt.Font font) 
    voidsetPaint​(java.awt.Paint paint) 
    voidsetStroke​(java.awt.Stroke stroke) 
    voidsetTransform​(java.awt.geom.AffineTransform tx) 
    voidsetXorMode​(java.awt.Color xorMode) 
    java.awt.ShapetransformShape​(java.awt.Shape shape) 
    java.awt.ShapeuntransformShape​(java.awt.Shape shape) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +finalize, getClass, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      DEFAULT_BACKGROUND

      +
      public static final java.awt.Color DEFAULT_BACKGROUND
      +
      Default background color.
      +
      +
    • +
    • +
      +

      DEFAULT_COLOR

      +
      public static final java.awt.Color DEFAULT_COLOR
      +
      Default color.
      +
      +
    • +
    • +
      +

      DEFAULT_CLIP

      +
      public static final java.awt.Shape DEFAULT_CLIP
      +
      Default clipping shape.
      +
      +
    • +
    • +
      +

      DEFAULT_COMPOSITE

      +
      public static final java.awt.Composite DEFAULT_COMPOSITE
      +
      Default composite mode.
      +
      +
    • +
    • +
      +

      DEFAULT_FONT

      +
      public static final java.awt.Font DEFAULT_FONT
      +
      Default font.
      +
      +
    • +
    • +
      +

      DEFAULT_PAINT

      +
      public static final java.awt.Color DEFAULT_PAINT
      +
      Default paint.
      +
      +
    • +
    • +
      +

      DEFAULT_STROKE

      +
      public static final java.awt.Stroke DEFAULT_STROKE
      +
      Default stroke.
      +
      +
    • +
    • +
      +

      DEFAULT_TRANSFORM

      +
      public static final java.awt.geom.AffineTransform DEFAULT_TRANSFORM
      +
      Default transformation.
      +
      +
    • +
    • +
      +

      DEFAULT_XOR_MODE

      +
      public static final java.awt.Color DEFAULT_XOR_MODE
      +
      Default XOR mode.
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      GraphicsState

      +
      public GraphicsState()
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      clone

      +
      public java.lang.Object clone() + throws java.lang.CloneNotSupportedException
      +
      +
      Overrides:
      +
      clone in class java.lang.Object
      +
      Throws:
      +
      java.lang.CloneNotSupportedException
      +
      +
      +
    • +
    • +
      +

      transformShape

      +
      public java.awt.Shape transformShape​(java.awt.Shape shape)
      +
      +
    • +
    • +
      +

      untransformShape

      +
      public java.awt.Shape untransformShape​(java.awt.Shape shape)
      +
      +
    • +
    • +
      +

      getHints

      +
      public java.awt.RenderingHints getHints()
      +
      +
    • +
    • +
      +

      getBackground

      +
      public java.awt.Color getBackground()
      +
      +
    • +
    • +
      +

      setBackground

      +
      public void setBackground​(java.awt.Color background)
      +
      +
    • +
    • +
      +

      getColor

      +
      public java.awt.Color getColor()
      +
      +
    • +
    • +
      +

      setColor

      +
      public void setColor​(java.awt.Color color)
      +
      +
    • +
    • +
      +

      getClip

      +
      public java.awt.Shape getClip()
      +
      +
    • +
    • +
      +

      setClip

      +
      public void setClip​(java.awt.Shape clip)
      +
      +
    • +
    • +
      +

      getComposite

      +
      public java.awt.Composite getComposite()
      +
      +
    • +
    • +
      +

      setComposite

      +
      public void setComposite​(java.awt.Composite composite)
      +
      +
    • +
    • +
      +

      getFont

      +
      public java.awt.Font getFont()
      +
      +
    • +
    • +
      +

      setFont

      +
      public void setFont​(java.awt.Font font)
      +
      +
    • +
    • +
      +

      getPaint

      +
      public java.awt.Paint getPaint()
      +
      +
    • +
    • +
      +

      setPaint

      +
      public void setPaint​(java.awt.Paint paint)
      +
      +
    • +
    • +
      +

      getStroke

      +
      public java.awt.Stroke getStroke()
      +
      +
    • +
    • +
      +

      setStroke

      +
      public void setStroke​(java.awt.Stroke stroke)
      +
      +
    • +
    • +
      +

      getTransform

      +
      public java.awt.geom.AffineTransform getTransform()
      +
      +
    • +
    • +
      +

      setTransform

      +
      public void setTransform​(java.awt.geom.AffineTransform tx)
      +
      +
    • +
    • +
      +

      getXorMode

      +
      public java.awt.Color getXorMode()
      +
      +
    • +
    • +
      +

      setXorMode

      +
      public void setXorMode​(java.awt.Color xorMode)
      +
      +
    • +
    • +
      +

      equals

      +
      public boolean equals​(java.lang.Object obj)
      +
      +
      Overrides:
      +
      equals in class java.lang.Object
      +
      +
      +
    • +
    • +
      +

      hashCode

      +
      public int hashCode()
      +
      +
      Overrides:
      +
      hashCode in class java.lang.Object
      +
      +
      +
    • +
    • +
      +

      isDefault

      +
      public boolean isDefault()
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/PageSize.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/PageSize.html new file mode 100644 index 0000000..8781738 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/PageSize.html @@ -0,0 +1,406 @@ + + + + + +PageSize (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class PageSize

+
+
java.lang.Object +
org.xbib.graphics.io.vector.PageSize
+
+
+
+
public class PageSize
+extends java.lang.Object
+
+
+
    + +
  • +
    +

    Field Summary

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Fields
    Modifier and TypeFieldDescription
    static PageSizeA3 
    static PageSizeA4 
    static PageSizeA5 
    static PageSizeLEDGER 
    static PageSizeLEGAL 
    static PageSizeLETTER 
    static PageSizeTABLOID 
    +
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    PageSize​(double width, +double height) 
    PageSize​(double x, +double y, +double width, +double height) 
    PageSize​(java.awt.geom.Rectangle2D size) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    doublegetHeight() 
    PageSizegetLandscape() 
    PageSizegetPortrait() 
    doublegetWidth() 
    doublegetX() 
    doublegetY() 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      TABLOID

      +
      public static final PageSize TABLOID
      +
      +
    • +
    • +
      +

      LETTER

      +
      public static final PageSize LETTER
      +
      +
    • +
    • + +
    • +
    • +
      +

      LEDGER

      +
      public static final PageSize LEDGER
      +
      +
    • +
    • +
      +

      A3

      +
      public static final PageSize A3
      +
      +
    • +
    • +
      +

      A4

      +
      public static final PageSize A4
      +
      +
    • +
    • +
      +

      A5

      +
      public static final PageSize A5
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      PageSize

      +
      public PageSize​(double x, +double y, +double width, +double height)
      +
      +
    • +
    • +
      +

      PageSize

      +
      public PageSize​(double width, +double height)
      +
      +
    • +
    • +
      +

      PageSize

      +
      public PageSize​(java.awt.geom.Rectangle2D size)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getPortrait

      +
      public PageSize getPortrait()
      +
      +
    • +
    • +
      +

      getLandscape

      +
      public PageSize getLandscape()
      +
      +
    • +
    • +
      +

      getX

      +
      public double getX()
      +
      +
    • +
    • +
      +

      getY

      +
      public double getY()
      +
      +
    • +
    • +
      +

      getHeight

      +
      public double getHeight()
      +
      +
    • +
    • +
      +

      getWidth

      +
      public double getWidth()
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/Processor.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/Processor.html new file mode 100644 index 0000000..8535fad --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/Processor.html @@ -0,0 +1,185 @@ + + + + + +Processor (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Interface Processor

+
+
+
+
All Known Implementing Classes:
+
EPSProcessor, PDFProcessor, SVGProcessor
+
+
+
public interface Processor
+
+
+
    + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    ProcessorResultprocess​(java.lang.Iterable<Command<?>> commands, +PageSize pageSize) 
    +
    +
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      process

      +
      ProcessorResult process​(java.lang.Iterable<Command<?>> commands, +PageSize pageSize) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/ProcessorResult.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/ProcessorResult.html new file mode 100644 index 0000000..4d552b7 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/ProcessorResult.html @@ -0,0 +1,215 @@ + + + + + +ProcessorResult (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Interface ProcessorResult

+
+
+
+
All Known Implementing Classes:
+
EPSProcessorResult, PDFProcessorResult, SVGProcessorResult
+
+
+
public interface ProcessorResult
+
+
+
    + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidclose() 
    voidhandle​(Command<?> command) 
    voidwrite​(java.io.OutputStream out) 
    +
    +
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      handle

      +
      void handle​(Command<?> command) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      write

      +
      void write​(java.io.OutputStream out) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      void close() + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/VectorGraphics2D.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/VectorGraphics2D.html new file mode 100644 index 0000000..9d27dfb --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/VectorGraphics2D.html @@ -0,0 +1,1664 @@ + + + + + +VectorGraphics2D (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class VectorGraphics2D

+
+
java.lang.Object +
java.awt.Graphics +
java.awt.Graphics2D +
org.xbib.graphics.io.vector.VectorGraphics2D
+
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Cloneable
+
+
+
Direct Known Subclasses:
+
EPSGraphics2D, PDFGraphics2D, SVGGraphics2D
+
+
+
public class VectorGraphics2D
+extends java.awt.Graphics2D
+implements java.lang.Cloneable
+
Base for classes that want to implement vector export.
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    VectorGraphics2D() 
    VectorGraphics2D​(Processor processor, +PageSize pageSize) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidaddRenderingHints​(java.util.Map<?,​?> hints) 
    voidclearRect​(int x, +int y, +int width, +int height) 
    voidclip​(java.awt.Shape s) 
    voidclipRect​(int x, +int y, +int width, +int height) 
    java.lang.Objectclone() 
    voidcopyArea​(int x, +int y, +int width, +int height, +int dx, +int dy) 
    java.awt.Graphicscreate() 
    voiddispose() 
    voiddraw​(java.awt.Shape s) 
    voiddrawArc​(int x, +int y, +int width, +int height, +int startAngle, +int arcAngle) 
    voiddrawGlyphVector​(java.awt.font.GlyphVector g, +float x, +float y) 
    voiddrawImage​(java.awt.image.BufferedImage img, +java.awt.image.BufferedImageOp op, +int x, +int y) 
    booleandrawImage​(java.awt.Image img, +int dx1, +int dy1, +int dx2, +int dy2, +int sx1, +int sy1, +int sx2, +int sy2, +java.awt.Color bgcolor, +java.awt.image.ImageObserver observer) 
    booleandrawImage​(java.awt.Image img, +int dx1, +int dy1, +int dx2, +int dy2, +int sx1, +int sy1, +int sx2, +int sy2, +java.awt.image.ImageObserver observer) 
    booleandrawImage​(java.awt.Image img, +int x, +int y, +int width, +int height, +java.awt.Color bgcolor, +java.awt.image.ImageObserver observer) 
    booleandrawImage​(java.awt.Image img, +int x, +int y, +int width, +int height, +java.awt.image.ImageObserver observer) 
    booleandrawImage​(java.awt.Image img, +int x, +int y, +java.awt.Color bgcolor, +java.awt.image.ImageObserver observer) 
    booleandrawImage​(java.awt.Image img, +int x, +int y, +java.awt.image.ImageObserver observer) 
    booleandrawImage​(java.awt.Image img, +java.awt.geom.AffineTransform xform, +java.awt.image.ImageObserver obs) 
    voiddrawLine​(int x1, +int y1, +int x2, +int y2) 
    voiddrawOval​(int x, +int y, +int width, +int height) 
    voiddrawPolygon​(int[] xPoints, +int[] yPoints, +int nPoints) 
    voiddrawPolygon​(java.awt.Polygon p) 
    voiddrawPolyline​(int[] xPoints, +int[] yPoints, +int nPoints) 
    voiddrawRect​(int x, +int y, +int width, +int height) 
    voiddrawRenderableImage​(java.awt.image.renderable.RenderableImage img, +java.awt.geom.AffineTransform xform) 
    voiddrawRenderedImage​(java.awt.image.RenderedImage img, +java.awt.geom.AffineTransform xform) 
    voiddrawRoundRect​(int x, +int y, +int width, +int height, +int arcWidth, +int arcHeight) 
    voiddrawString​(java.lang.String str, +float x, +float y) 
    voiddrawString​(java.lang.String str, +int x, +int y) 
    voiddrawString​(java.text.AttributedCharacterIterator iterator, +float x, +float y) 
    voiddrawString​(java.text.AttributedCharacterIterator iterator, +int x, +int y) 
    voidfill​(java.awt.Shape s) 
    voidfillArc​(int x, +int y, +int width, +int height, +int startAngle, +int arcAngle) 
    voidfillOval​(int x, +int y, +int width, +int height) 
    voidfillPolygon​(int[] xPoints, +int[] yPoints, +int nPoints) 
    voidfillPolygon​(java.awt.Polygon p) 
    voidfillRect​(int x, +int y, +int width, +int height) 
    voidfillRoundRect​(int x, +int y, +int width, +int height, +int arcWidth, +int arcHeight) 
    java.awt.ColorgetBackground() 
    byte[]getBytes() 
    java.awt.ShapegetClip() 
    java.awt.RectanglegetClipBounds() 
    java.awt.ColorgetColor() 
    protected java.lang.Iterable<Command<?>>getCommands() 
    java.awt.CompositegetComposite() 
    java.awt.GraphicsConfigurationgetDeviceConfiguration() 
    java.awt.FontgetFont() 
    java.awt.FontMetricsgetFontMetrics​(java.awt.Font f) 
    java.awt.font.FontRenderContextgetFontRenderContext() 
    PageSizegetPageSize() 
    java.awt.PaintgetPaint() 
    java.lang.ObjectgetRenderingHint​(java.awt.RenderingHints.Key hintKey) 
    java.awt.RenderingHintsgetRenderingHints() 
    java.awt.StrokegetStroke() 
    java.awt.geom.AffineTransformgetTransform() 
    java.awt.ColorgetXORMode() 
    booleanhit​(java.awt.Rectangle rect, +java.awt.Shape s, +boolean onStroke) 
    protected booleanisDisposed() 
    voidrotate​(double theta) 
    voidrotate​(double theta, +double x, +double y) 
    voidscale​(double sx, +double sy) 
    voidsetBackground​(java.awt.Color color) 
    voidsetClip​(int x, +int y, +int width, +int height) 
    voidsetClip​(java.awt.Shape clip) 
    voidsetColor​(java.awt.Color c) 
    voidsetComposite​(java.awt.Composite comp) 
    voidsetFont​(java.awt.Font font) 
    voidsetPaint​(java.awt.Paint paint) 
    voidsetPaintMode() 
    voidsetRenderingHint​(java.awt.RenderingHints.Key hintKey, +java.lang.Object hintValue) 
    voidsetRenderingHints​(java.util.Map<?,​?> hints) 
    voidsetStroke​(java.awt.Stroke s) 
    voidsetTransform​(java.awt.geom.AffineTransform tx) 
    voidsetXORMode​(java.awt.Color c1) 
    voidshear​(double shx, +double shy) 
    voidtransform​(java.awt.geom.AffineTransform tx) 
    voidtranslate​(double tx, +double ty) 
    voidtranslate​(int x, +int y) 
    voidwriteTo​(java.io.OutputStream out) 
    +
    +
    +
    +

    Methods inherited from class java.awt.Graphics2D

    +draw3DRect, fill3DRect
    +
    +

    Methods inherited from class java.awt.Graphics

    +create, drawBytes, drawChars, finalize, getClipBounds, getClipRect, getFontMetrics, hitClip, toString
    +
    +

    Methods inherited from class java.lang.Object

    +equals, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      VectorGraphics2D

      +
      public VectorGraphics2D()
      +
      +
    • +
    • +
      +

      VectorGraphics2D

      +
      public VectorGraphics2D​(Processor processor, +PageSize pageSize)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getPageSize

      +
      public PageSize getPageSize()
      +
      +
    • +
    • +
      +

      getBytes

      +
      public byte[] getBytes() + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      writeTo

      +
      public void writeTo​(java.io.OutputStream out) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      clone

      +
      public java.lang.Object clone() + throws java.lang.CloneNotSupportedException
      +
      +
      Overrides:
      +
      clone in class java.lang.Object
      +
      Throws:
      +
      java.lang.CloneNotSupportedException
      +
      +
      +
    • +
    • +
      +

      addRenderingHints

      +
      public void addRenderingHints​(java.util.Map<?,​?> hints)
      +
      +
      Specified by:
      +
      addRenderingHints in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      clip

      +
      public void clip​(java.awt.Shape s)
      +
      +
      Specified by:
      +
      clip in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      draw

      +
      public void draw​(java.awt.Shape s)
      +
      +
      Specified by:
      +
      draw in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawGlyphVector

      +
      public void drawGlyphVector​(java.awt.font.GlyphVector g, +float x, +float y)
      +
      +
      Specified by:
      +
      drawGlyphVector in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public boolean drawImage​(java.awt.Image img, +java.awt.geom.AffineTransform xform, +java.awt.image.ImageObserver obs)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public void drawImage​(java.awt.image.BufferedImage img, +java.awt.image.BufferedImageOp op, +int x, +int y)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawRenderableImage

      +
      public void drawRenderableImage​(java.awt.image.renderable.RenderableImage img, +java.awt.geom.AffineTransform xform)
      +
      +
      Specified by:
      +
      drawRenderableImage in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawRenderedImage

      +
      public void drawRenderedImage​(java.awt.image.RenderedImage img, +java.awt.geom.AffineTransform xform)
      +
      +
      Specified by:
      +
      drawRenderedImage in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawString

      +
      public void drawString​(java.lang.String str, +int x, +int y)
      +
      +
      Specified by:
      +
      drawString in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawString

      +
      public void drawString​(java.lang.String str, +float x, +float y)
      +
      +
      Specified by:
      +
      drawString in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawString

      +
      public void drawString​(java.text.AttributedCharacterIterator iterator, +int x, +int y)
      +
      +
      Specified by:
      +
      drawString in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      drawString

      +
      public void drawString​(java.text.AttributedCharacterIterator iterator, +float x, +float y)
      +
      +
      Specified by:
      +
      drawString in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      fill

      +
      public void fill​(java.awt.Shape s)
      +
      +
      Specified by:
      +
      fill in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getBackground

      +
      public java.awt.Color getBackground()
      +
      +
      Specified by:
      +
      getBackground in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      setBackground

      +
      public void setBackground​(java.awt.Color color)
      +
      +
      Specified by:
      +
      setBackground in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getComposite

      +
      public java.awt.Composite getComposite()
      +
      +
      Specified by:
      +
      getComposite in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      setComposite

      +
      public void setComposite​(java.awt.Composite comp)
      +
      +
      Specified by:
      +
      setComposite in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getDeviceConfiguration

      +
      public java.awt.GraphicsConfiguration getDeviceConfiguration()
      +
      +
      Specified by:
      +
      getDeviceConfiguration in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getFontRenderContext

      +
      public java.awt.font.FontRenderContext getFontRenderContext()
      +
      +
      Specified by:
      +
      getFontRenderContext in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getPaint

      +
      public java.awt.Paint getPaint()
      +
      +
      Specified by:
      +
      getPaint in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      setPaint

      +
      public void setPaint​(java.awt.Paint paint)
      +
      +
      Specified by:
      +
      setPaint in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getRenderingHint

      +
      public java.lang.Object getRenderingHint​(java.awt.RenderingHints.Key hintKey)
      +
      +
      Specified by:
      +
      getRenderingHint in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getRenderingHints

      +
      public java.awt.RenderingHints getRenderingHints()
      +
      +
      Specified by:
      +
      getRenderingHints in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      setRenderingHints

      +
      public void setRenderingHints​(java.util.Map<?,​?> hints)
      +
      +
      Specified by:
      +
      setRenderingHints in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getStroke

      +
      public java.awt.Stroke getStroke()
      +
      +
      Specified by:
      +
      getStroke in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      setStroke

      +
      public void setStroke​(java.awt.Stroke s)
      +
      +
      Specified by:
      +
      setStroke in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      hit

      +
      public boolean hit​(java.awt.Rectangle rect, +java.awt.Shape s, +boolean onStroke)
      +
      +
      Specified by:
      +
      hit in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      setRenderingHint

      +
      public void setRenderingHint​(java.awt.RenderingHints.Key hintKey, +java.lang.Object hintValue)
      +
      +
      Specified by:
      +
      setRenderingHint in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      getTransform

      +
      public java.awt.geom.AffineTransform getTransform()
      +
      +
      Specified by:
      +
      getTransform in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      setTransform

      +
      public void setTransform​(java.awt.geom.AffineTransform tx)
      +
      +
      Specified by:
      +
      setTransform in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      shear

      +
      public void shear​(double shx, +double shy)
      +
      +
      Specified by:
      +
      shear in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      transform

      +
      public void transform​(java.awt.geom.AffineTransform tx)
      +
      +
      Specified by:
      +
      transform in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      translate

      +
      public void translate​(int x, +int y)
      +
      +
      Specified by:
      +
      translate in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      translate

      +
      public void translate​(double tx, +double ty)
      +
      +
      Specified by:
      +
      translate in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      rotate

      +
      public void rotate​(double theta)
      +
      +
      Specified by:
      +
      rotate in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      rotate

      +
      public void rotate​(double theta, +double x, +double y)
      +
      +
      Specified by:
      +
      rotate in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      scale

      +
      public void scale​(double sx, +double sy)
      +
      +
      Specified by:
      +
      scale in class java.awt.Graphics2D
      +
      +
      +
    • +
    • +
      +

      clearRect

      +
      public void clearRect​(int x, +int y, +int width, +int height)
      +
      +
      Specified by:
      +
      clearRect in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      clipRect

      +
      public void clipRect​(int x, +int y, +int width, +int height)
      +
      +
      Specified by:
      +
      clipRect in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      copyArea

      +
      public void copyArea​(int x, +int y, +int width, +int height, +int dx, +int dy)
      +
      +
      Specified by:
      +
      copyArea in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      create

      +
      public java.awt.Graphics create()
      +
      +
      Specified by:
      +
      create in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      dispose

      +
      public void dispose()
      +
      +
      Specified by:
      +
      dispose in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawArc

      +
      public void drawArc​(int x, +int y, +int width, +int height, +int startAngle, +int arcAngle)
      +
      +
      Specified by:
      +
      drawArc in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public boolean drawImage​(java.awt.Image img, +int x, +int y, +java.awt.image.ImageObserver observer)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public boolean drawImage​(java.awt.Image img, +int x, +int y, +java.awt.Color bgcolor, +java.awt.image.ImageObserver observer)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public boolean drawImage​(java.awt.Image img, +int x, +int y, +int width, +int height, +java.awt.image.ImageObserver observer)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public boolean drawImage​(java.awt.Image img, +int x, +int y, +int width, +int height, +java.awt.Color bgcolor, +java.awt.image.ImageObserver observer)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public boolean drawImage​(java.awt.Image img, +int dx1, +int dy1, +int dx2, +int dy2, +int sx1, +int sy1, +int sx2, +int sy2, +java.awt.image.ImageObserver observer)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawImage

      +
      public boolean drawImage​(java.awt.Image img, +int dx1, +int dy1, +int dx2, +int dy2, +int sx1, +int sy1, +int sx2, +int sy2, +java.awt.Color bgcolor, +java.awt.image.ImageObserver observer)
      +
      +
      Specified by:
      +
      drawImage in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawLine

      +
      public void drawLine​(int x1, +int y1, +int x2, +int y2)
      +
      +
      Specified by:
      +
      drawLine in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawOval

      +
      public void drawOval​(int x, +int y, +int width, +int height)
      +
      +
      Specified by:
      +
      drawOval in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawPolygon

      +
      public void drawPolygon​(java.awt.Polygon p)
      +
      +
      Overrides:
      +
      drawPolygon in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawPolygon

      +
      public void drawPolygon​(int[] xPoints, +int[] yPoints, +int nPoints)
      +
      +
      Specified by:
      +
      drawPolygon in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawPolyline

      +
      public void drawPolyline​(int[] xPoints, +int[] yPoints, +int nPoints)
      +
      +
      Specified by:
      +
      drawPolyline in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawRect

      +
      public void drawRect​(int x, +int y, +int width, +int height)
      +
      +
      Overrides:
      +
      drawRect in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      drawRoundRect

      +
      public void drawRoundRect​(int x, +int y, +int width, +int height, +int arcWidth, +int arcHeight)
      +
      +
      Specified by:
      +
      drawRoundRect in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      fillArc

      +
      public void fillArc​(int x, +int y, +int width, +int height, +int startAngle, +int arcAngle)
      +
      +
      Specified by:
      +
      fillArc in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      fillOval

      +
      public void fillOval​(int x, +int y, +int width, +int height)
      +
      +
      Specified by:
      +
      fillOval in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      fillPolygon

      +
      public void fillPolygon​(java.awt.Polygon p)
      +
      +
      Overrides:
      +
      fillPolygon in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      fillPolygon

      +
      public void fillPolygon​(int[] xPoints, +int[] yPoints, +int nPoints)
      +
      +
      Specified by:
      +
      fillPolygon in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      fillRect

      +
      public void fillRect​(int x, +int y, +int width, +int height)
      +
      +
      Specified by:
      +
      fillRect in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      fillRoundRect

      +
      public void fillRoundRect​(int x, +int y, +int width, +int height, +int arcWidth, +int arcHeight)
      +
      +
      Specified by:
      +
      fillRoundRect in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      getClip

      +
      public java.awt.Shape getClip()
      +
      +
      Specified by:
      +
      getClip in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      setClip

      +
      public void setClip​(java.awt.Shape clip)
      +
      +
      Specified by:
      +
      setClip in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      getClipBounds

      +
      public java.awt.Rectangle getClipBounds()
      +
      +
      Specified by:
      +
      getClipBounds in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      getColor

      +
      public java.awt.Color getColor()
      +
      +
      Specified by:
      +
      getColor in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      setColor

      +
      public void setColor​(java.awt.Color c)
      +
      +
      Specified by:
      +
      setColor in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      getFont

      +
      public java.awt.Font getFont()
      +
      +
      Specified by:
      +
      getFont in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      setFont

      +
      public void setFont​(java.awt.Font font)
      +
      +
      Specified by:
      +
      setFont in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      getFontMetrics

      +
      public java.awt.FontMetrics getFontMetrics​(java.awt.Font f)
      +
      +
      Specified by:
      +
      getFontMetrics in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      setClip

      +
      public void setClip​(int x, +int y, +int width, +int height)
      +
      +
      Specified by:
      +
      setClip in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      setPaintMode

      +
      public void setPaintMode()
      +
      +
      Specified by:
      +
      setPaintMode in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      getXORMode

      +
      public java.awt.Color getXORMode()
      +
      +
    • +
    • +
      +

      setXORMode

      +
      public void setXORMode​(java.awt.Color c1)
      +
      +
      Specified by:
      +
      setXORMode in class java.awt.Graphics
      +
      +
      +
    • +
    • +
      +

      getCommands

      +
      protected java.lang.Iterable<Command<?>> getCommands()
      +
      +
    • +
    • +
      +

      isDisposed

      +
      protected boolean isDisposed()
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/VectorGraphicsFormat.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/VectorGraphicsFormat.html new file mode 100644 index 0000000..f862782 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/VectorGraphicsFormat.html @@ -0,0 +1,291 @@ + + + + + +VectorGraphicsFormat (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Enum VectorGraphicsFormat

+
+
java.lang.Object +
java.lang.Enum<VectorGraphicsFormat> +
org.xbib.graphics.io.vector.VectorGraphicsFormat
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Serializable, java.lang.Comparable<VectorGraphicsFormat>, java.lang.constant.Constable
+
+
+
public enum VectorGraphicsFormat
+extends java.lang.Enum<VectorGraphicsFormat>
+
+
+
    + +
  • +
    +

    Nested Class Summary

    +
    +

    Nested classes/interfaces inherited from class java.lang.Enum

    +java.lang.Enum.EnumDesc<E extends java.lang.Enum<E>>
    +
    +
  • + +
  • +
    +

    Enum Constant Summary

    +
    + + + + + + + + + + + + + + + + + + + + + + +
    Enum Constants
    Enum ConstantDescription
    EPS 
    PDF 
    SVG 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    static VectorGraphicsFormatvalueOf​(java.lang.String name) +
    Returns the enum constant of this type with the specified name.
    +
    static VectorGraphicsFormat[]values() +
    Returns an array containing the constants of this enum type, in +the order they are declared.
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Enum

    +clone, compareTo, describeConstable, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
    +
    +

    Methods inherited from class java.lang.Object

    +getClass, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Enum Constant Details

    + +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      values

      +
      public static VectorGraphicsFormat[] values()
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      Returns:
      +
      an array containing the constants of this enum type, in the order they are declared
      +
      +
      +
    • +
    • +
      +

      valueOf

      +
      public static VectorGraphicsFormat valueOf​(java.lang.String name)
      +
      Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
      +
      +
      Parameters:
      +
      name - the name of the enum constant to be returned.
      +
      Returns:
      +
      the enum constant with the specified name
      +
      Throws:
      +
      java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
      +
      java.lang.NullPointerException - if the argument is null
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/AffineTransformCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/AffineTransformCommand.html new file mode 100644 index 0000000..c2debd2 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/AffineTransformCommand.html @@ -0,0 +1,188 @@ + + + + + +AffineTransformCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class AffineTransformCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.geom.AffineTransform> +
org.xbib.graphics.io.vector.commands.AffineTransformCommand
+
+
+
+
+
+
Direct Known Subclasses:
+
RotateCommand, ScaleCommand, ShearCommand, TransformCommand, TranslateCommand
+
+
+
public abstract class AffineTransformCommand
+extends StateCommand<java.awt.geom.AffineTransform>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    AffineTransformCommand​(java.awt.geom.AffineTransform transform) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      AffineTransformCommand

      +
      public AffineTransformCommand​(java.awt.geom.AffineTransform transform)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/CreateCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/CreateCommand.html new file mode 100644 index 0000000..8298f78 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/CreateCommand.html @@ -0,0 +1,184 @@ + + + + + +CreateCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class CreateCommand

+
+
java.lang.Object + +
+
+
+
public class CreateCommand
+extends StateCommand<VectorGraphics2D>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    CreateCommand​(VectorGraphics2D graphics) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    + +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DisposeCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DisposeCommand.html new file mode 100644 index 0000000..f45496c --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DisposeCommand.html @@ -0,0 +1,184 @@ + + + + + +DisposeCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class DisposeCommand

+
+
java.lang.Object + +
+
+
+
public class DisposeCommand
+extends StateCommand<VectorGraphics2D>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    DisposeCommand​(VectorGraphics2D graphics) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      DisposeCommand

      +
      public DisposeCommand​(VectorGraphics2D graphics)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawImageCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawImageCommand.html new file mode 100644 index 0000000..2f79991 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawImageCommand.html @@ -0,0 +1,305 @@ + + + + + +DrawImageCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class DrawImageCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<java.awt.Image> +
org.xbib.graphics.io.vector.commands.DrawImageCommand
+
+
+
+
+
public class DrawImageCommand
+extends Command<java.awt.Image>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    DrawImageCommand​(java.awt.Image image, +int imageWidth, +int imageHeight, +double x, +double y, +double width, +double height) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    doublegetHeight() 
    intgetImageHeight() 
    intgetImageWidth() 
    doublegetWidth() 
    doublegetX() 
    doublegetY() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      DrawImageCommand

      +
      public DrawImageCommand​(java.awt.Image image, +int imageWidth, +int imageHeight, +double x, +double y, +double width, +double height)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getImageWidth

      +
      public int getImageWidth()
      +
      +
    • +
    • +
      +

      getImageHeight

      +
      public int getImageHeight()
      +
      +
    • +
    • +
      +

      getX

      +
      public double getX()
      +
      +
    • +
    • +
      +

      getY

      +
      public double getY()
      +
      +
    • +
    • +
      +

      getWidth

      +
      public double getWidth()
      +
      +
    • +
    • +
      +

      getHeight

      +
      public double getHeight()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class Command<java.awt.Image>
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawShapeCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawShapeCommand.html new file mode 100644 index 0000000..70bd5f0 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawShapeCommand.html @@ -0,0 +1,182 @@ + + + + + +DrawShapeCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class DrawShapeCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<java.awt.Shape> +
org.xbib.graphics.io.vector.commands.DrawShapeCommand
+
+
+
+
+
public class DrawShapeCommand
+extends Command<java.awt.Shape>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    DrawShapeCommand​(java.awt.Shape shape) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      DrawShapeCommand

      +
      public DrawShapeCommand​(java.awt.Shape shape)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawStringCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawStringCommand.html new file mode 100644 index 0000000..065b1e2 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/DrawStringCommand.html @@ -0,0 +1,253 @@ + + + + + +DrawStringCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class DrawStringCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<java.lang.String> +
org.xbib.graphics.io.vector.commands.DrawStringCommand
+
+
+
+
+
public class DrawStringCommand
+extends Command<java.lang.String>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    DrawStringCommand​(java.lang.String string, +double x, +double y) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    doublegetX() 
    doublegetY() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      DrawStringCommand

      +
      public DrawStringCommand​(java.lang.String string, +double x, +double y)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getX

      +
      public double getX()
      +
      +
    • +
    • +
      +

      getY

      +
      public double getY()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class Command<java.lang.String>
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/FillShapeCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/FillShapeCommand.html new file mode 100644 index 0000000..e34dac6 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/FillShapeCommand.html @@ -0,0 +1,182 @@ + + + + + +FillShapeCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class FillShapeCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<java.awt.Shape> +
org.xbib.graphics.io.vector.commands.FillShapeCommand
+
+
+
+
+
public class FillShapeCommand
+extends Command<java.awt.Shape>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    FillShapeCommand​(java.awt.Shape shape) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      FillShapeCommand

      +
      public FillShapeCommand​(java.awt.Shape shape)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/Group.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/Group.html new file mode 100644 index 0000000..13a1241 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/Group.html @@ -0,0 +1,223 @@ + + + + + +Group (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ + +
java.lang.Object +
org.xbib.graphics.io.vector.Command<java.util.List<Command<?>>> +
org.xbib.graphics.io.vector.commands.Group
+
+
+
+
+
public class Group
+extends Command<java.util.List<Command<?>>>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Group() 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidadd​(Command<?> command) 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Group

      +
      public Group()
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      add

      +
      public void add​(Command<?> command)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/RotateCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/RotateCommand.html new file mode 100644 index 0000000..e517e41 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/RotateCommand.html @@ -0,0 +1,268 @@ + + + + + +RotateCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class RotateCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.geom.AffineTransform> +
org.xbib.graphics.io.vector.commands.AffineTransformCommand +
org.xbib.graphics.io.vector.commands.RotateCommand
+
+
+
+
+
+
+
public class RotateCommand
+extends AffineTransformCommand
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    RotateCommand​(double theta, +double centerX, +double centerY) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    doublegetCenterX() 
    doublegetCenterY() 
    doublegetTheta() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      RotateCommand

      +
      public RotateCommand​(double theta, +double centerX, +double centerY)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getTheta

      +
      public double getTheta()
      +
      +
    • +
    • +
      +

      getCenterX

      +
      public double getCenterX()
      +
      +
    • +
    • +
      +

      getCenterY

      +
      public double getCenterY()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class Command<java.awt.geom.AffineTransform>
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/ScaleCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/ScaleCommand.html new file mode 100644 index 0000000..18f43bf --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/ScaleCommand.html @@ -0,0 +1,255 @@ + + + + + +ScaleCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class ScaleCommand

+
+
java.lang.Object + +
+
+
+
public class ScaleCommand
+extends AffineTransformCommand
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    ScaleCommand​(double scaleX, +double scaleY) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    doublegetScaleX() 
    doublegetScaleY() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      ScaleCommand

      +
      public ScaleCommand​(double scaleX, +double scaleY)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getScaleX

      +
      public double getScaleX()
      +
      +
    • +
    • +
      +

      getScaleY

      +
      public double getScaleY()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class Command<java.awt.geom.AffineTransform>
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetBackgroundCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetBackgroundCommand.html new file mode 100644 index 0000000..2b0dcb9 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetBackgroundCommand.html @@ -0,0 +1,184 @@ + + + + + +SetBackgroundCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetBackgroundCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Color> +
org.xbib.graphics.io.vector.commands.SetBackgroundCommand
+
+
+
+
+
+
public class SetBackgroundCommand
+extends StateCommand<java.awt.Color>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetBackgroundCommand​(java.awt.Color color) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetBackgroundCommand

      +
      public SetBackgroundCommand​(java.awt.Color color)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetClipCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetClipCommand.html new file mode 100644 index 0000000..ef6e4c3 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetClipCommand.html @@ -0,0 +1,184 @@ + + + + + +SetClipCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetClipCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Shape> +
org.xbib.graphics.io.vector.commands.SetClipCommand
+
+
+
+
+
+
public class SetClipCommand
+extends StateCommand<java.awt.Shape>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetClipCommand​(java.awt.Shape shape) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetClipCommand

      +
      public SetClipCommand​(java.awt.Shape shape)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetColorCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetColorCommand.html new file mode 100644 index 0000000..df7b1c5 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetColorCommand.html @@ -0,0 +1,184 @@ + + + + + +SetColorCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetColorCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Color> +
org.xbib.graphics.io.vector.commands.SetColorCommand
+
+
+
+
+
+
public class SetColorCommand
+extends StateCommand<java.awt.Color>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetColorCommand​(java.awt.Color color) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetColorCommand

      +
      public SetColorCommand​(java.awt.Color color)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetCompositeCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetCompositeCommand.html new file mode 100644 index 0000000..a199abb --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetCompositeCommand.html @@ -0,0 +1,184 @@ + + + + + +SetCompositeCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetCompositeCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Composite> +
org.xbib.graphics.io.vector.commands.SetCompositeCommand
+
+
+
+
+
+
public class SetCompositeCommand
+extends StateCommand<java.awt.Composite>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetCompositeCommand​(java.awt.Composite composite) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetCompositeCommand

      +
      public SetCompositeCommand​(java.awt.Composite composite)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetFontCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetFontCommand.html new file mode 100644 index 0000000..37af80a --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetFontCommand.html @@ -0,0 +1,184 @@ + + + + + +SetFontCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetFontCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Font> +
org.xbib.graphics.io.vector.commands.SetFontCommand
+
+
+
+
+
+
public class SetFontCommand
+extends StateCommand<java.awt.Font>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetFontCommand​(java.awt.Font font) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetFontCommand

      +
      public SetFontCommand​(java.awt.Font font)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetHintCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetHintCommand.html new file mode 100644 index 0000000..7a6afb5 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetHintCommand.html @@ -0,0 +1,242 @@ + + + + + +SetHintCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetHintCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.lang.Object> +
org.xbib.graphics.io.vector.commands.SetHintCommand
+
+
+
+
+
+
public class SetHintCommand
+extends StateCommand<java.lang.Object>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetHintCommand​(java.lang.Object hintKey, +java.lang.Object hintValue) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    java.lang.ObjectgetKey() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetHintCommand

      +
      public SetHintCommand​(java.lang.Object hintKey, +java.lang.Object hintValue)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getKey

      +
      public java.lang.Object getKey()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class Command<java.lang.Object>
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetPaintCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetPaintCommand.html new file mode 100644 index 0000000..29cc1d1 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetPaintCommand.html @@ -0,0 +1,184 @@ + + + + + +SetPaintCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetPaintCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Paint> +
org.xbib.graphics.io.vector.commands.SetPaintCommand
+
+
+
+
+
+
public class SetPaintCommand
+extends StateCommand<java.awt.Paint>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetPaintCommand​(java.awt.Paint paint) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetPaintCommand

      +
      public SetPaintCommand​(java.awt.Paint paint)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetStrokeCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetStrokeCommand.html new file mode 100644 index 0000000..7db8a5f --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetStrokeCommand.html @@ -0,0 +1,184 @@ + + + + + +SetStrokeCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetStrokeCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Stroke> +
org.xbib.graphics.io.vector.commands.SetStrokeCommand
+
+
+
+
+
+
public class SetStrokeCommand
+extends StateCommand<java.awt.Stroke>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetStrokeCommand​(java.awt.Stroke stroke) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetStrokeCommand

      +
      public SetStrokeCommand​(java.awt.Stroke stroke)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetTransformCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetTransformCommand.html new file mode 100644 index 0000000..f735a1b --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetTransformCommand.html @@ -0,0 +1,184 @@ + + + + + +SetTransformCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetTransformCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.geom.AffineTransform> +
org.xbib.graphics.io.vector.commands.SetTransformCommand
+
+
+
+
+
+
public class SetTransformCommand
+extends StateCommand<java.awt.geom.AffineTransform>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetTransformCommand​(java.awt.geom.AffineTransform transform) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetTransformCommand

      +
      public SetTransformCommand​(java.awt.geom.AffineTransform transform)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetXORModeCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetXORModeCommand.html new file mode 100644 index 0000000..d0c5f1f --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/SetXORModeCommand.html @@ -0,0 +1,184 @@ + + + + + +SetXORModeCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SetXORModeCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.Color> +
org.xbib.graphics.io.vector.commands.SetXORModeCommand
+
+
+
+
+
+
public class SetXORModeCommand
+extends StateCommand<java.awt.Color>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SetXORModeCommand​(java.awt.Color mode) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SetXORModeCommand

      +
      public SetXORModeCommand​(java.awt.Color mode)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/ShearCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/ShearCommand.html new file mode 100644 index 0000000..93ca541 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/ShearCommand.html @@ -0,0 +1,255 @@ + + + + + +ShearCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class ShearCommand

+
+
java.lang.Object + +
+
+
+
public class ShearCommand
+extends AffineTransformCommand
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    ShearCommand​(double shearX, +double shearY) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    doublegetShearX() 
    doublegetShearY() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      ShearCommand

      +
      public ShearCommand​(double shearX, +double shearY)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getShearX

      +
      public double getShearX()
      +
      +
    • +
    • +
      +

      getShearY

      +
      public double getShearY()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class Command<java.awt.geom.AffineTransform>
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/StateCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/StateCommand.html new file mode 100644 index 0000000..a8182f6 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/StateCommand.html @@ -0,0 +1,186 @@ + + + + + +StateCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class StateCommand<T>

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<T>
+
+
+
+
+
Direct Known Subclasses:
+
AffineTransformCommand, CreateCommand, DisposeCommand, SetBackgroundCommand, SetClipCommand, SetColorCommand, SetCompositeCommand, SetFontCommand, SetHintCommand, SetPaintCommand, SetStrokeCommand, SetTransformCommand, SetXORModeCommand
+
+
+
public abstract class StateCommand<T>
+extends Command<T>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    StateCommand​(T value) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      StateCommand

      +
      public StateCommand​(T value)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/TransformCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/TransformCommand.html new file mode 100644 index 0000000..396f1b1 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/TransformCommand.html @@ -0,0 +1,227 @@ + + + + + +TransformCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class TransformCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.geom.AffineTransform> +
org.xbib.graphics.io.vector.commands.AffineTransformCommand +
org.xbib.graphics.io.vector.commands.TransformCommand
+
+
+
+
+
+
+
public class TransformCommand
+extends AffineTransformCommand
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    TransformCommand​(java.awt.geom.AffineTransform transform) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    java.awt.geom.AffineTransformgetTransform() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue, toString
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      TransformCommand

      +
      public TransformCommand​(java.awt.geom.AffineTransform transform)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getTransform

      +
      public java.awt.geom.AffineTransform getTransform()
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/TranslateCommand.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/TranslateCommand.html new file mode 100644 index 0000000..6822cae --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/TranslateCommand.html @@ -0,0 +1,255 @@ + + + + + +TranslateCommand (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class TranslateCommand

+
+
java.lang.Object +
org.xbib.graphics.io.vector.Command<T> +
org.xbib.graphics.io.vector.commands.StateCommand<java.awt.geom.AffineTransform> +
org.xbib.graphics.io.vector.commands.AffineTransformCommand +
org.xbib.graphics.io.vector.commands.TranslateCommand
+
+
+
+
+
+
+
public class TranslateCommand
+extends AffineTransformCommand
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    TranslateCommand​(double x, +double y) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    doublegetDeltaX() 
    doublegetDeltaY() 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.Command

    +equals, getValue
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      TranslateCommand

      +
      public TranslateCommand​(double x, +double y)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getDeltaX

      +
      public double getDeltaX()
      +
      +
    • +
    • +
      +

      getDeltaY

      +
      public double getDeltaY()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class Command<java.awt.geom.AffineTransform>
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/package-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/package-summary.html new file mode 100644 index 0000000..2f8ae00 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/package-summary.html @@ -0,0 +1,195 @@ + + + + + +org.xbib.graphics.io.vector.commands (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+ +

Package org.xbib.graphics.io.vector.commands

+
+
+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/package-tree.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/package-tree.html new file mode 100644 index 0000000..c7197e8 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/commands/package-tree.html @@ -0,0 +1,126 @@ + + + + + +org.xbib.graphics.io.vector.commands Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.xbib.graphics.io.vector.commands

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSGraphics2D.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSGraphics2D.html new file mode 100644 index 0000000..93cc807 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSGraphics2D.html @@ -0,0 +1,217 @@ + + + + + +EPSGraphics2D (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class EPSGraphics2D

+
+
java.lang.Object +
java.awt.Graphics +
java.awt.Graphics2D +
org.xbib.graphics.io.vector.VectorGraphics2D +
org.xbib.graphics.io.vector.eps.EPSGraphics2D
+
+
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Cloneable
+
+
+
public class EPSGraphics2D
+extends VectorGraphics2D
+
Graphics2D implementation that saves all operations to a string + in the Encapsulated PostScript® (EPS) format.
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      EPSGraphics2D

      +
      public EPSGraphics2D​(double x, +double y, +double width, +double height)
      +
      Initializes a new VectorGraphics2D pipeline for translating Graphics2D + commands to EPS data. The document dimensions must be specified as + parameters.
      +
      +
      Parameters:
      +
      x - Left offset.
      +
      y - Top offset
      +
      width - Width.
      +
      height - Height.
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSProcessor.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSProcessor.html new file mode 100644 index 0000000..ce9f358 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSProcessor.html @@ -0,0 +1,232 @@ + + + + + +EPSProcessor (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class EPSProcessor

+
+
java.lang.Object +
org.xbib.graphics.io.vector.eps.EPSProcessor
+
+
+
+
All Implemented Interfaces:
+
Processor
+
+
+
public class EPSProcessor
+extends java.lang.Object
+implements Processor
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    EPSProcessor() 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    ProcessorResultprocess​(java.lang.Iterable<Command<?>> commands, +PageSize pageSize) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      EPSProcessor

      +
      public EPSProcessor()
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      process

      +
      public ProcessorResult process​(java.lang.Iterable<Command<?>> commands, +PageSize pageSize) + throws java.io.IOException
      +
      +
      Specified by:
      +
      process in interface Processor
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSProcessorResult.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSProcessorResult.html new file mode 100644 index 0000000..14548be --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/EPSProcessorResult.html @@ -0,0 +1,263 @@ + + + + + +EPSProcessorResult (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class EPSProcessorResult

+
+
java.lang.Object +
org.xbib.graphics.io.vector.eps.EPSProcessorResult
+
+
+
+
All Implemented Interfaces:
+
ProcessorResult
+
+
+
public class EPSProcessorResult
+extends java.lang.Object
+implements ProcessorResult
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    EPSProcessorResult​(PageSize pageSize) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidclose() 
    voidhandle​(Command<?> command) 
    voidwrite​(java.io.OutputStream out) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      EPSProcessorResult

      +
      public EPSProcessorResult​(PageSize pageSize)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      write

      +
      public void write​(java.io.OutputStream out) + throws java.io.IOException
      +
      +
      Specified by:
      +
      write in interface ProcessorResult
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      public void close()
      +
      +
      Specified by:
      +
      close in interface ProcessorResult
      +
      +
      +
    • +
    • +
      +

      handle

      +
      public void handle​(Command<?> command) + throws java.io.IOException
      +
      +
      Specified by:
      +
      handle in interface ProcessorResult
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/package-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/package-summary.html new file mode 100644 index 0000000..cf3c2fb --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/package-summary.html @@ -0,0 +1,114 @@ + + + + + +org.xbib.graphics.io.vector.eps (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+ +

Package org.xbib.graphics.io.vector.eps

+
+
+
    +
  • +
    + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary
    ClassDescription
    EPSGraphics2D +
    Graphics2D implementation that saves all operations to a string + in the Encapsulated PostScript® (EPS) format.
    +
    EPSProcessor 
    EPSProcessorResult 
    +
    +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/package-tree.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/package-tree.html new file mode 100644 index 0000000..7ad64b6 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/eps/package-tree.html @@ -0,0 +1,107 @@ + + + + + +org.xbib.graphics.io.vector.eps Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.xbib.graphics.io.vector.eps

+Package Hierarchies: + +
+
+

Class Hierarchy

+
    +
  • java.lang.Object + +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.html new file mode 100644 index 0000000..8a7a2dc --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.html @@ -0,0 +1,254 @@ + + + + + +AbsoluteToRelativeTransformsFilter (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class AbsoluteToRelativeTransformsFilter

+
+
java.lang.Object +
org.xbib.graphics.io.vector.filters.Filter +
org.xbib.graphics.io.vector.filters.AbsoluteToRelativeTransformsFilter
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Iterable<Command<?>>, java.util.Iterator<Command<?>>
+
+
+
public class AbsoluteToRelativeTransformsFilter
+extends Filter
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    AbsoluteToRelativeTransformsFilter​(java.lang.Iterable<Command<?>> stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected java.util.List<Command<?>>filter​(Command<?> command) 
    Command<?>next() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.filters.Filter

    +hasNext, iterator, remove
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +

    Methods inherited from interface java.lang.Iterable

    +forEach, spliterator
    +
    +

    Methods inherited from interface java.util.Iterator

    +forEachRemaining
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      AbsoluteToRelativeTransformsFilter

      +
      public AbsoluteToRelativeTransformsFilter​(java.lang.Iterable<Command<?>> stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      next

      +
      public Command<?> next()
      +
      +
      Specified by:
      +
      next in interface java.util.Iterator<Command<?>>
      +
      Overrides:
      +
      next in class Filter
      +
      +
      +
    • +
    • +
      +

      filter

      +
      protected java.util.List<Command<?>> filter​(Command<?> command)
      +
      +
      Specified by:
      +
      filter in class Filter
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.html new file mode 100644 index 0000000..22157fa --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.html @@ -0,0 +1,254 @@ + + + + + +FillPaintedShapeAsImageFilter (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class FillPaintedShapeAsImageFilter

+
+
java.lang.Object +
org.xbib.graphics.io.vector.filters.Filter +
org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Iterable<Command<?>>, java.util.Iterator<Command<?>>
+
+
+
public class FillPaintedShapeAsImageFilter
+extends Filter
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    FillPaintedShapeAsImageFilter​(java.lang.Iterable<Command<?>> stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected java.util.List<Command<?>>filter​(Command<?> command) 
    Command<?>next() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.filters.Filter

    +hasNext, iterator, remove
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +

    Methods inherited from interface java.lang.Iterable

    +forEach, spliterator
    +
    +

    Methods inherited from interface java.util.Iterator

    +forEachRemaining
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      FillPaintedShapeAsImageFilter

      +
      public FillPaintedShapeAsImageFilter​(java.lang.Iterable<Command<?>> stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      next

      +
      public Command<?> next()
      +
      +
      Specified by:
      +
      next in interface java.util.Iterator<Command<?>>
      +
      Overrides:
      +
      next in class Filter
      +
      +
      +
    • +
    • +
      +

      filter

      +
      protected java.util.List<Command<?>> filter​(Command<?> command)
      +
      +
      Specified by:
      +
      filter in class Filter
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/Filter.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/Filter.html new file mode 100644 index 0000000..2e89ecf --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/Filter.html @@ -0,0 +1,293 @@ + + + + + +Filter (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ + +
java.lang.Object +
org.xbib.graphics.io.vector.filters.Filter
+
+
+
+
All Implemented Interfaces:
+
java.lang.Iterable<Command<?>>, java.util.Iterator<Command<?>>
+
+
+
Direct Known Subclasses:
+
AbsoluteToRelativeTransformsFilter, FillPaintedShapeAsImageFilter, GroupingFilter, OptimizeFilter
+
+
+
public abstract class Filter
+extends java.lang.Object
+implements java.lang.Iterable<Command<?>>, java.util.Iterator<Command<?>>
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Filter​(java.lang.Iterable<Command<?>> stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected abstract java.util.List<Command<?>>filter​(Command<?> command) 
    booleanhasNext() 
    java.util.Iterator<Command<?>>iterator() 
    Command<?>next() 
    voidremove() 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +

    Methods inherited from interface java.lang.Iterable

    +forEach, spliterator
    +
    +

    Methods inherited from interface java.util.Iterator

    +forEachRemaining
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Filter

      +
      public Filter​(java.lang.Iterable<Command<?>> stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      iterator

      +
      public java.util.Iterator<Command<?>> iterator()
      +
      +
      Specified by:
      +
      iterator in interface java.lang.Iterable<Command<?>>
      +
      +
      +
    • +
    • +
      +

      hasNext

      +
      public boolean hasNext()
      +
      +
      Specified by:
      +
      hasNext in interface java.util.Iterator<Command<?>>
      +
      +
      +
    • +
    • +
      +

      next

      +
      public Command<?> next()
      +
      +
      Specified by:
      +
      next in interface java.util.Iterator<Command<?>>
      +
      +
      +
    • +
    • +
      +

      remove

      +
      public void remove()
      +
      +
      Specified by:
      +
      remove in interface java.util.Iterator<Command<?>>
      +
      +
      +
    • +
    • +
      +

      filter

      +
      protected abstract java.util.List<Command<?>> filter​(Command<?> command)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/GroupingFilter.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/GroupingFilter.html new file mode 100644 index 0000000..7b386e0 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/GroupingFilter.html @@ -0,0 +1,286 @@ + + + + + +GroupingFilter (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class GroupingFilter

+
+
java.lang.Object +
org.xbib.graphics.io.vector.filters.Filter +
org.xbib.graphics.io.vector.filters.GroupingFilter
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Iterable<Command<?>>, java.util.Iterator<Command<?>>
+
+
+
Direct Known Subclasses:
+
StateChangeGroupingFilter
+
+
+
public abstract class GroupingFilter
+extends Filter
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    GroupingFilter​(java.lang.Iterable<Command<?>> stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected java.util.List<Command<?>>filter​(Command<?> command) 
    booleanhasNext() 
    protected abstract booleanisGrouped​(Command<?> command) 
    Command<?>next() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.filters.Filter

    +iterator, remove
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +

    Methods inherited from interface java.lang.Iterable

    +forEach, spliterator
    +
    +

    Methods inherited from interface java.util.Iterator

    +forEachRemaining
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      GroupingFilter

      +
      public GroupingFilter​(java.lang.Iterable<Command<?>> stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      hasNext

      +
      public boolean hasNext()
      +
      +
      Specified by:
      +
      hasNext in interface java.util.Iterator<Command<?>>
      +
      Overrides:
      +
      hasNext in class Filter
      +
      +
      +
    • +
    • +
      +

      next

      +
      public Command<?> next()
      +
      +
      Specified by:
      +
      next in interface java.util.Iterator<Command<?>>
      +
      Overrides:
      +
      next in class Filter
      +
      +
      +
    • +
    • +
      +

      filter

      +
      protected java.util.List<Command<?>> filter​(Command<?> command)
      +
      +
      Specified by:
      +
      filter in class Filter
      +
      +
      +
    • +
    • +
      +

      isGrouped

      +
      protected abstract boolean isGrouped​(Command<?> command)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/OptimizeFilter.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/OptimizeFilter.html new file mode 100644 index 0000000..0a442b8 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/OptimizeFilter.html @@ -0,0 +1,271 @@ + + + + + +OptimizeFilter (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class OptimizeFilter

+
+
java.lang.Object +
org.xbib.graphics.io.vector.filters.Filter +
org.xbib.graphics.io.vector.filters.OptimizeFilter
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Iterable<Command<?>>, java.util.Iterator<Command<?>>
+
+
+
public class OptimizeFilter
+extends Filter
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    OptimizeFilter​(java.lang.Iterable<Command<?>> stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected java.util.List<Command<?>>filter​(Command<?> command) 
    booleanhasNext() 
    Command<?>next() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.filters.Filter

    +iterator, remove
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +

    Methods inherited from interface java.lang.Iterable

    +forEach, spliterator
    +
    +

    Methods inherited from interface java.util.Iterator

    +forEachRemaining
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      OptimizeFilter

      +
      public OptimizeFilter​(java.lang.Iterable<Command<?>> stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      hasNext

      +
      public boolean hasNext()
      +
      +
      Specified by:
      +
      hasNext in interface java.util.Iterator<Command<?>>
      +
      Overrides:
      +
      hasNext in class Filter
      +
      +
      +
    • +
    • +
      +

      next

      +
      public Command<?> next()
      +
      +
      Specified by:
      +
      next in interface java.util.Iterator<Command<?>>
      +
      Overrides:
      +
      next in class Filter
      +
      +
      +
    • +
    • +
      +

      filter

      +
      protected java.util.List<Command<?>> filter​(Command<?> command)
      +
      +
      Specified by:
      +
      filter in class Filter
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.html new file mode 100644 index 0000000..e878f1b --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.html @@ -0,0 +1,242 @@ + + + + + +StateChangeGroupingFilter (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class StateChangeGroupingFilter

+
+
java.lang.Object +
org.xbib.graphics.io.vector.filters.Filter +
org.xbib.graphics.io.vector.filters.GroupingFilter +
org.xbib.graphics.io.vector.filters.StateChangeGroupingFilter
+
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Iterable<Command<?>>, java.util.Iterator<Command<?>>
+
+
+
public class StateChangeGroupingFilter
+extends GroupingFilter
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    StateChangeGroupingFilter​(java.lang.Iterable<Command<?>> stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected booleanisGrouped​(Command<?> command) 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.filters.GroupingFilter

    +filter, hasNext, next
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.filters.Filter

    +iterator, remove
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +

    Methods inherited from interface java.lang.Iterable

    +forEach, spliterator
    +
    +

    Methods inherited from interface java.util.Iterator

    +forEachRemaining
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      StateChangeGroupingFilter

      +
      public StateChangeGroupingFilter​(java.lang.Iterable<Command<?>> stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    + +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/package-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/package-summary.html new file mode 100644 index 0000000..bffcac4 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/package-summary.html @@ -0,0 +1,123 @@ + + + + + +org.xbib.graphics.io.vector.filters (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+ +

Package org.xbib.graphics.io.vector.filters

+
+
+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/package-tree.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/package-tree.html new file mode 100644 index 0000000..322cfb7 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/filters/package-tree.html @@ -0,0 +1,104 @@ + + + + + +org.xbib.graphics.io.vector.filters Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.xbib.graphics.io.vector.filters

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/package-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/package-summary.html new file mode 100644 index 0000000..927ffff --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/package-summary.html @@ -0,0 +1,159 @@ + + + + + +org.xbib.graphics.io.vector (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+ +

Package org.xbib.graphics.io.vector

+
+
+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/package-tree.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/package-tree.html new file mode 100644 index 0000000..4a41ef1 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/package-tree.html @@ -0,0 +1,125 @@ + + + + + +org.xbib.graphics.io.vector Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.xbib.graphics.io.vector

+Package Hierarchies: + +
+
+

Class Hierarchy

+
    +
  • java.lang.Object +
      +
    • org.xbib.graphics.io.vector.Command<T>
    • +
    • java.awt.Graphics +
        +
      • java.awt.Graphics2D +
          +
        • org.xbib.graphics.io.vector.VectorGraphics2D (implements java.lang.Cloneable)
        • +
        +
      • +
      +
    • +
    • org.xbib.graphics.io.vector.GraphicsState (implements java.lang.Cloneable)
    • +
    • org.xbib.graphics.io.vector.PageSize
    • +
    +
  • +
+
+
+

Interface Hierarchy

+ +
+
+

Enum Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.lang.constant.Constable, java.io.Serializable) + +
    • +
    +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/GeneratedPayload.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/GeneratedPayload.html new file mode 100644 index 0000000..00be0a2 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/GeneratedPayload.html @@ -0,0 +1,277 @@ + + + + + +GeneratedPayload (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class GeneratedPayload

+
+
java.lang.Object +
java.io.OutputStream +
org.xbib.graphics.io.vector.pdf.Payload +
org.xbib.graphics.io.vector.pdf.GeneratedPayload
+
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
Direct Known Subclasses:
+
SizePayload
+
+
+
public abstract class GeneratedPayload
+extends Payload
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    GeneratedPayload​(boolean stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected abstract byte[]generatePayload() 
    byte[]getBytes() 
    voidwrite​(int b) 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.pdf.Payload

    +addFilter, close, isStream
    +
    +

    Methods inherited from class java.io.OutputStream

    +flush, nullOutputStream, write, write
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      GeneratedPayload

      +
      public GeneratedPayload​(boolean stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getBytes

      +
      public byte[] getBytes() + throws java.io.IOException
      +
      +
      Overrides:
      +
      getBytes in class Payload
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      write

      +
      public void write​(int b) + throws java.io.IOException
      +
      +
      Overrides:
      +
      write in class Payload
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      generatePayload

      +
      protected abstract byte[] generatePayload() + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFGraphics2D.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFGraphics2D.html new file mode 100644 index 0000000..b51ac79 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFGraphics2D.html @@ -0,0 +1,217 @@ + + + + + +PDFGraphics2D (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class PDFGraphics2D

+
+
java.lang.Object +
java.awt.Graphics +
java.awt.Graphics2D +
org.xbib.graphics.io.vector.VectorGraphics2D +
org.xbib.graphics.io.vector.pdf.PDFGraphics2D
+
+
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Cloneable
+
+
+
public class PDFGraphics2D
+extends VectorGraphics2D
+
Graphics2D implementation that saves all operations to a string + in the Portable Document Format (PDF).
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      PDFGraphics2D

      +
      public PDFGraphics2D​(double x, +double y, +double width, +double height)
      +
      Initializes a new VectorGraphics2D pipeline for translating Graphics2D + commands to PDF data. The document dimensions must be specified as + parameters.
      +
      +
      Parameters:
      +
      x - Left offset.
      +
      y - Top offset
      +
      width - Width.
      +
      height - Height.
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFObject.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFObject.html new file mode 100644 index 0000000..4bfb061 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFObject.html @@ -0,0 +1,259 @@ + + + + + +PDFObject (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class PDFObject

+
+
java.lang.Object +
org.xbib.graphics.io.vector.pdf.PDFObject
+
+
+
+
Direct Known Subclasses:
+
Resources
+
+
+
public class PDFObject
+extends java.lang.Object
+
+
+
    + +
  • +
    +

    Field Summary

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Fields
    Modifier and TypeFieldDescription
    java.util.Map<java.lang.String,​java.lang.Object>dict 
    intid 
    Payloadpayload 
    intversion 
    +
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    PDFObject​(int id, +int version, +java.util.Map<java.lang.String,​java.lang.Object> dict, +Payload payload) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      id

      +
      public final int id
      +
      +
    • +
    • +
      +

      version

      +
      public final int version
      +
      +
    • +
    • +
      +

      dict

      +
      public final java.util.Map<java.lang.String,​java.lang.Object> dict
      +
      +
    • +
    • +
      +

      payload

      +
      public final Payload payload
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      PDFObject

      +
      public PDFObject​(int id, +int version, +java.util.Map<java.lang.String,​java.lang.Object> dict, +Payload payload)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFProcessor.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFProcessor.html new file mode 100644 index 0000000..e25b4c2 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFProcessor.html @@ -0,0 +1,242 @@ + + + + + +PDFProcessor (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class PDFProcessor

+
+
java.lang.Object +
org.xbib.graphics.io.vector.pdf.PDFProcessor
+
+
+
+
All Implemented Interfaces:
+
Processor
+
+
+
public class PDFProcessor
+extends java.lang.Object
+implements Processor
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    PDFProcessor() 
    PDFProcessor​(boolean compressed) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    ProcessorResultprocess​(java.lang.Iterable<Command<?>> commands, +PageSize pageSize) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      PDFProcessor

      +
      public PDFProcessor()
      +
      +
    • +
    • +
      +

      PDFProcessor

      +
      public PDFProcessor​(boolean compressed)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      process

      +
      public ProcessorResult process​(java.lang.Iterable<Command<?>> commands, +PageSize pageSize) + throws java.io.IOException
      +
      +
      Specified by:
      +
      process in interface Processor
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFProcessorResult.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFProcessorResult.html new file mode 100644 index 0000000..d76c884 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/PDFProcessorResult.html @@ -0,0 +1,298 @@ + + + + + +PDFProcessorResult (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class PDFProcessorResult

+
+
java.lang.Object +
org.xbib.graphics.io.vector.pdf.PDFProcessorResult
+
+
+
+
All Implemented Interfaces:
+
ProcessorResult
+
+
+
public class PDFProcessorResult
+extends java.lang.Object
+implements ProcessorResult
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    PDFProcessorResult​(PageSize pageSize) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidclose() 
    voidhandle​(Command<?> command) 
    voidsetCompressed​(boolean compressed) 
    static java.lang.StringtoString​(PDFObject obj) 
    voidwrite​(java.io.OutputStream out) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      PDFProcessorResult

      +
      public PDFProcessorResult​(PageSize pageSize) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      setCompressed

      +
      public void setCompressed​(boolean compressed)
      +
      +
    • +
    • +
      +

      toString

      +
      public static java.lang.String toString​(PDFObject obj) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      write

      +
      public void write​(java.io.OutputStream out) + throws java.io.IOException
      +
      +
      Specified by:
      +
      write in interface ProcessorResult
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      handle

      +
      public void handle​(Command<?> command) + throws java.io.IOException
      +
      +
      Specified by:
      +
      handle in interface ProcessorResult
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      public void close() + throws java.io.IOException
      +
      +
      Specified by:
      +
      close in interface ProcessorResult
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/Payload.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/Payload.html new file mode 100644 index 0000000..03891bb --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/Payload.html @@ -0,0 +1,309 @@ + + + + + +Payload (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ + +
java.lang.Object +
java.io.OutputStream +
org.xbib.graphics.io.vector.pdf.Payload
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
Direct Known Subclasses:
+
GeneratedPayload
+
+
+
public class Payload
+extends java.io.OutputStream
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Payload​(boolean stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidaddFilter​(java.lang.Class<? extends java.io.FilterOutputStream> filterClass) 
    voidclose() 
    byte[]getBytes() 
    booleanisStream() 
    voidwrite​(int b) 
    +
    +
    +
    +

    Methods inherited from class java.io.OutputStream

    +flush, nullOutputStream, write, write
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Payload

      +
      public Payload​(boolean stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getBytes

      +
      public byte[] getBytes() + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      isStream

      +
      public boolean isStream()
      +
      +
    • +
    • +
      +

      write

      +
      public void write​(int b) + throws java.io.IOException
      +
      +
      Specified by:
      +
      write in class java.io.OutputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      public void close() + throws java.io.IOException
      +
      +
      Specified by:
      +
      close in interface java.lang.AutoCloseable
      +
      Specified by:
      +
      close in interface java.io.Closeable
      +
      Overrides:
      +
      close in class java.io.OutputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      addFilter

      +
      public void addFilter​(java.lang.Class<? extends java.io.FilterOutputStream> filterClass) + throws java.lang.NoSuchMethodException, +java.lang.IllegalAccessException, +java.lang.reflect.InvocationTargetException, +java.lang.InstantiationException
      +
      +
      Throws:
      +
      java.lang.NoSuchMethodException
      +
      java.lang.IllegalAccessException
      +
      java.lang.reflect.InvocationTargetException
      +
      java.lang.InstantiationException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/Resources.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/Resources.html new file mode 100644 index 0000000..82d851d --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/Resources.html @@ -0,0 +1,253 @@ + + + + + +Resources (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class Resources

+
+
java.lang.Object +
org.xbib.graphics.io.vector.pdf.PDFObject +
org.xbib.graphics.io.vector.pdf.Resources
+
+
+
+
+
public class Resources
+extends PDFObject
+
+
+
    + +
  • +
    +

    Field Summary

    +
    +

    Fields inherited from class org.xbib.graphics.io.vector.pdf.PDFObject

    +dict, id, payload, version
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Resources​(int id, +int version) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    java.lang.StringgetId​(java.awt.Font font) 
    java.lang.StringgetId​(java.lang.Double transparency) 
    java.lang.StringgetId​(PDFObject image) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Resources

      +
      public Resources​(int id, +int version)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getId

      +
      public java.lang.String getId​(java.awt.Font font)
      +
      +
    • +
    • +
      +

      getId

      +
      public java.lang.String getId​(PDFObject image)
      +
      +
    • +
    • +
      +

      getId

      +
      public java.lang.String getId​(java.lang.Double transparency)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/SizePayload.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/SizePayload.html new file mode 100644 index 0000000..3515bc4 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/SizePayload.html @@ -0,0 +1,248 @@ + + + + + +SizePayload (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SizePayload

+
+
java.lang.Object +
java.io.OutputStream + +
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
public class SizePayload
+extends GeneratedPayload
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SizePayload​(PDFObject object, +java.lang.String charset, +boolean stream) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    protected byte[]generatePayload() 
    +
    +
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.pdf.GeneratedPayload

    +getBytes, write
    +
    +

    Methods inherited from class org.xbib.graphics.io.vector.pdf.Payload

    +addFilter, close, isStream
    +
    +

    Methods inherited from class java.io.OutputStream

    +flush, nullOutputStream, write, write
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SizePayload

      +
      public SizePayload​(PDFObject object, +java.lang.String charset, +boolean stream)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      generatePayload

      +
      protected byte[] generatePayload() + throws java.io.IOException
      +
      +
      Specified by:
      +
      generatePayload in class GeneratedPayload
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/package-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/package-summary.html new file mode 100644 index 0000000..65c0bb6 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/package-summary.html @@ -0,0 +1,134 @@ + + + + + +org.xbib.graphics.io.vector.pdf (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+ +

Package org.xbib.graphics.io.vector.pdf

+
+
+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/package-tree.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/package-tree.html new file mode 100644 index 0000000..5271000 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/pdf/package-tree.html @@ -0,0 +1,125 @@ + + + + + +org.xbib.graphics.io.vector.pdf Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.xbib.graphics.io.vector.pdf

+Package Hierarchies: + +
+
+

Class Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.awt.Graphics +
        +
      • java.awt.Graphics2D + +
      • +
      +
    • +
    • java.io.OutputStream (implements java.io.Closeable, java.io.Flushable) + +
    • +
    • org.xbib.graphics.io.vector.pdf.PDFObject +
        +
      • org.xbib.graphics.io.vector.pdf.Resources
      • +
      +
    • +
    • org.xbib.graphics.io.vector.pdf.PDFProcessor (implements org.xbib.graphics.io.vector.Processor)
    • +
    • org.xbib.graphics.io.vector.pdf.PDFProcessorResult (implements org.xbib.graphics.io.vector.ProcessorResult)
    • +
    +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGGraphics2D.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGGraphics2D.html new file mode 100644 index 0000000..4cf5b6d --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGGraphics2D.html @@ -0,0 +1,217 @@ + + + + + +SVGGraphics2D (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SVGGraphics2D

+
+
java.lang.Object +
java.awt.Graphics +
java.awt.Graphics2D +
org.xbib.graphics.io.vector.VectorGraphics2D +
org.xbib.graphics.io.vector.svg.SVGGraphics2D
+
+
+
+
+
+
+
All Implemented Interfaces:
+
java.lang.Cloneable
+
+
+
public class SVGGraphics2D
+extends VectorGraphics2D
+
Graphics2D implementation that saves all operations to a string + in the Scaled Vector Graphics (SVG) format.
+
+
+ +
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SVGGraphics2D

      +
      public SVGGraphics2D​(double x, +double y, +double width, +double height)
      +
      Initializes a new VectorGraphics2D pipeline for translating Graphics2D + commands to SVG data. The document dimensions must be specified as + parameters.
      +
      +
      Parameters:
      +
      x - Left offset.
      +
      y - Top offset
      +
      width - Width.
      +
      height - Height.
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGProcessor.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGProcessor.html new file mode 100644 index 0000000..9b211b0 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGProcessor.html @@ -0,0 +1,229 @@ + + + + + +SVGProcessor (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SVGProcessor

+
+
java.lang.Object +
org.xbib.graphics.io.vector.svg.SVGProcessor
+
+
+
+
All Implemented Interfaces:
+
Processor
+
+
+
public class SVGProcessor
+extends java.lang.Object
+implements Processor
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SVGProcessor() 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    ProcessorResultprocess​(java.lang.Iterable<Command<?>> commands, +PageSize pageSize) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SVGProcessor

      +
      public SVGProcessor()
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    + +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGProcessorResult.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGProcessorResult.html new file mode 100644 index 0000000..6c766cd --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/SVGProcessorResult.html @@ -0,0 +1,275 @@ + + + + + +SVGProcessorResult (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class SVGProcessorResult

+
+
java.lang.Object +
org.xbib.graphics.io.vector.svg.SVGProcessorResult
+
+
+
+
All Implemented Interfaces:
+
ProcessorResult
+
+
+
public class SVGProcessorResult
+extends java.lang.Object
+implements ProcessorResult
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    SVGProcessorResult​(PageSize pageSize) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidclose() 
    voidhandle​(Command<?> command) 
    java.lang.StringtoString() 
    voidwrite​(java.io.OutputStream out) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      SVGProcessorResult

      +
      public SVGProcessorResult​(PageSize pageSize)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      write

      +
      public void write​(java.io.OutputStream out) + throws java.io.IOException
      +
      +
      Specified by:
      +
      write in interface ProcessorResult
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      public void close()
      +
      +
      Specified by:
      +
      close in interface ProcessorResult
      +
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class java.lang.Object
      +
      +
      +
    • +
    • +
      +

      handle

      +
      public void handle​(Command<?> command)
      +
      +
      Specified by:
      +
      handle in interface ProcessorResult
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/package-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/package-summary.html new file mode 100644 index 0000000..8c70de0 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/package-summary.html @@ -0,0 +1,114 @@ + + + + + +org.xbib.graphics.io.vector.svg (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+ +

Package org.xbib.graphics.io.vector.svg

+
+
+
    +
  • +
    + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary
    ClassDescription
    SVGGraphics2D +
    Graphics2D implementation that saves all operations to a string + in the Scaled Vector Graphics (SVG) format.
    +
    SVGProcessor 
    SVGProcessorResult 
    +
    +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/package-tree.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/package-tree.html new file mode 100644 index 0000000..7a46cbb --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/svg/package-tree.html @@ -0,0 +1,107 @@ + + + + + +org.xbib.graphics.io.vector.svg Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.xbib.graphics.io.vector.svg

+Package Hierarchies: + +
+
+

Class Hierarchy

+
    +
  • java.lang.Object + +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ASCII85EncodeStream.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ASCII85EncodeStream.html new file mode 100644 index 0000000..5f84e4f --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ASCII85EncodeStream.html @@ -0,0 +1,284 @@ + + + + + +ASCII85EncodeStream (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class ASCII85EncodeStream

+
+
java.lang.Object +
java.io.OutputStream +
java.io.FilterOutputStream +
org.xbib.graphics.io.vector.util.ASCII85EncodeStream
+
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
public class ASCII85EncodeStream
+extends java.io.FilterOutputStream
+
+
+
    + +
  • +
    +

    Field Summary

    +
    +

    Fields inherited from class java.io.FilterOutputStream

    +out
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    ASCII85EncodeStream​(java.io.OutputStream out) 
    ASCII85EncodeStream​(java.io.OutputStream out, +java.lang.String prefix, +java.lang.String suffix) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidclose() 
    voidwrite​(int b) 
    +
    +
    +
    +

    Methods inherited from class java.io.FilterOutputStream

    +flush, write, write
    +
    +

    Methods inherited from class java.io.OutputStream

    +nullOutputStream
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      ASCII85EncodeStream

      +
      public ASCII85EncodeStream​(java.io.OutputStream out, +java.lang.String prefix, +java.lang.String suffix)
      +
      +
    • +
    • +
      +

      ASCII85EncodeStream

      +
      public ASCII85EncodeStream​(java.io.OutputStream out)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      write

      +
      public void write​(int b) + throws java.io.IOException
      +
      +
      Overrides:
      +
      write in class java.io.FilterOutputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      public void close() + throws java.io.IOException
      +
      +
      Specified by:
      +
      close in interface java.lang.AutoCloseable
      +
      Specified by:
      +
      close in interface java.io.Closeable
      +
      Overrides:
      +
      close in class java.io.FilterOutputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/AlphaToMaskOp.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/AlphaToMaskOp.html new file mode 100644 index 0000000..30c6c27 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/AlphaToMaskOp.html @@ -0,0 +1,314 @@ + + + + + +AlphaToMaskOp (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class AlphaToMaskOp

+
+
java.lang.Object +
org.xbib.graphics.io.vector.util.AlphaToMaskOp
+
+
+
+
All Implemented Interfaces:
+
java.awt.image.BufferedImageOp
+
+
+
public class AlphaToMaskOp
+extends java.lang.Object
+implements java.awt.image.BufferedImageOp
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    AlphaToMaskOp() 
    AlphaToMaskOp​(boolean inverted) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    java.awt.image.BufferedImagecreateCompatibleDestImage​(java.awt.image.BufferedImage src, +java.awt.image.ColorModel destCM) 
    java.awt.image.BufferedImagefilter​(java.awt.image.BufferedImage src, +java.awt.image.BufferedImage dest) 
    java.awt.geom.Rectangle2DgetBounds2D​(java.awt.image.BufferedImage src) 
    java.awt.geom.Point2DgetPoint2D​(java.awt.geom.Point2D srcPt, +java.awt.geom.Point2D dstPt) 
    java.awt.RenderingHintsgetRenderingHints() 
    booleanisInverted() 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      AlphaToMaskOp

      +
      public AlphaToMaskOp​(boolean inverted)
      +
      +
    • +
    • +
      +

      AlphaToMaskOp

      +
      public AlphaToMaskOp()
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      isInverted

      +
      public boolean isInverted()
      +
      +
    • +
    • +
      +

      filter

      +
      public java.awt.image.BufferedImage filter​(java.awt.image.BufferedImage src, +java.awt.image.BufferedImage dest)
      +
      +
      Specified by:
      +
      filter in interface java.awt.image.BufferedImageOp
      +
      +
      +
    • +
    • +
      +

      getBounds2D

      +
      public java.awt.geom.Rectangle2D getBounds2D​(java.awt.image.BufferedImage src)
      +
      +
      Specified by:
      +
      getBounds2D in interface java.awt.image.BufferedImageOp
      +
      +
      +
    • +
    • +
      +

      createCompatibleDestImage

      +
      public java.awt.image.BufferedImage createCompatibleDestImage​(java.awt.image.BufferedImage src, +java.awt.image.ColorModel destCM)
      +
      +
      Specified by:
      +
      createCompatibleDestImage in interface java.awt.image.BufferedImageOp
      +
      +
      +
    • +
    • +
      +

      getPoint2D

      +
      public java.awt.geom.Point2D getPoint2D​(java.awt.geom.Point2D srcPt, +java.awt.geom.Point2D dstPt)
      +
      +
      Specified by:
      +
      getPoint2D in interface java.awt.image.BufferedImageOp
      +
      +
      +
    • +
    • +
      +

      getRenderingHints

      +
      public java.awt.RenderingHints getRenderingHints()
      +
      +
      Specified by:
      +
      getRenderingHints in interface java.awt.image.BufferedImageOp
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/Base64EncodeStream.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/Base64EncodeStream.html new file mode 100644 index 0000000..3888609 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/Base64EncodeStream.html @@ -0,0 +1,270 @@ + + + + + +Base64EncodeStream (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class Base64EncodeStream

+
+
java.lang.Object +
java.io.OutputStream +
java.io.FilterOutputStream +
org.xbib.graphics.io.vector.util.Base64EncodeStream
+
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
public class Base64EncodeStream
+extends java.io.FilterOutputStream
+
+
+
    + +
  • +
    +

    Field Summary

    +
    +

    Fields inherited from class java.io.FilterOutputStream

    +out
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Base64EncodeStream​(java.io.OutputStream out) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidclose() 
    voidwrite​(int b) 
    +
    +
    +
    +

    Methods inherited from class java.io.FilterOutputStream

    +flush, write, write
    +
    +

    Methods inherited from class java.io.OutputStream

    +nullOutputStream
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Base64EncodeStream

      +
      public Base64EncodeStream​(java.io.OutputStream out)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      write

      +
      public void write​(int b) + throws java.io.IOException
      +
      +
      Overrides:
      +
      write in class java.io.FilterOutputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      public void close() + throws java.io.IOException
      +
      +
      Specified by:
      +
      close in interface java.lang.AutoCloseable
      +
      Specified by:
      +
      close in interface java.io.Closeable
      +
      Overrides:
      +
      close in class java.io.FilterOutputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/DataUtils.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/DataUtils.html new file mode 100644 index 0000000..d1dc714 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/DataUtils.html @@ -0,0 +1,497 @@ + + + + + +DataUtils (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class DataUtils

+
+
java.lang.Object +
org.xbib.graphics.io.vector.util.DataUtils
+
+
+
+
public abstract class DataUtils
+extends java.lang.Object
+
Abstract class that contains utility functions for working with data + collections like maps or lists.
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + +
    Constructors
    ModifierConstructorDescription
    protected DataUtils() +
    Default constructor that prevents creation of class.
    +
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    static java.util.List<java.lang.Double>asList​(double[] elements) +
    Converts an array of double numbers to a list of Doubles.
    +
    static java.util.List<java.lang.Float>asList​(float[] elements) +
    Converts an array of float numbers to a list of Floats.
    +
    static java.lang.Stringformat​(java.lang.Number number) +
    Returns a formatted string of the specified number.
    +
    static java.lang.Stringformat​(java.lang.Object obj) +
    Returns a formatted string of the specified object.
    +
    static java.lang.Stringjoin​(java.lang.String separator, +double[] elements) +
    Returns a string containing all double numbers concatenated by a + specified separator.
    +
    static java.lang.Stringjoin​(java.lang.String separator, +float[] elements) +
    Returns a string containing all float numbers concatenated by a + specified separator.
    +
    static java.lang.Stringjoin​(java.lang.String separator, +java.lang.Object[] elements) +
    Returns a string containing all elements concatenated by a specified + separator.
    +
    static java.lang.Stringjoin​(java.lang.String separator, +java.util.List<?> elements) +
    Returns a string containing all elements concatenated by a specified + separator.
    +
    static <K,​ +V> java.util.Map<K,​V>map​(K[] keys, +V[] values) +
    Creates a mapping from two arrays, one with keys, one with values.
    +
    static intmax​(int... values) +
    Returns the largest of all specified values.
    +
    static java.lang.StringstripTrailing​(java.lang.String s, +java.lang.String substr) +
    Removes the specified trailing pattern from a string.
    +
    static voidtransfer​(java.io.InputStream in, +java.io.OutputStream out, +int bufferSize) +
    Copies data from an input stream to an output stream using a buffer of + specified size.
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      DataUtils

      +
      protected DataUtils()
      +
      Default constructor that prevents creation of class.
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      map

      +
      public static <K,​ +V> java.util.Map<K,​V> map​(K[] keys, +V[] values)
      +
      Creates a mapping from two arrays, one with keys, one with values.
      +
      +
      Type Parameters:
      +
      K - Data type of the keys.
      +
      V - Data type of the values.
      +
      Parameters:
      +
      keys - Array containing the keys.
      +
      values - Array containing the values.
      +
      Returns:
      +
      Map with keys and values from the specified arrays.
      +
      +
      +
    • +
    • +
      +

      join

      +
      public static java.lang.String join​(java.lang.String separator, +java.util.List<?> elements)
      +
      Returns a string containing all elements concatenated by a specified + separator.
      +
      +
      Parameters:
      +
      separator - Separator string.
      +
      elements - List of elements that should be concatenated.
      +
      Returns:
      +
      a concatenated string.
      +
      +
      +
    • +
    • +
      +

      join

      +
      public static java.lang.String join​(java.lang.String separator, +java.lang.Object[] elements)
      +
      Returns a string containing all elements concatenated by a specified + separator.
      +
      +
      Parameters:
      +
      separator - Separator string.
      +
      elements - Array of elements that should be concatenated.
      +
      Returns:
      +
      a concatenated string.
      +
      +
      +
    • +
    • +
      +

      join

      +
      public static java.lang.String join​(java.lang.String separator, +double[] elements)
      +
      Returns a string containing all double numbers concatenated by a + specified separator.
      +
      +
      Parameters:
      +
      separator - Separator string.
      +
      elements - Array of double numbers that should be concatenated.
      +
      Returns:
      +
      a concatenated string.
      +
      +
      +
    • +
    • +
      +

      join

      +
      public static java.lang.String join​(java.lang.String separator, +float[] elements)
      +
      Returns a string containing all float numbers concatenated by a + specified separator.
      +
      +
      Parameters:
      +
      separator - Separator string.
      +
      elements - Array of float numbers that should be concatenated.
      +
      Returns:
      +
      a concatenated string.
      +
      +
      +
    • +
    • +
      +

      max

      +
      public static int max​(int... values)
      +
      Returns the largest of all specified values.
      +
      +
      Parameters:
      +
      values - Several integer values.
      +
      Returns:
      +
      largest value.
      +
      +
      +
    • +
    • +
      +

      transfer

      +
      public static void transfer​(java.io.InputStream in, +java.io.OutputStream out, +int bufferSize) + throws java.io.IOException
      +
      Copies data from an input stream to an output stream using a buffer of + specified size.
      +
      +
      Parameters:
      +
      in - Input stream.
      +
      out - Output stream.
      +
      bufferSize - Size of the copy buffer.
      +
      Throws:
      +
      java.io.IOException - when an error occurs while copying.
      +
      +
      +
    • +
    • +
      +

      format

      +
      public static java.lang.String format​(java.lang.Number number)
      +
      Returns a formatted string of the specified number. All trailing zeroes + or decimal points will be stripped.
      +
      +
      Parameters:
      +
      number - Number to convert to a string.
      +
      Returns:
      +
      A formatted string.
      +
      +
      +
    • +
    • +
      +

      format

      +
      public static java.lang.String format​(java.lang.Object obj)
      +
      Returns a formatted string of the specified object.
      +
      +
      Parameters:
      +
      obj - Object to convert to a string.
      +
      Returns:
      +
      A formatted string.
      +
      +
      +
    • +
    • +
      +

      asList

      +
      public static java.util.List<java.lang.Float> asList​(float[] elements)
      +
      Converts an array of float numbers to a list of Floats. + The list will be empty if the array is empty or null.
      +
      +
      Parameters:
      +
      elements - Array of float numbers.
      +
      Returns:
      +
      A list with all numbers as Float.
      +
      +
      +
    • +
    • +
      +

      asList

      +
      public static java.util.List<java.lang.Double> asList​(double[] elements)
      +
      Converts an array of double numbers to a list of Doubles. + The list will be empty if the array is empty or null.
      +
      +
      Parameters:
      +
      elements - Array of double numbers.
      +
      Returns:
      +
      A list with all numbers as Double.
      +
      +
      +
    • +
    • +
      +

      stripTrailing

      +
      public static java.lang.String stripTrailing​(java.lang.String s, +java.lang.String substr)
      +
      Removes the specified trailing pattern from a string.
      +
      +
      Parameters:
      +
      s - string.
      +
      substr - trailing pattern.
      +
      Returns:
      +
      A string without the trailing pattern.
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/FlateEncodeStream.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/FlateEncodeStream.html new file mode 100644 index 0000000..57b9668 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/FlateEncodeStream.html @@ -0,0 +1,208 @@ + + + + + +FlateEncodeStream (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class FlateEncodeStream

+
+
java.lang.Object +
java.io.OutputStream +
java.io.FilterOutputStream +
java.util.zip.DeflaterOutputStream +
org.xbib.graphics.io.vector.util.FlateEncodeStream
+
+
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
public class FlateEncodeStream
+extends java.util.zip.DeflaterOutputStream
+
+
+
    + +
  • +
    +

    Field Summary

    +
    +

    Fields inherited from class java.util.zip.DeflaterOutputStream

    +buf, def
    +
    +

    Fields inherited from class java.io.FilterOutputStream

    +out
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    FlateEncodeStream​(java.io.OutputStream out) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class java.util.zip.DeflaterOutputStream

    +close, deflate, finish, flush, write, write
    +
    +

    Methods inherited from class java.io.FilterOutputStream

    +write
    +
    +

    Methods inherited from class java.io.OutputStream

    +nullOutputStream
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      FlateEncodeStream

      +
      public FlateEncodeStream​(java.io.OutputStream out)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/FormattingWriter.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/FormattingWriter.html new file mode 100644 index 0000000..0c2e018 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/FormattingWriter.html @@ -0,0 +1,363 @@ + + + + + +FormattingWriter (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class FormattingWriter

+
+
java.lang.Object +
org.xbib.graphics.io.vector.util.FormattingWriter
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
public class FormattingWriter
+extends java.lang.Object
+implements java.io.Closeable, java.io.Flushable
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    FormattingWriter​(java.io.OutputStream out, +java.lang.String encoding, +java.lang.String eol) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidclose() 
    voidflush() 
    FormattingWriterformat​(java.lang.String format, +java.lang.Object... args) 
    longtell() 
    FormattingWriterwrite​(java.lang.Number number) 
    FormattingWriterwrite​(java.lang.String string) 
    FormattingWriterwriteln() 
    FormattingWriterwriteln​(java.lang.Number number) 
    FormattingWriterwriteln​(java.lang.String string) 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      FormattingWriter

      +
      public FormattingWriter​(java.io.OutputStream out, +java.lang.String encoding, +java.lang.String eol)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      write

      +
      public FormattingWriter write​(java.lang.String string) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      write

      +
      public FormattingWriter write​(java.lang.Number number) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      writeln

      +
      public FormattingWriter writeln() + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      writeln

      +
      public FormattingWriter writeln​(java.lang.String string) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      writeln

      +
      public FormattingWriter writeln​(java.lang.Number number) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      format

      +
      public FormattingWriter format​(java.lang.String format, +java.lang.Object... args) + throws java.io.IOException
      +
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      flush

      +
      public void flush() + throws java.io.IOException
      +
      +
      Specified by:
      +
      flush in interface java.io.Flushable
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      close

      +
      public void close() + throws java.io.IOException
      +
      +
      Specified by:
      +
      close in interface java.lang.AutoCloseable
      +
      Specified by:
      +
      close in interface java.io.Closeable
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    • +
      +

      tell

      +
      public long tell()
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/GraphicsUtils.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/GraphicsUtils.html new file mode 100644 index 0000000..935430b --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/GraphicsUtils.html @@ -0,0 +1,375 @@ + + + + + +GraphicsUtils (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class GraphicsUtils

+
+
java.lang.Object +
org.xbib.graphics.io.vector.util.GraphicsUtils
+
+
+
+
public abstract class GraphicsUtils
+extends java.lang.Object
+
Abstract class that contains utility functions for working with graphics. + For example, this includes font handling.
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + +
    Constructors
    ModifierConstructorDescription
    protected GraphicsUtils() +
    Default constructor that prevents creation of class.
    +
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    static java.awt.Shapeclone​(java.awt.Shape shape) 
    static booleanequals​(java.awt.Shape shapeA, +java.awt.Shape shapeB) 
    static java.awt.image.BufferedImagegetAlphaImage​(java.awt.image.BufferedImage image) 
    static java.awt.FontgetPhysicalFont​(java.awt.Font logicalFont) 
    static java.awt.FontgetPhysicalFont​(java.awt.Font logicalFont, +java.lang.String testText) +
    Try to guess physical font from the properties of a logical font, like + "Dialog", "Serif", "Monospaced" etc.
    +
    static booleanhasAlpha​(java.awt.Image image) +
    This method returns true if the specified image has the + possibility to store transparent pixels.
    +
    static java.awt.image.BufferedImagetoBufferedImage​(java.awt.Image image) +
    This method returns a buffered image with the contents of an image.
    +
    static java.awt.image.BufferedImagetoBufferedImage​(java.awt.image.RenderedImage image) +
    Converts an arbitrary image to a BufferedImage.
    +
    static booleanusesAlpha​(java.awt.Image image) +
    This method returns true if the specified image has at least one + pixel that is not fully opaque.
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      GraphicsUtils

      +
      protected GraphicsUtils()
      +
      Default constructor that prevents creation of class.
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      hasAlpha

      +
      public static boolean hasAlpha​(java.awt.Image image)
      +
      This method returns true if the specified image has the + possibility to store transparent pixels. + Inspired by http://www.exampledepot.com/egs/java.awt.image/HasAlpha.html
      +
      +
      Parameters:
      +
      image - Image that should be checked for alpha channel.
      +
      Returns:
      +
      true if the specified image can have transparent pixels, + false otherwise
      +
      +
      +
    • +
    • +
      +

      usesAlpha

      +
      public static boolean usesAlpha​(java.awt.Image image)
      +
      This method returns true if the specified image has at least one + pixel that is not fully opaque.
      +
      +
      Parameters:
      +
      image - Image that should be checked for non-opaque pixels.
      +
      Returns:
      +
      true if the specified image has transparent pixels, + false otherwise
      +
      +
      +
    • +
    • +
      +

      toBufferedImage

      +
      public static java.awt.image.BufferedImage toBufferedImage​(java.awt.image.RenderedImage image)
      +
      Converts an arbitrary image to a BufferedImage.
      +
      +
      Parameters:
      +
      image - Image that should be converted.
      +
      Returns:
      +
      a buffered image containing the image pixels, or the original + instance if the image already was of type BufferedImage.
      +
      +
      +
    • +
    • +
      +

      toBufferedImage

      +
      public static java.awt.image.BufferedImage toBufferedImage​(java.awt.Image image)
      +
      This method returns a buffered image with the contents of an image. + Taken from http://www.exampledepot.com/egs/java.awt.image/Image2Buf.html
      +
      +
      Parameters:
      +
      image - Image to be converted
      +
      Returns:
      +
      a buffered image with the contents of the specified image
      +
      +
      +
    • +
    • +
      +

      clone

      +
      public static java.awt.Shape clone​(java.awt.Shape shape)
      +
      +
    • +
    • +
      +

      getPhysicalFont

      +
      public static java.awt.Font getPhysicalFont​(java.awt.Font logicalFont, +java.lang.String testText)
      +
      Try to guess physical font from the properties of a logical font, like + "Dialog", "Serif", "Monospaced" etc.
      +
      +
      Parameters:
      +
      logicalFont - Logical font object.
      +
      testText - Text used to determine font properties.
      +
      Returns:
      +
      An object of the first matching physical font. The original font + object is returned if it was a physical font or no font matched.
      +
      +
      +
    • +
    • +
      +

      getPhysicalFont

      +
      public static java.awt.Font getPhysicalFont​(java.awt.Font logicalFont)
      +
      +
    • +
    • +
      +

      getAlphaImage

      +
      public static java.awt.image.BufferedImage getAlphaImage​(java.awt.image.BufferedImage image)
      +
      +
    • +
    • +
      +

      equals

      +
      public static boolean equals​(java.awt.Shape shapeA, +java.awt.Shape shapeB)
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ImageDataStream.Interleaving.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ImageDataStream.Interleaving.html new file mode 100644 index 0000000..e4f783e --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ImageDataStream.Interleaving.html @@ -0,0 +1,305 @@ + + + + + +ImageDataStream.Interleaving (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Enum ImageDataStream.Interleaving

+
+
java.lang.Object +
java.lang.Enum<ImageDataStream.Interleaving> +
org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Serializable, java.lang.Comparable<ImageDataStream.Interleaving>, java.lang.constant.Constable
+
+
+
Enclosing class:
+
ImageDataStream
+
+
+
public static enum ImageDataStream.Interleaving
+extends java.lang.Enum<ImageDataStream.Interleaving>
+
+
+
    + +
  • +
    +

    Nested Class Summary

    +
    +

    Nested classes/interfaces inherited from class java.lang.Enum

    +java.lang.Enum.EnumDesc<E extends java.lang.Enum<E>>
    +
    +
  • + +
  • +
    +

    Enum Constant Summary

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Enum Constants
    Enum ConstantDescription
    ALPHA_ONLY 
    ROW 
    SAMPLE 
    WITHOUT_ALPHA 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    static ImageDataStream.InterleavingvalueOf​(java.lang.String name) +
    Returns the enum constant of this type with the specified name.
    +
    static ImageDataStream.Interleaving[]values() +
    Returns an array containing the constants of this enum type, in +the order they are declared.
    +
    +
    +
    +
    +

    Methods inherited from class java.lang.Enum

    +clone, compareTo, describeConstable, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
    +
    +

    Methods inherited from class java.lang.Object

    +getClass, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Enum Constant Details

    + +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      values

      +
      public static ImageDataStream.Interleaving[] values()
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      Returns:
      +
      an array containing the constants of this enum type, in the order they are declared
      +
      +
      +
    • +
    • +
      +

      valueOf

      +
      public static ImageDataStream.Interleaving valueOf​(java.lang.String name)
      +
      Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
      +
      +
      Parameters:
      +
      name - the name of the enum constant to be returned.
      +
      Returns:
      +
      the enum constant with the specified name
      +
      Throws:
      +
      java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
      +
      java.lang.NullPointerException - if the argument is null
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ImageDataStream.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ImageDataStream.html new file mode 100644 index 0000000..07b0551 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/ImageDataStream.html @@ -0,0 +1,283 @@ + + + + + +ImageDataStream (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class ImageDataStream

+
+
java.lang.Object +
java.io.InputStream +
org.xbib.graphics.io.vector.util.ImageDataStream
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.lang.AutoCloseable
+
+
+
public class ImageDataStream
+extends java.io.InputStream
+
+
+
    + +
  • +
    +

    Nested Class Summary

    +
    + + + + + + + + + + + + + + + + +
    Nested Classes
    Modifier and TypeClassDescription
    static class ImageDataStream.Interleaving 
    +
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    ImageDataStream​(java.awt.image.BufferedImage image, +ImageDataStream.Interleaving interleaving) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    java.awt.image.BufferedImagegetImage() 
    ImageDataStream.InterleavinggetInterleaving() 
    intread() 
    +
    +
    +
    +

    Methods inherited from class java.io.InputStream

    +available, close, mark, markSupported, nullInputStream, read, read, readAllBytes, readNBytes, readNBytes, reset, skip, skipNBytes, transferTo
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    + +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getImage

      +
      public java.awt.image.BufferedImage getImage()
      +
      +
    • +
    • +
      +

      getInterleaving

      +
      public ImageDataStream.Interleaving getInterleaving()
      +
      +
    • +
    • +
      +

      read

      +
      public int read() + throws java.io.IOException
      +
      +
      Specified by:
      +
      read in class java.io.InputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/LineWrapOutputStream.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/LineWrapOutputStream.html new file mode 100644 index 0000000..534ab23 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/LineWrapOutputStream.html @@ -0,0 +1,301 @@ + + + + + +LineWrapOutputStream (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class LineWrapOutputStream

+
+
java.lang.Object +
java.io.OutputStream +
java.io.FilterOutputStream +
org.xbib.graphics.io.vector.util.LineWrapOutputStream
+
+
+
+
+
+
All Implemented Interfaces:
+
java.io.Closeable, java.io.Flushable, java.lang.AutoCloseable
+
+
+
public class LineWrapOutputStream
+extends java.io.FilterOutputStream
+
+
+
    + +
  • +
    +

    Field Summary

    +
    + + + + + + + + + + + + + + + + +
    Fields
    Modifier and TypeFieldDescription
    static java.lang.StringSTANDARD_EOL 
    +
    +
    +

    Fields inherited from class java.io.FilterOutputStream

    +out
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    LineWrapOutputStream​(java.io.OutputStream sink, +int lineWidth) 
    LineWrapOutputStream​(java.io.OutputStream sink, +int lineWidth, +java.lang.String eol) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    voidwrite​(int b) 
    +
    +
    +
    +

    Methods inherited from class java.io.FilterOutputStream

    +close, flush, write, write
    +
    +

    Methods inherited from class java.io.OutputStream

    +nullOutputStream
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      STANDARD_EOL

      +
      public static final java.lang.String STANDARD_EOL
      +
      +
      See Also:
      +
      Constant Field Values
      +
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      LineWrapOutputStream

      +
      public LineWrapOutputStream​(java.io.OutputStream sink, +int lineWidth, +java.lang.String eol)
      +
      +
    • +
    • +
      +

      LineWrapOutputStream

      +
      public LineWrapOutputStream​(java.io.OutputStream sink, +int lineWidth)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      write

      +
      public void write​(int b) + throws java.io.IOException
      +
      +
      Overrides:
      +
      write in class java.io.FilterOutputStream
      +
      Throws:
      +
      java.io.IOException
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.Key.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.Key.html new file mode 100644 index 0000000..aee65f0 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.Key.html @@ -0,0 +1,259 @@ + + + + + +VectorHints.Key (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class VectorHints.Key

+
+
java.lang.Object +
java.awt.RenderingHints.Key +
org.xbib.graphics.io.vector.util.VectorHints.Key
+
+
+
+
+
Enclosing class:
+
VectorHints
+
+
+
public static class VectorHints.Key
+extends java.awt.RenderingHints.Key
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Key​(int privateKey, +java.lang.String description) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    intgetIndex() 
    booleanisCompatibleValue​(java.lang.Object val) 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class java.awt.RenderingHints.Key

    +equals, hashCode, intKey
    +
    +

    Methods inherited from class java.lang.Object

    +clone, finalize, getClass, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Key

      +
      public Key​(int privateKey, +java.lang.String description)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      getIndex

      +
      public int getIndex()
      +
      +
    • +
    • +
      +

      isCompatibleValue

      +
      public boolean isCompatibleValue​(java.lang.Object val)
      +
      +
      Specified by:
      +
      isCompatibleValue in class java.awt.RenderingHints.Key
      +
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class java.lang.Object
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.Value.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.Value.html new file mode 100644 index 0000000..f187cab --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.Value.html @@ -0,0 +1,263 @@ + + + + + +VectorHints.Value (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class VectorHints.Value

+
+
java.lang.Object +
org.xbib.graphics.io.vector.util.VectorHints.Value
+
+
+
+
Enclosing class:
+
VectorHints
+
+
+
public static class VectorHints.Value
+extends java.lang.Object
+
+
+
    + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + +
    Constructors
    ConstructorDescription
    Value​(VectorHints.Key key, +int index, +java.lang.String description) 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Modifier and TypeMethodDescription
    java.lang.StringgetId() 
    intgetIndex() 
    booleanisCompatibleKey​(java.awt.RenderingHints.Key key) 
    java.lang.StringtoString() 
    +
    +
    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      Value

      +
      public Value​(VectorHints.Key key, +int index, +java.lang.String description)
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Method Details

    +
      +
    • +
      +

      isCompatibleKey

      +
      public boolean isCompatibleKey​(java.awt.RenderingHints.Key key)
      +
      +
    • +
    • +
      +

      getIndex

      +
      public int getIndex()
      +
      +
    • +
    • +
      +

      getId

      +
      public java.lang.String getId()
      +
      +
    • +
    • +
      +

      toString

      +
      public java.lang.String toString()
      +
      +
      Overrides:
      +
      toString in class java.lang.Object
      +
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.html new file mode 100644 index 0000000..488799b --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/VectorHints.html @@ -0,0 +1,314 @@ + + + + + +VectorHints (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+ + +

Class VectorHints

+
+
java.lang.Object +
org.xbib.graphics.io.vector.util.VectorHints
+
+
+
+
public abstract class VectorHints
+extends java.lang.Object
+
+
+
    + +
  • +
    +

    Nested Class Summary

    +
    + + + + + + + + + + + + + + + + + + + + + +
    Nested Classes
    Modifier and TypeClassDescription
    static class VectorHints.Key 
    static class VectorHints.Value 
    +
    +
    +
  • + +
  • +
    +

    Field Summary

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Fields
    Modifier and TypeFieldDescription
    static VectorHints.KeyKEY_EXPORT 
    static VectorHints.KeyKEY_TEXT 
    static java.lang.ObjectVALUE_EXPORT_QUALITY 
    static java.lang.ObjectVALUE_EXPORT_READABILITY 
    static java.lang.ObjectVALUE_EXPORT_SIZE 
    static java.lang.ObjectVALUE_TEXT_DEFAULT 
    static java.lang.ObjectVALUE_TEXT_VECTOR 
    +
    +
    +
  • + +
  • +
    +

    Constructor Summary

    +
    + + + + + + + + + + + + + + + + +
    Constructors
    ModifierConstructorDescription
    protected VectorHints() 
    +
    +
    +
  • + +
  • +
    +

    Method Summary

    +
    +

    Methods inherited from class java.lang.Object

    +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    +
    +
  • +
+
+
+
    + +
  • +
    +

    Field Details

    +
      +
    • +
      +

      KEY_EXPORT

      +
      public static final VectorHints.Key KEY_EXPORT
      +
      +
    • +
    • +
      +

      VALUE_EXPORT_READABILITY

      +
      public static final java.lang.Object VALUE_EXPORT_READABILITY
      +
      +
    • +
    • +
      +

      VALUE_EXPORT_QUALITY

      +
      public static final java.lang.Object VALUE_EXPORT_QUALITY
      +
      +
    • +
    • +
      +

      VALUE_EXPORT_SIZE

      +
      public static final java.lang.Object VALUE_EXPORT_SIZE
      +
      +
    • +
    • +
      +

      KEY_TEXT

      +
      public static final VectorHints.Key KEY_TEXT
      +
      +
    • +
    • +
      +

      VALUE_TEXT_DEFAULT

      +
      public static final java.lang.Object VALUE_TEXT_DEFAULT
      +
      +
    • +
    • +
      +

      VALUE_TEXT_VECTOR

      +
      public static final java.lang.Object VALUE_TEXT_VECTOR
      +
      +
    • +
    +
    +
  • + +
  • +
    +

    Constructor Details

    +
      +
    • +
      +

      VectorHints

      +
      protected VectorHints()
      +
      +
    • +
    +
    +
  • +
+
+ +
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/package-summary.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/package-summary.html new file mode 100644 index 0000000..acf8071 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/package-summary.html @@ -0,0 +1,171 @@ + + + + + +org.xbib.graphics.io.vector.util (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+ +

Package org.xbib.graphics.io.vector.util

+
+
+ +
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/package-tree.html b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/package-tree.html new file mode 100644 index 0000000..5578957 --- /dev/null +++ b/io-vector/build/docs/javadoc/org.xbib.graphics.io.vector/org/xbib/graphics/io/vector/util/package-tree.html @@ -0,0 +1,138 @@ + + + + + +org.xbib.graphics.io.vector.util Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.xbib.graphics.io.vector.util

+Package Hierarchies: + +
+
+

Class Hierarchy

+
    +
  • java.lang.Object +
      +
    • org.xbib.graphics.io.vector.util.AlphaToMaskOp (implements java.awt.image.BufferedImageOp)
    • +
    • org.xbib.graphics.io.vector.util.DataUtils
    • +
    • org.xbib.graphics.io.vector.util.FormattingWriter (implements java.io.Closeable, java.io.Flushable)
    • +
    • org.xbib.graphics.io.vector.util.GraphicsUtils
    • +
    • java.io.InputStream (implements java.io.Closeable) + +
    • +
    • java.io.OutputStream (implements java.io.Closeable, java.io.Flushable) + +
    • +
    • java.awt.RenderingHints.Key + +
    • +
    • org.xbib.graphics.io.vector.util.VectorHints
    • +
    • org.xbib.graphics.io.vector.util.VectorHints.Value
    • +
    +
  • +
+
+
+

Enum Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.lang.constant.Constable, java.io.Serializable) + +
    • +
    +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/overview-tree.html b/io-vector/build/docs/javadoc/overview-tree.html new file mode 100644 index 0000000..918c680 --- /dev/null +++ b/io-vector/build/docs/javadoc/overview-tree.html @@ -0,0 +1,235 @@ + + + + + +Class Hierarchy (io-vector 3.0.0 API) + + + + + + + + + + + + + +
+ +
+
+ +
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+

Enum Hierarchy

+
    +
  • java.lang.Object + +
  • +
+
+
+ +
+
+ + diff --git a/io-vector/build/docs/javadoc/package-search-index.js b/io-vector/build/docs/javadoc/package-search-index.js new file mode 100644 index 0000000..8baa428 --- /dev/null +++ b/io-vector/build/docs/javadoc/package-search-index.js @@ -0,0 +1 @@ +packageSearchIndex = [{"l":"All Packages","u":"allpackages-index.html"},{"m":"org.xbib.graphics.io.vector","l":"org.xbib.graphics.io.vector"},{"m":"org.xbib.graphics.io.vector","l":"org.xbib.graphics.io.vector.commands"},{"m":"org.xbib.graphics.io.vector","l":"org.xbib.graphics.io.vector.eps"},{"m":"org.xbib.graphics.io.vector","l":"org.xbib.graphics.io.vector.filters"},{"m":"org.xbib.graphics.io.vector","l":"org.xbib.graphics.io.vector.pdf"},{"m":"org.xbib.graphics.io.vector","l":"org.xbib.graphics.io.vector.svg"},{"m":"org.xbib.graphics.io.vector","l":"org.xbib.graphics.io.vector.util"}];updateSearchResults(); \ No newline at end of file diff --git a/io-vector/build/docs/javadoc/resources/glass.png b/io-vector/build/docs/javadoc/resources/glass.png new file mode 100644 index 0000000..a7f591f Binary files /dev/null and b/io-vector/build/docs/javadoc/resources/glass.png differ diff --git a/io-vector/build/docs/javadoc/resources/x.png b/io-vector/build/docs/javadoc/resources/x.png new file mode 100644 index 0000000..30548a7 Binary files /dev/null and b/io-vector/build/docs/javadoc/resources/x.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_55_fbf9ee_1x400.png b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000..34abd18 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_65_dadada_1x400.png b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_65_dadada_1x400.png new file mode 100644 index 0000000..f058a93 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_65_dadada_1x400.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_75_dadada_1x400.png b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 0000000..2ce04c1 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_75_e6e6e6_1x400.png b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 0000000..a90afb8 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_95_fef1ec_1x400.png b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000..dbe091f Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000..5dc3593 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-icons_222222_256x240.png b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000..e723e17 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_222222_256x240.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-icons_2e83ff_256x240.png b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000..1f5f497 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_2e83ff_256x240.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-icons_454545_256x240.png b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000..618f5b0 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_454545_256x240.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-icons_888888_256x240.png b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_888888_256x240.png new file mode 100644 index 0000000..ee5e33f Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_888888_256x240.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/images/ui-icons_cd0a0a_256x240.png b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000..7e8ebc1 Binary files /dev/null and b/io-vector/build/docs/javadoc/script-dir/images/ui-icons_cd0a0a_256x240.png differ diff --git a/io-vector/build/docs/javadoc/script-dir/jquery-3.5.1.min.js b/io-vector/build/docs/javadoc/script-dir/jquery-3.5.1.min.js new file mode 100644 index 0000000..b061403 --- /dev/null +++ b/io-vector/build/docs/javadoc/script-dir/jquery-3.5.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0a;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,l=/top|center|bottom/,h=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};h>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),l.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-r-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-r-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.widget("ui.menu",{version:"1.12.1",defaultElement:"
    ",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,l=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=l.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=l.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n;this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("
      ").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,this.element[0]!==t.ui.safeActiveElement(this.document[0])&&this.element.trigger("focus")})},menufocus:function(e,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",e,{item:n})&&e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&t.trim(s).length&&(this.liveRegion.children().hide(),t("
      ").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,i){var s=i.item.data("ui-autocomplete-item"),n=this.previous;this.element[0]!==t.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=n,this._delay(function(){this.previous=n,this.selectedItem=s})),!1!==this._trigger("select",e,{item:s})&&this._value(s.value),this.term=this._value(),this.close(e),this.selectedItem=s}}),this.liveRegion=t("
      ",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(e){var i=this.menu.element[0];return e.target===this.element[0]||e.target===i||t.contains(i,e.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){var e=this.term===this._value(),i=this.menu.element.is(":visible"),s=t.altKey||t.ctrlKey||t.metaKey||t.shiftKey;(!e||e&&!i&&!s)&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length").append(t("
      ").text(i.label)).appendTo(e)},_move:function(t,e){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[t](e),void 0):(this.search(null,e),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),t.extend(t.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(e,i){var s=RegExp(t.ui.autocomplete.escapeRegex(i),"i");return t.grep(e,function(t){return s.test(t.label||t.value||t)})}}),t.widget("ui.autocomplete",t.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(t>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.children().hide(),t("
      ").text(i).appendTo(this.liveRegion))}}),t.ui.autocomplete}); \ No newline at end of file diff --git a/io-vector/build/docs/javadoc/script-dir/jquery-ui.structure.min.css b/io-vector/build/docs/javadoc/script-dir/jquery-ui.structure.min.css new file mode 100644 index 0000000..e880892 --- /dev/null +++ b/io-vector/build/docs/javadoc/script-dir/jquery-ui.structure.min.css @@ -0,0 +1,5 @@ +/*! jQuery UI - v1.12.1 - 2018-12-06 +* http://jqueryui.com +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0} \ No newline at end of file diff --git a/io-vector/build/docs/javadoc/script.js b/io-vector/build/docs/javadoc/script.js new file mode 100644 index 0000000..4428408 --- /dev/null +++ b/io-vector/build/docs/javadoc/script.js @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +var moduleSearchIndex; +var packageSearchIndex; +var typeSearchIndex; +var memberSearchIndex; +var tagSearchIndex; +function loadScripts(doc, tag) { + createElem(doc, tag, 'search.js'); + + createElem(doc, tag, 'module-search-index.js'); + createElem(doc, tag, 'package-search-index.js'); + createElem(doc, tag, 'type-search-index.js'); + createElem(doc, tag, 'member-search-index.js'); + createElem(doc, tag, 'tag-search-index.js'); +} + +function createElem(doc, tag, path) { + var script = doc.createElement(tag); + var scriptElement = doc.getElementsByTagName(tag)[0]; + script.src = pathtoroot + path; + scriptElement.parentNode.insertBefore(script, scriptElement); +} + +function show(type) { + count = 0; + for (var key in data) { + var row = document.getElementById(key); + if ((data[key] & type) !== 0) { + row.style.display = ''; + row.className = (count++ % 2) ? rowColor : altColor; + } + else + row.style.display = 'none'; + } + updateTabs(type); +} + +function updateTabs(type) { + var firstRow = document.getElementById(Object.keys(data)[0]); + var table = firstRow.closest('table'); + for (var value in tabs) { + var tab = document.getElementById(tabs[value][0]); + if (value == type) { + tab.className = activeTableTab; + tab.innerHTML = tabs[value][1]; + tab.setAttribute('aria-selected', true); + tab.setAttribute('tabindex',0); + table.setAttribute('aria-labelledby', tabs[value][0]); + } + else { + tab.className = tableTab; + tab.setAttribute('aria-selected', false); + tab.setAttribute('tabindex',-1); + tab.setAttribute('onclick', "show("+ value + ")"); + tab.innerHTML = tabs[value][1]; + } + } +} + +function switchTab(e) { + if (e.keyCode == 37 || e.keyCode == 38) { + $("[aria-selected=true]").prev().click().focus(); + e.preventDefault(); + } + if (e.keyCode == 39 || e.keyCode == 40) { + $("[aria-selected=true]").next().click().focus(); + e.preventDefault(); + } +} + +var updateSearchResults = function() {}; + +function indexFilesLoaded() { + return moduleSearchIndex + && packageSearchIndex + && typeSearchIndex + && memberSearchIndex + && tagSearchIndex; +} diff --git a/io-vector/build/docs/javadoc/search.js b/io-vector/build/docs/javadoc/search.js new file mode 100644 index 0000000..9d19ba1 --- /dev/null +++ b/io-vector/build/docs/javadoc/search.js @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +var noResult = {l: "No results found"}; +var loading = {l: "Loading search index..."}; +var catModules = "Modules"; +var catPackages = "Packages"; +var catTypes = "Types"; +var catMembers = "Members"; +var catSearchTags = "SearchTags"; +var highlight = "$&"; +var searchPattern = ""; +var RANKING_THRESHOLD = 2; +var NO_MATCH = 0xffff; +var MAX_RESULTS_PER_CATEGORY = 500; +var UNNAMED = ""; +function escapeHtml(str) { + return str.replace(//g, ">"); +} +function getHighlightedText(item, matcher) { + var escapedItem = escapeHtml(item); + return escapedItem.replace(matcher, highlight); +} +function getURLPrefix(ui) { + var urlPrefix=""; + var slash = "/"; + if (ui.item.category === catModules) { + return ui.item.l + slash; + } else if (ui.item.category === catPackages && ui.item.m) { + return ui.item.m + slash; + } else if (ui.item.category === catTypes || ui.item.category === catMembers) { + if (ui.item.m) { + urlPrefix = ui.item.m + slash; + } else { + $.each(packageSearchIndex, function(index, item) { + if (item.m && ui.item.p === item.l) { + urlPrefix = item.m + slash; + } + }); + } + return urlPrefix; + } + return urlPrefix; +} +function makeCamelCaseRegex(term) { + var pattern = ""; + var isWordToken = false; + term.replace(/,\s*/g, ", ").trim().split(/\s+/).forEach(function(w, index) { + if (index > 0) { + // whitespace between identifiers is significant + pattern += (isWordToken && /^\w/.test(w)) ? "\\s+" : "\\s*"; + } + var tokens = w.split(/(?=[A-Z,.()<>[\/])/); + for (var i = 0; i < tokens.length; i++) { + var s = tokens[i]; + if (s === "") { + continue; + } + pattern += $.ui.autocomplete.escapeRegex(s); + isWordToken = /\w$/.test(s); + if (isWordToken) { + pattern += "([a-z0-9_$<>\\[\\]]*?)"; + } + } + }); + return pattern; +} +function createMatcher(pattern, flags) { + var isCamelCase = /[A-Z]/.test(pattern); + return new RegExp(pattern, flags + (isCamelCase ? "" : "i")); +} +var watermark = 'Search'; +$(function() { + $("#search").val(''); + $("#search").prop("disabled", false); + $("#reset").prop("disabled", false); + $("#search").val(watermark).addClass('watermark'); + $("#search").blur(function() { + if ($(this).val().length == 0) { + $(this).val(watermark).addClass('watermark'); + } + }); + $("#search").on('click keydown paste', function() { + if ($(this).val() == watermark) { + $(this).val('').removeClass('watermark'); + } + }); + $("#reset").click(function() { + $("#search").val(''); + $("#search").focus(); + }); + $("#search").focus(); + $("#search")[0].setSelectionRange(0, 0); +}); +$.widget("custom.catcomplete", $.ui.autocomplete, { + _create: function() { + this._super(); + this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)"); + }, + _renderMenu: function(ul, items) { + var rMenu = this; + var currentCategory = ""; + rMenu.menu.bindings = $(); + $.each(items, function(index, item) { + var li; + if (item.category && item.category !== currentCategory) { + ul.append("
    • " + item.category + "
    • "); + currentCategory = item.category; + } + li = rMenu._renderItemData(ul, item); + if (item.category) { + li.attr("aria-label", item.category + " : " + item.l); + li.attr("class", "result-item"); + } else { + li.attr("aria-label", item.l); + li.attr("class", "result-item"); + } + }); + }, + _renderItem: function(ul, item) { + var label = ""; + var matcher = createMatcher(escapeHtml(searchPattern), "g"); + if (item.category === catModules) { + label = getHighlightedText(item.l, matcher); + } else if (item.category === catPackages) { + label = getHighlightedText(item.l, matcher); + } else if (item.category === catTypes) { + label = (item.p && item.p !== UNNAMED) + ? getHighlightedText(item.p + "." + item.l, matcher) + : getHighlightedText(item.l, matcher); + } else if (item.category === catMembers) { + label = (item.p && item.p !== UNNAMED) + ? getHighlightedText(item.p + "." + item.c + "." + item.l, matcher) + : getHighlightedText(item.c + "." + item.l, matcher); + } else if (item.category === catSearchTags) { + label = getHighlightedText(item.l, matcher); + } else { + label = item.l; + } + var li = $("
    • ").appendTo(ul); + var div = $("
      ").appendTo(li); + if (item.category === catSearchTags) { + if (item.d) { + div.html(label + " (" + item.h + ")
      " + + item.d + "
      "); + } else { + div.html(label + " (" + item.h + ")"); + } + } else { + if (item.m) { + div.html(item.m + "/" + label); + } else { + div.html(label); + } + } + return li; + } +}); +function rankMatch(match, category) { + if (!match) { + return NO_MATCH; + } + var index = match.index; + var input = match.input; + var leftBoundaryMatch = 2; + var periferalMatch = 0; + var delta = 0; + // make sure match is anchored on a left word boundary + if (index === 0 || /\W/.test(input[index - 1]) || "_" === input[index - 1] || "_" === input[index]) { + leftBoundaryMatch = 0; + } else if (input[index] === input[index].toUpperCase() && !/^[A-Z0-9_$]+$/.test(input)) { + leftBoundaryMatch = 1; + } + var matchEnd = index + match[0].length; + var leftParen = input.indexOf("("); + // exclude peripheral matches + if (category !== catModules && category !== catSearchTags) { + var endOfName = leftParen > -1 ? leftParen : input.length; + var delim = category === catPackages ? "/" : "."; + if (leftParen > -1 && leftParen < index) { + periferalMatch += 2; + } else if (input.lastIndexOf(delim, endOfName) >= matchEnd) { + periferalMatch += 2; + } + } + for (var i = 1; i < match.length; i++) { + // lower ranking if parts of the name are missing + if (match[i]) + delta += match[i].length; + } + if (category === catTypes) { + // lower ranking if a type name contains unmatched camel-case parts + if (/[A-Z]/.test(input.substring(matchEnd))) + delta += 5; + if (/[A-Z]/.test(input.substring(0, index))) + delta += 5; + } + return leftBoundaryMatch + periferalMatch + (delta / 200); + +} +function doSearch(request, response) { + var result = []; + var newResults = []; + + searchPattern = makeCamelCaseRegex(request.term); + if (searchPattern === "") { + return this.close(); + } + var camelCaseMatcher = createMatcher(searchPattern, ""); + var boundaryMatcher = createMatcher("\\b" + searchPattern, ""); + + function concatResults(a1, a2) { + a2.sort(function(e1, e2) { + return e1.ranking - e2.ranking; + }); + a1 = a1.concat(a2.map(function(e) { return e.item; })); + a2.length = 0; + return a1; + } + + if (moduleSearchIndex) { + $.each(moduleSearchIndex, function(index, item) { + item.category = catModules; + var ranking = rankMatch(boundaryMatcher.exec(item.l), catModules); + if (ranking < RANKING_THRESHOLD) { + newResults.push({ ranking: ranking, item: item }); + } + return newResults.length < MAX_RESULTS_PER_CATEGORY; + }); + result = concatResults(result, newResults); + } + if (packageSearchIndex) { + $.each(packageSearchIndex, function(index, item) { + item.category = catPackages; + var name = (item.m && request.term.indexOf("/") > -1) + ? (item.m + "/" + item.l) + : item.l; + var ranking = rankMatch(boundaryMatcher.exec(name), catPackages); + if (ranking < RANKING_THRESHOLD) { + newResults.push({ ranking: ranking, item: item }); + } + return newResults.length < MAX_RESULTS_PER_CATEGORY; + }); + result = concatResults(result, newResults); + } + if (typeSearchIndex) { + $.each(typeSearchIndex, function(index, item) { + item.category = catTypes; + var name = request.term.indexOf(".") > -1 + ? item.p + "." + item.l + : item.l; + var ranking = rankMatch(camelCaseMatcher.exec(name), catTypes); + if (ranking < RANKING_THRESHOLD) { + newResults.push({ ranking: ranking, item: item }); + } + return newResults.length < MAX_RESULTS_PER_CATEGORY; + }); + result = concatResults(result, newResults); + } + if (memberSearchIndex) { + $.each(memberSearchIndex, function(index, item) { + item.category = catMembers; + var name = request.term.indexOf(".") > -1 + ? item.p + "." + item.c + "." + item.l + : item.l; + var ranking = rankMatch(camelCaseMatcher.exec(name), catMembers); + if (ranking < RANKING_THRESHOLD) { + newResults.push({ ranking: ranking, item: item }); + } + return newResults.length < MAX_RESULTS_PER_CATEGORY; + }); + result = concatResults(result, newResults); + } + if (tagSearchIndex) { + $.each(tagSearchIndex, function(index, item) { + item.category = catSearchTags; + var ranking = rankMatch(boundaryMatcher.exec(item.l), catSearchTags); + if (ranking < RANKING_THRESHOLD) { + newResults.push({ ranking: ranking, item: item }); + } + return newResults.length < MAX_RESULTS_PER_CATEGORY; + }); + result = concatResults(result, newResults); + } + if (!indexFilesLoaded()) { + updateSearchResults = function() { + doSearch(request, response); + } + result.unshift(loading); + } else { + updateSearchResults = function() {}; + } + response(result); +} +$(function() { + $("#search").catcomplete({ + minLength: 1, + delay: 300, + source: doSearch, + response: function(event, ui) { + if (!ui.content.length) { + ui.content.push(noResult); + } else { + $("#search").empty(); + } + }, + autoFocus: true, + focus: function(event, ui) { + return false; + }, + position: { + collision: "flip" + }, + select: function(event, ui) { + if (ui.item.category) { + var url = getURLPrefix(ui); + if (ui.item.category === catModules) { + url += "module-summary.html"; + } else if (ui.item.category === catPackages) { + if (ui.item.u) { + url = ui.item.u; + } else { + url += ui.item.l.replace(/\./g, '/') + "/package-summary.html"; + } + } else if (ui.item.category === catTypes) { + if (ui.item.u) { + url = ui.item.u; + } else if (ui.item.p === UNNAMED) { + url += ui.item.l + ".html"; + } else { + url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.l + ".html"; + } + } else if (ui.item.category === catMembers) { + if (ui.item.p === UNNAMED) { + url += ui.item.c + ".html" + "#"; + } else { + url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.c + ".html" + "#"; + } + if (ui.item.u) { + url += ui.item.u; + } else { + url += ui.item.l; + } + } else if (ui.item.category === catSearchTags) { + url += ui.item.u; + } + if (top !== window) { + parent.classFrame.location = pathtoroot + url; + } else { + window.location.href = pathtoroot + url; + } + $("#search").focus(); + } + } + }); +}); diff --git a/io-vector/build/docs/javadoc/stylesheet.css b/io-vector/build/docs/javadoc/stylesheet.css new file mode 100644 index 0000000..79a9d97 --- /dev/null +++ b/io-vector/build/docs/javadoc/stylesheet.css @@ -0,0 +1,792 @@ +/* + * Javadoc style sheet + */ + +@import url('resources/fonts/dejavu.css'); + +/* + * Styles for individual HTML elements. + * + * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular + * HTML element throughout the page. + */ + +body { + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; + padding:0; + height:100%; + width:100%; +} +iframe { + margin:0; + padding:0; + height:100%; + width:100%; + overflow-y:scroll; + border:none; +} +a:link, a:visited { + text-decoration:none; + color:#4A6782; +} +a[href]:hover, a[href]:focus { + text-decoration:none; + color:#bb7a2a; +} +a[name] { + color:#353833; +} +pre { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; +} +h1 { + font-size:20px; +} +h2 { + font-size:18px; +} +h3 { + font-size:16px; +} +h4 { + font-size:13px; +} +h5 { + font-size:12px; +} +h6 { + font-size:11px; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; +} +dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; +} +table tr td dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} +button { + font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size: 14px; +} +/* + * Styles for HTML generated by javadoc. + * + * These are style classes that are used by the standard doclet to generate HTML documentation. + */ + +/* + * Styles for document title and copyright. + */ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.about-language { + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; +} +.legal-copy { + margin-left:.5em; +} +.tab { + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* + * Styles for navigation bar. + */ +@media screen { + .flex-box { + position:fixed; + display:flex; + flex-direction:column; + height: 100%; + width: 100%; + } + .flex-header { + flex: 0 0 auto; + } + .flex-content { + flex: 1 1 auto; + overflow-y: auto; + } +} +.top-nav { + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.bottom-nav { + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.sub-nav { + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; +} +.sub-nav div { + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; +} +.sub-nav .nav-list { + padding-top:5px; +} +ul.nav-list, ul.sub-nav-list { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.nav-list li{ + list-style:none; + float:left; + padding: 5px 6px; + text-transform:uppercase; +} +.sub-nav .nav-list-search { + float:right; + margin:0 0 0 0; + padding:5px 6px; + clear:none; +} +.nav-list-search label { + position:relative; + right:-16px; +} +ul.sub-nav-list li { + list-style:none; + float:left; + padding-top:10px; +} +.top-nav a:link, .top-nav a:active, .top-nav a:visited, .bottom-nav a:link, .bottom-nav a:active, .bottom-nav a:visited { + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; +} +.top-nav a:hover, .bottom-nav a:hover { + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; +} +.nav-bar-cell1-rev { + background-color:#F8981D; + color:#253441; + margin: auto 5px; +} +.skip-nav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* + * Hide navigation links and search box in print layout + */ +@media print { + ul.nav-list, div.sub-nav { + display:none; + } +} +/* + * Styles for page header and footer. + */ +.title { + color:#2c4557; + margin:10px 0; +} +.sub-title { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 15px 0; + padding:0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:13px; +} +/* + * Styles for headings. + */ +body.class-declaration-page .summary h2, +body.class-declaration-page .details h2, +body.class-use-page h2, +body.module-declaration-page .block-list h2 { + font-style: italic; + padding:0; + margin:15px 0; +} +body.class-declaration-page .summary h3, +body.class-declaration-page .details h3, +body.class-declaration-page .summary .inherited-list h2 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +/* + * Styles for page layout containers. + */ +main { + clear:both; + padding:10px 20px; + position:relative; +} +dl.notes > dt { + font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +dl.notes > dd { + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} +dl.name-value > dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +dl.name-value > dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* + * Styles for lists. + */ +li.circle { + list-style:circle; +} +ul.horizontal li { + display:inline; + font-size:0.9em; +} +div.inheritance { + margin:0; + padding:0; +} +div.inheritance div.inheritance { + margin-left:2em; +} +ul.block-list, +ul.details-list, +ul.member-list, +ul.summary-list { + margin:10px 0 10px 0; + padding:0; +} +ul.block-list > li, +ul.details-list > li, +ul.member-list > li, +ul.summary-list > li { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* + * Styles for tables. + */ +.overview-summary table, .member-summary table, .type-summary table, .use-summary table, .constants-summary table, .deprecated-summary table, +.requires-summary table, .packages-summary table, .provides-summary table, .uses-summary table, .system-properties-summary table { + width:100%; + border-spacing:0; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; +} +.overview-summary table, .member-summary table, .requires-summary table, .packages-summary table, +.provides-summary table, .uses-summary table, .system-properties-summary table { + padding:0px; +} +.overview-summary caption, .member-summary caption, .type-summary caption, +.use-summary caption, .constants-summary caption, .deprecated-summary caption, +.requires-summary caption, .packages-summary caption, .provides-summary caption, +.uses-summary caption, .system-properties-summary caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; +} +.constants-summary caption a:link, .constants-summary caption a:visited, +.use-summary caption a:link, .use-summary caption a:visited { + color:#1f389c; +} +.overview-summary caption a:link, .member-summary caption a:link, .type-summary caption a:link, +.deprecated-summary caption a:link, +.requires-summary caption a:link, .packages-summary caption a:link, .provides-summary caption a:link, +.uses-summary caption a:link, +.overview-summary caption a:hover, .member-summary caption a:hover, .type-summary caption a:hover, +.use-summary caption a:hover, .constants-summary caption a:hover, .deprecated-summary caption a:hover, +.requires-summary caption a:hover, .packages-summary caption a:hover, .provides-summary caption a:hover, +.uses-summary caption a:hover, +.overview-summary caption a:active, .member-summary caption a:active, .type-summary caption a:active, +.use-summary caption a:active, .constants-summary caption a:active, .deprecated-summary caption a:active, +.requires-summary caption a:active, .packages-summary caption a:active, .provides-summary caption a:active, +.uses-summary caption a:active, +.overview-summary caption a:visited, .member-summary caption a:visited, .type-summary caption a:visited, +.deprecated-summary caption a:visited, +.requires-summary caption a:visited, .packages-summary caption a:visited, .provides-summary caption a:visited, +.uses-summary caption a:visited { + color:#FFFFFF; +} +.overview-summary caption span, .member-summary caption span, .type-summary caption span, +.use-summary caption span, .constants-summary caption span, .deprecated-summary caption span, +.requires-summary caption span, .packages-summary caption span, .provides-summary caption span, +.uses-summary caption span, .system-properties-summary caption span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; + border: none; + height:16px; +} + +div.table-tabs > button { + border: none; + cursor: pointer; + padding: 5px 12px 7px 12px; + font-weight: bold; + margin-right: 3px; +} +div.table-tabs > button.active-table-tab { + background: #F8981D; + color: #253441; +} +div.table-tabs > button.table-tab { + background: #4D7A97; + color: #FFFFFF; +} + +.row-color th, +.alt-color th { + font-weight:normal; +} +.overview-summary td, .member-summary td, .type-summary td, +.use-summary td, .constants-summary td, .deprecated-summary td, +.requires-summary td, .packages-summary td, .provides-summary td, +.uses-summary td, .system-properties-summary td { + text-align:left; + padding:0px 0px 12px 10px; +} +th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .use-summary th, +.constants-summary th, .packages-summary th, td.col-first, td.col-second, td.col-last, .use-summary td, +.constants-summary td, .system-properties-summary th { + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .constants-summary th, +.packages-summary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; +} +td.col-first, th.col-first { + font-size:13px; +} +td.col-second, th.col-second, td.col-last, th.col-constructor-name, th.col-deprecated-item-name, th.col-last { + font-size:13px; +} +.constants-summary th, .packages-summary th { + font-size:13px; +} +.provides-summary th.col-first, .provides-summary th.col-last, .provides-summary td.col-first, +.provides-summary td.col-last { + white-space:normal; + font-size:13px; +} +.overview-summary td.col-first, .overview-summary th.col-first, +.requires-summary td.col-first, .requires-summary th.col-first, +.packages-summary td.col-first, .packages-summary td.col-second, .packages-summary th.col-first, .packages-summary th, +.uses-summary td.col-first, .uses-summary th.col-first, +.provides-summary td.col-first, .provides-summary th.col-first, +.member-summary td.col-first, .member-summary th.col-first, +.member-summary td.col-second, .member-summary th.col-second, .member-summary th.col-constructor-name, +.type-summary td.col-first, .type-summary th.col-first { + vertical-align:top; +} +.packages-summary th.col-last, .packages-summary td.col-last { + white-space:normal; +} +td.col-first a:link, td.col-first a:visited, +td.col-second a:link, td.col-second a:visited, +th.col-first a:link, th.col-first a:visited, +th.col-second a:link, th.col-second a:visited, +th.col-constructor-name a:link, th.col-constructor-name a:visited, +th.col-deprecated-item-name a:link, th.col-deprecated-item-name a:visited, +.constant-values-container td a:link, .constant-values-container td a:visited, +.all-classes-container td a:link, .all-classes-container td a:visited, +.all-packages-container td a:link, .all-packages-container td a:visited { + font-weight:bold; +} +.table-sub-heading-color { + background-color:#EEEEFF; +} +.alt-color, .alt-color th { + background-color:#FFFFFF; +} +.row-color, .row-color th { + background-color:#EEEEEF; +} +/* + * Styles for contents. + */ +.description pre { + margin-top:0; +} +.deprecated-content { + margin:0; + padding:10px 0; +} +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} +td.col-last div { + padding-top:0px; +} +td.col-last a { + padding-bottom:3px; +} +div.member-signature { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + margin:14px 0; + white-space: pre-wrap; +} +div.member-signature span.annotations { + white-space: pre-wrap; +} +div.member-signature span.type-parameters-long, +div.member-signature span.parameters, +div.member-signature span.exceptions { + display: inline-block; + vertical-align: top; + white-space: pre; +} +div.member-signature span.type-parameters { + white-space: normal; +} +/* + * Styles for formatting effect. + */ +.source-line-no { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:10px; +} +.block { + display:block; + margin:0 10px 5px 0; + color:#474747; +} +.deprecated-label, .descfrm-type-label, .implementation-label, .member-name-label, .member-name-link, +.module-label-in-package, .module-label-in-type, .override-specify-label, .package-label-in-type, +.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link { + font-weight:bold; +} +.deprecation-comment, .help-footnote, .interface-name { + font-style:italic; +} +.deprecation-block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; + border-style:solid; + border-width:thin; + border-radius:10px; + padding:10px; + margin-bottom:10px; + margin-right:10px; + display:inline-block; +} +div.block div.deprecation-comment, div.block div.block span.emphasized-phrase, +div.block div.block span.interface-name { + font-style:normal; +} +/* + * Styles specific to HTML5 elements. + */ +main, nav, header, footer, section { + display:block; +} +/* + * Styles for javadoc search. + */ +.ui-autocomplete-category { + font-weight:bold; + font-size:15px; + padding:7px 0 7px 3px; + background-color:#4D7A97; + color:#FFFFFF; +} +.result-item { + font-size:13px; +} +.ui-autocomplete { + max-height:85%; + max-width:65%; + overflow-y:scroll; + overflow-x:scroll; + white-space:nowrap; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); +} +ul.ui-autocomplete { + position:fixed; + z-index:999999; +} +ul.ui-autocomplete li { + float:left; + clear:both; + width:100%; +} +.result-highlight { + font-weight:bold; +} +#search { + background-image:url('resources/glass.png'); + background-size:13px; + background-repeat:no-repeat; + background-position:2px 3px; + padding-left:20px; + position:relative; + right:-18px; + width:400px; +} +#reset { + background-color: rgb(255,255,255); + background-image:url('resources/x.png'); + background-position:center; + background-repeat:no-repeat; + background-size:12px; + border:0 none; + width:16px; + height:16px; + position:relative; + left:-4px; + top:-4px; + font-size:0px; +} +.watermark { + color:#545454; +} +.search-tag-desc-result { + font-style:italic; + font-size:11px; +} +.search-tag-holder-result { + font-style:italic; + font-size:12px; +} +.search-tag-result:target { + background-color:yellow; +} +.module-graph span { + display:none; + position:absolute; +} +.module-graph:hover span { + display:block; + margin: -100px 0 0 100px; + z-index: 1; +} +.inherited-list { + margin: 10px 0 10px 0; +} +section.description { + line-height: 1.4; +} +.summary section[class$="-summary"], .details section[class$="-details"], +.class-uses .detail, .serialized-class-details { + padding: 0px 20px 5px 10px; + border: 1px solid #ededed; + background-color: #f8f8f8; +} +.inherited-list, section[class$="-details"] .detail { + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; +} +.vertical-separator { + padding: 0 5px; +} +ul.help-section-list { + margin: 0; +} +/* + * Indicator icon for external links. + */ +main a[href*="://"]::after { + content:""; + display:inline-block; + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); + background-size:100% 100%; + width:7px; + height:7px; + margin-left:2px; + margin-bottom:4px; +} +main a[href*="://"]:hover::after, +main a[href*="://"]:focus::after { + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); +} + +/* + * Styles for user-provided tables. + * + * borderless: + * No borders, vertical margins, styled caption. + * This style is provided for use with existing doc comments. + * In general, borderless tables should not be used for layout purposes. + * + * plain: + * Plain borders around table and cells, vertical margins, styled caption. + * Best for small tables or for complex tables for tables with cells that span + * rows and columns, when the "striped" style does not work well. + * + * striped: + * Borders around the table and vertical borders between cells, striped rows, + * vertical margins, styled caption. + * Best for tables that have a header row, and a body containing a series of simple rows. + */ + +table.borderless, +table.plain, +table.striped { + margin-top: 10px; + margin-bottom: 10px; +} +table.borderless > caption, +table.plain > caption, +table.striped > caption { + font-weight: bold; + font-size: smaller; +} +table.borderless th, table.borderless td, +table.plain th, table.plain td, +table.striped th, table.striped td { + padding: 2px 5px; +} +table.borderless, +table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th, +table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td { + border: none; +} +table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr { + background-color: transparent; +} +table.plain { + border-collapse: collapse; + border: 1px solid black; +} +table.plain > thead > tr, table.plain > tbody tr, table.plain > tr { + background-color: transparent; +} +table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th, +table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td { + border: 1px solid black; +} +table.striped { + border-collapse: collapse; + border: 1px solid black; +} +table.striped > thead { + background-color: #E3E3E3; +} +table.striped > thead > tr > th, table.striped > thead > tr > td { + border: 1px solid black; +} +table.striped > tbody > tr:nth-child(even) { + background-color: #EEE +} +table.striped > tbody > tr:nth-child(odd) { + background-color: #FFF +} +table.striped > tbody > tr > th, table.striped > tbody > tr > td { + border-left: 1px solid black; + border-right: 1px solid black; +} +table.striped > tbody > tr > th { + font-weight: normal; +} diff --git a/io-vector/build/docs/javadoc/tag-search-index.js b/io-vector/build/docs/javadoc/tag-search-index.js new file mode 100644 index 0000000..0367dae --- /dev/null +++ b/io-vector/build/docs/javadoc/tag-search-index.js @@ -0,0 +1 @@ +tagSearchIndex = [];updateSearchResults(); \ No newline at end of file diff --git a/io-vector/build/docs/javadoc/type-search-index.js b/io-vector/build/docs/javadoc/type-search-index.js new file mode 100644 index 0000000..a9a7afd --- /dev/null +++ b/io-vector/build/docs/javadoc/type-search-index.js @@ -0,0 +1 @@ +typeSearchIndex = [{"p":"org.xbib.graphics.io.vector.filters","l":"AbsoluteToRelativeTransformsFilter"},{"p":"org.xbib.graphics.io.vector.commands","l":"AffineTransformCommand"},{"l":"All Classes","u":"allclasses-index.html"},{"p":"org.xbib.graphics.io.vector.util","l":"AlphaToMaskOp"},{"p":"org.xbib.graphics.io.vector.util","l":"ASCII85EncodeStream"},{"p":"org.xbib.graphics.io.vector.util","l":"Base64EncodeStream"},{"p":"org.xbib.graphics.io.vector","l":"Command"},{"p":"org.xbib.graphics.io.vector.commands","l":"CreateCommand"},{"p":"org.xbib.graphics.io.vector.util","l":"DataUtils"},{"p":"org.xbib.graphics.io.vector.commands","l":"DisposeCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"DrawImageCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"DrawShapeCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"DrawStringCommand"},{"p":"org.xbib.graphics.io.vector.eps","l":"EPSGraphics2D"},{"p":"org.xbib.graphics.io.vector.eps","l":"EPSProcessor"},{"p":"org.xbib.graphics.io.vector.eps","l":"EPSProcessorResult"},{"p":"org.xbib.graphics.io.vector.filters","l":"FillPaintedShapeAsImageFilter"},{"p":"org.xbib.graphics.io.vector.commands","l":"FillShapeCommand"},{"p":"org.xbib.graphics.io.vector.filters","l":"Filter"},{"p":"org.xbib.graphics.io.vector.util","l":"FlateEncodeStream"},{"p":"org.xbib.graphics.io.vector.util","l":"FormattingWriter"},{"p":"org.xbib.graphics.io.vector.pdf","l":"GeneratedPayload"},{"p":"org.xbib.graphics.io.vector","l":"GraphicsState"},{"p":"org.xbib.graphics.io.vector.util","l":"GraphicsUtils"},{"p":"org.xbib.graphics.io.vector.commands","l":"Group"},{"p":"org.xbib.graphics.io.vector.filters","l":"GroupingFilter"},{"p":"org.xbib.graphics.io.vector.util","l":"ImageDataStream"},{"p":"org.xbib.graphics.io.vector.util","l":"ImageDataStream.Interleaving"},{"p":"org.xbib.graphics.io.vector.util","l":"VectorHints.Key"},{"p":"org.xbib.graphics.io.vector.util","l":"LineWrapOutputStream"},{"p":"org.xbib.graphics.io.vector.filters","l":"OptimizeFilter"},{"p":"org.xbib.graphics.io.vector","l":"PageSize"},{"p":"org.xbib.graphics.io.vector.pdf","l":"Payload"},{"p":"org.xbib.graphics.io.vector.pdf","l":"PDFGraphics2D"},{"p":"org.xbib.graphics.io.vector.pdf","l":"PDFObject"},{"p":"org.xbib.graphics.io.vector.pdf","l":"PDFProcessor"},{"p":"org.xbib.graphics.io.vector.pdf","l":"PDFProcessorResult"},{"p":"org.xbib.graphics.io.vector","l":"Processor"},{"p":"org.xbib.graphics.io.vector","l":"ProcessorResult"},{"p":"org.xbib.graphics.io.vector.pdf","l":"Resources"},{"p":"org.xbib.graphics.io.vector.commands","l":"RotateCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"ScaleCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetBackgroundCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetClipCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetColorCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetCompositeCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetFontCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetHintCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetPaintCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetStrokeCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetTransformCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"SetXORModeCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"ShearCommand"},{"p":"org.xbib.graphics.io.vector.pdf","l":"SizePayload"},{"p":"org.xbib.graphics.io.vector.filters","l":"StateChangeGroupingFilter"},{"p":"org.xbib.graphics.io.vector.commands","l":"StateCommand"},{"p":"org.xbib.graphics.io.vector.svg","l":"SVGGraphics2D"},{"p":"org.xbib.graphics.io.vector.svg","l":"SVGProcessor"},{"p":"org.xbib.graphics.io.vector.svg","l":"SVGProcessorResult"},{"p":"org.xbib.graphics.io.vector.commands","l":"TransformCommand"},{"p":"org.xbib.graphics.io.vector.commands","l":"TranslateCommand"},{"p":"org.xbib.graphics.io.vector.util","l":"VectorHints.Value"},{"p":"org.xbib.graphics.io.vector","l":"VectorGraphics2D"},{"p":"org.xbib.graphics.io.vector","l":"VectorGraphicsFormat"},{"p":"org.xbib.graphics.io.vector.util","l":"VectorHints"}];updateSearchResults(); \ No newline at end of file diff --git a/io-vector/build/libs/io-vector-3.0.0-javadoc.jar b/io-vector/build/libs/io-vector-3.0.0-javadoc.jar new file mode 100644 index 0000000..25cd4f6 Binary files /dev/null and b/io-vector/build/libs/io-vector-3.0.0-javadoc.jar differ diff --git a/io-vector/build/libs/io-vector-3.0.0-sources.jar b/io-vector/build/libs/io-vector-3.0.0-sources.jar new file mode 100644 index 0000000..f1e11d7 Binary files /dev/null and b/io-vector/build/libs/io-vector-3.0.0-sources.jar differ diff --git a/io-vector/build/libs/io-vector-3.0.0.jar b/io-vector/build/libs/io-vector-3.0.0.jar new file mode 100644 index 0000000..ae846a1 Binary files /dev/null and b/io-vector/build/libs/io-vector-3.0.0.jar differ diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest.html new file mode 100644 index 0000000..f14f528 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest + + + + + +
      +

      Class org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.016s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testAbsoluteAndRelativeTransformsIdentical()0.001spassed
      testRelativeTransformAfterDispose()0spassed
      testTranslateCorrect()0.015spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest.html new file mode 100644 index 0000000..4e96fc9 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest.html @@ -0,0 +1,101 @@ + + + + + +Test results - Class org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest + + + + + +
      +

      Class org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      2
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      1.956s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testFillShapeNotReplacedWithoutPaintCommand()0.013spassed
      testFillShapeReplacedWithDrawImage()1.943spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.FilterTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.FilterTest.html new file mode 100644 index 0000000..390144f --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.FilterTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.filters.FilterTest + + + + + +
      +

      Class org.xbib.graphics.io.filters.FilterTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.004s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      duplicate()0.001spassed
      filterAll()0.001spassed
      filterNone()0.002spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.GroupingFilterTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.GroupingFilterTest.html new file mode 100644 index 0000000..1cf1308 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.filters.GroupingFilterTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class org.xbib.graphics.io.filters.GroupingFilterTest + + + + + +
      +

      Class org.xbib.graphics.io.filters.GroupingFilterTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      1
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.001s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + +
      TestDurationResult
      filtered()0.001spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.GraphicsStateTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.GraphicsStateTest.html new file mode 100644 index 0000000..899fc52 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.GraphicsStateTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.GraphicsStateTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.GraphicsStateTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.003s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testClone()0.002spassed
      testEquals()0.001spassed
      testInitialStateIsEqualToGraphics2D()0spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.TestUtilsTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.TestUtilsTest.html new file mode 100644 index 0000000..403186a --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.TestUtilsTest.html @@ -0,0 +1,131 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.TestUtilsTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.TestUtilsTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      8
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.005s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testParseXMLAttributesTag()0.001spassed
      testParseXmlCDATA()0spassed
      testParseXmlComment()0spassed
      testParseXmlDeclaration()0spassed
      testParseXmlDoctype()0.001spassed
      testParseXmlEmptyElement()0.001spassed
      testParseXmlEndTag()0.001spassed
      testParseXmlStartTag()0.001spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.VectorGraphics2DTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.VectorGraphics2DTest.html new file mode 100644 index 0000000..ebf146f --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.VectorGraphics2DTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.VectorGraphics2DTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.VectorGraphics2DTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.006s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testCreateEmitsCreateCommand()0spassed
      testDisposeCommandEmitted()0.001spassed
      testEmptyVectorGraphics2DStartsWithCreateCommand()0.005spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.eps.EPSProcessorTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.eps.EPSProcessorTest.html new file mode 100644 index 0000000..5f6a3f7 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.eps.EPSProcessorTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.eps.EPSProcessorTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.eps.EPSProcessorTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      1
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      3.546s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + +
      TestDurationResult
      envelopeForEmptyDocument()3.546spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.pdf.PDFProcessorTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.pdf.PDFProcessorTest.html new file mode 100644 index 0000000..1b4711a --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.pdf.PDFProcessorTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.pdf.PDFProcessorTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.pdf.PDFProcessorTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      1
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.312s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + +
      TestDurationResult
      envelopeForEmptyDocument()0.312spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.svg.SVGProcessorTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.svg.SVGProcessorTest.html new file mode 100644 index 0000000..f8513e6 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.svg.SVGProcessorTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.svg.SVGProcessorTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.svg.SVGProcessorTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.103s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      drawShapeBlack()0.004spassed
      envelopeForEmptyDocument()0.005spassed
      fillShapeBlack()0.094spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest.html new file mode 100644 index 0000000..781e7e4 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.002s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testEmpty()0.001spassed
      testEncoding()0.001spassed
      testPadding()0spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.Base64EncodeStreamTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.Base64EncodeStreamTest.html new file mode 100644 index 0000000..10f3724 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.Base64EncodeStreamTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.util.Base64EncodeStreamTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.util.Base64EncodeStreamTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.002s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testEmpty()0spassed
      testEncoding()0.001spassed
      testPadding()0.001spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.DataUtilsTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.DataUtilsTest.html new file mode 100644 index 0000000..55192d3 --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.DataUtilsTest.html @@ -0,0 +1,106 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.util.DataUtilsTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.util.DataUtilsTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.002s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      stripComplexSubstring()0.001spassed
      stripTrailingSpaces()0.001spassed
      stripTrailingSpacesInMultilineString()0spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.GraphicsUtilsTest.html b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.GraphicsUtilsTest.html new file mode 100644 index 0000000..39a7f0b --- /dev/null +++ b/io-vector/build/reports/tests/test/classes/org.xbib.graphics.io.vector.util.GraphicsUtilsTest.html @@ -0,0 +1,111 @@ + + + + + +Test results - Class org.xbib.graphics.io.vector.util.GraphicsUtilsTest + + + + + +
      +

      Class org.xbib.graphics.io.vector.util.GraphicsUtilsTest

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      4
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.397s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Tests

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TestDurationResult
      testCloneShape()0.003spassed
      testHasAlpha()0spassed
      testPhysicalFont()0.370spassed
      testToBufferedImage()0.024spassed
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/css/base-style.css b/io-vector/build/reports/tests/test/css/base-style.css new file mode 100644 index 0000000..4afa73e --- /dev/null +++ b/io-vector/build/reports/tests/test/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/io-vector/build/reports/tests/test/css/style.css b/io-vector/build/reports/tests/test/css/style.css new file mode 100644 index 0000000..3dc4913 --- /dev/null +++ b/io-vector/build/reports/tests/test/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/io-vector/build/reports/tests/test/index.html b/io-vector/build/reports/tests/test/index.html new file mode 100644 index 0000000..6b4ccca --- /dev/null +++ b/io-vector/build/reports/tests/test/index.html @@ -0,0 +1,313 @@ + + + + + +Test results - Test Summary + + + + + +
      +

      Test Summary

      +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      41
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      6.355s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Packages

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      PackageTestsFailuresIgnoredDurationSuccess rate
      +org.xbib.graphics.io.filters +9001.977s100%
      +org.xbib.graphics.io.vector +14000.014s100%
      +org.xbib.graphics.io.vector.eps +1003.546s100%
      +org.xbib.graphics.io.vector.pdf +1000.312s100%
      +org.xbib.graphics.io.vector.svg +3000.103s100%
      +org.xbib.graphics.io.vector.util +13000.403s100%
      +
      +
      +

      Classes

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ClassTestsFailuresIgnoredDurationSuccess rate
      +org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest +3000.016s100%
      +org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest +2001.956s100%
      +org.xbib.graphics.io.filters.FilterTest +3000.004s100%
      +org.xbib.graphics.io.filters.GroupingFilterTest +1000.001s100%
      +org.xbib.graphics.io.vector.GraphicsStateTest +3000.003s100%
      +org.xbib.graphics.io.vector.TestUtilsTest +8000.005s100%
      +org.xbib.graphics.io.vector.VectorGraphics2DTest +3000.006s100%
      +org.xbib.graphics.io.vector.eps.EPSProcessorTest +1003.546s100%
      +org.xbib.graphics.io.vector.pdf.PDFProcessorTest +1000.312s100%
      +org.xbib.graphics.io.vector.svg.SVGProcessorTest +3000.103s100%
      +org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest +3000.002s100%
      +org.xbib.graphics.io.vector.util.Base64EncodeStreamTest +3000.002s100%
      +org.xbib.graphics.io.vector.util.DataUtilsTest +3000.002s100%
      +org.xbib.graphics.io.vector.util.GraphicsUtilsTest +4000.397s100%
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/js/report.js b/io-vector/build/reports/tests/test/js/report.js new file mode 100644 index 0000000..83bab4a --- /dev/null +++ b/io-vector/build/reports/tests/test/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.filters.html b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.filters.html new file mode 100644 index 0000000..46414bc --- /dev/null +++ b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.filters.html @@ -0,0 +1,133 @@ + + + + + +Test results - Package org.xbib.graphics.io.filters + + + + + +
      +

      Package org.xbib.graphics.io.filters

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      9
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      1.977s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Classes

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ClassTestsFailuresIgnoredDurationSuccess rate
      +AbsoluteToRelativeTransformsFilterTest +3000.016s100%
      +FillPaintedShapeAsImageFilterTest +2001.956s100%
      +FilterTest +3000.004s100%
      +GroupingFilterTest +1000.001s100%
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.eps.html b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.eps.html new file mode 100644 index 0000000..ed2fa13 --- /dev/null +++ b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.eps.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package org.xbib.graphics.io.vector.eps + + + + + +
      +

      Package org.xbib.graphics.io.vector.eps

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      1
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      3.546s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Classes

      + + + + + + + + + + + + + + + + + + + +
      ClassTestsFailuresIgnoredDurationSuccess rate
      +EPSProcessorTest +1003.546s100%
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.html b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.html new file mode 100644 index 0000000..11db431 --- /dev/null +++ b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.html @@ -0,0 +1,123 @@ + + + + + +Test results - Package org.xbib.graphics.io.vector + + + + + +
      +

      Package org.xbib.graphics.io.vector

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      14
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.014s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Classes

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ClassTestsFailuresIgnoredDurationSuccess rate
      +GraphicsStateTest +3000.003s100%
      +TestUtilsTest +8000.005s100%
      +VectorGraphics2DTest +3000.006s100%
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.pdf.html b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.pdf.html new file mode 100644 index 0000000..4797191 --- /dev/null +++ b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.pdf.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package org.xbib.graphics.io.vector.pdf + + + + + +
      +

      Package org.xbib.graphics.io.vector.pdf

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      1
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.312s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Classes

      + + + + + + + + + + + + + + + + + + + +
      ClassTestsFailuresIgnoredDurationSuccess rate
      +PDFProcessorTest +1000.312s100%
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.svg.html b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.svg.html new file mode 100644 index 0000000..790872c --- /dev/null +++ b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.svg.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package org.xbib.graphics.io.vector.svg + + + + + +
      +

      Package org.xbib.graphics.io.vector.svg

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      3
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.103s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Classes

      + + + + + + + + + + + + + + + + + + + +
      ClassTestsFailuresIgnoredDurationSuccess rate
      +SVGProcessorTest +3000.103s100%
      +
      +
      + +
      + + diff --git a/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.util.html b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.util.html new file mode 100644 index 0000000..3c61dde --- /dev/null +++ b/io-vector/build/reports/tests/test/packages/org.xbib.graphics.io.vector.util.html @@ -0,0 +1,133 @@ + + + + + +Test results - Package org.xbib.graphics.io.vector.util + + + + + +
      +

      Package org.xbib.graphics.io.vector.util

      + +
      + + + + + +
      +
      + + + + + + + +
      +
      +
      13
      +

      tests

      +
      +
      +
      +
      0
      +

      failures

      +
      +
      +
      +
      0
      +

      ignored

      +
      +
      +
      +
      0.403s
      +

      duration

      +
      +
      +
      +
      +
      +
      100%
      +

      successful

      +
      +
      +
      +
      + +
      +

      Classes

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ClassTestsFailuresIgnoredDurationSuccess rate
      +ASCII85EncodeStreamTest +3000.002s100%
      +Base64EncodeStreamTest +3000.002s100%
      +DataUtilsTest +3000.002s100%
      +GraphicsUtilsTest +4000.397s100%
      +
      +
      + +
      + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest.xml new file mode 100644 index 0000000..7048c5c --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest.xml new file mode 100644 index 0000000..29d9978 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.FilterTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.FilterTest.xml new file mode 100644 index 0000000..fd9eb15 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.FilterTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.GroupingFilterTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.GroupingFilterTest.xml new file mode 100644 index 0000000..9ac5228 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.filters.GroupingFilterTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.GraphicsStateTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.GraphicsStateTest.xml new file mode 100644 index 0000000..3cfa68e --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.GraphicsStateTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.TestUtilsTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.TestUtilsTest.xml new file mode 100644 index 0000000..807a49b --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.TestUtilsTest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.VectorGraphics2DTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.VectorGraphics2DTest.xml new file mode 100644 index 0000000..0c0968b --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.VectorGraphics2DTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.eps.EPSProcessorTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.eps.EPSProcessorTest.xml new file mode 100644 index 0000000..63b28f7 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.eps.EPSProcessorTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.pdf.PDFProcessorTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.pdf.PDFProcessorTest.xml new file mode 100644 index 0000000..884ab97 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.pdf.PDFProcessorTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.svg.SVGProcessorTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.svg.SVGProcessorTest.xml new file mode 100644 index 0000000..d1295dd --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.svg.SVGProcessorTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest.xml new file mode 100644 index 0000000..b4fdfc9 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.Base64EncodeStreamTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.Base64EncodeStreamTest.xml new file mode 100644 index 0000000..6ef4051 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.Base64EncodeStreamTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.DataUtilsTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.DataUtilsTest.xml new file mode 100644 index 0000000..0f97322 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.DataUtilsTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.GraphicsUtilsTest.xml b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.GraphicsUtilsTest.xml new file mode 100644 index 0000000..9a5a447 --- /dev/null +++ b/io-vector/build/test-results/test/TEST-org.xbib.graphics.io.vector.util.GraphicsUtilsTest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/io-vector/build/test-results/test/binary/output.bin b/io-vector/build/test-results/test/binary/output.bin new file mode 100644 index 0000000..e69de29 diff --git a/io-vector/build/test-results/test/binary/output.bin.idx b/io-vector/build/test-results/test/binary/output.bin.idx new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/io-vector/build/test-results/test/binary/output.bin.idx differ diff --git a/io-vector/build/test-results/test/binary/results.bin b/io-vector/build/test-results/test/binary/results.bin new file mode 100644 index 0000000..214794e Binary files /dev/null and b/io-vector/build/test-results/test/binary/results.bin differ diff --git a/io-vector/build/tmp/compileJava/source-classes-mapping.txt b/io-vector/build/tmp/compileJava/source-classes-mapping.txt new file mode 100644 index 0000000..6dbfeb6 --- /dev/null +++ b/io-vector/build/tmp/compileJava/source-classes-mapping.txt @@ -0,0 +1,128 @@ +org/xbib/graphics/io/vector/commands/SetTransformCommand.java + org.xbib.graphics.io.vector.commands.SetTransformCommand +org/xbib/graphics/io/vector/commands/SetStrokeCommand.java + org.xbib.graphics.io.vector.commands.SetStrokeCommand +org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.java + org.xbib.graphics.io.vector.filters.StateChangeGroupingFilter +org/xbib/graphics/io/vector/commands/TransformCommand.java + org.xbib.graphics.io.vector.commands.TransformCommand +org/xbib/graphics/io/vector/PageSize.java + org.xbib.graphics.io.vector.PageSize +org/xbib/graphics/io/vector/util/AlphaToMaskOp.java + org.xbib.graphics.io.vector.util.AlphaToMaskOp +org/xbib/graphics/io/vector/commands/SetFontCommand.java + org.xbib.graphics.io.vector.commands.SetFontCommand +org/xbib/graphics/io/vector/commands/ShearCommand.java + org.xbib.graphics.io.vector.commands.ShearCommand +org/xbib/graphics/io/vector/commands/SetColorCommand.java + org.xbib.graphics.io.vector.commands.SetColorCommand +org/xbib/graphics/io/vector/commands/SetPaintCommand.java + org.xbib.graphics.io.vector.commands.SetPaintCommand +org/xbib/graphics/io/vector/util/FlateEncodeStream.java + org.xbib.graphics.io.vector.util.FlateEncodeStream +org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.java + org.xbib.graphics.io.vector.filters.AbsoluteToRelativeTransformsFilter +org/xbib/graphics/io/vector/commands/RotateCommand.java + org.xbib.graphics.io.vector.commands.RotateCommand +org/xbib/graphics/io/vector/pdf/GeneratedPayload.java + org.xbib.graphics.io.vector.pdf.GeneratedPayload +org/xbib/graphics/io/vector/commands/AffineTransformCommand.java + org.xbib.graphics.io.vector.commands.AffineTransformCommand +org/xbib/graphics/io/vector/ProcessorResult.java + org.xbib.graphics.io.vector.ProcessorResult +org/xbib/graphics/io/vector/util/GraphicsUtils.java + org.xbib.graphics.io.vector.util.GraphicsUtils + org.xbib.graphics.io.vector.util.GraphicsUtils$FontExpressivenessComparator +org/xbib/graphics/io/vector/util/VectorHints.java + org.xbib.graphics.io.vector.util.VectorHints + org.xbib.graphics.io.vector.util.VectorHints$Key + org.xbib.graphics.io.vector.util.VectorHints$Value +org/xbib/graphics/io/vector/Command.java + org.xbib.graphics.io.vector.Command +org/xbib/graphics/io/vector/commands/StateCommand.java + org.xbib.graphics.io.vector.commands.StateCommand +org/xbib/graphics/io/vector/GraphicsState.java + org.xbib.graphics.io.vector.GraphicsState +org/xbib/graphics/io/vector/util/ImageDataStream.java + org.xbib.graphics.io.vector.util.ImageDataStream + org.xbib.graphics.io.vector.util.ImageDataStream$Interleaving +org/xbib/graphics/io/vector/util/Base64EncodeStream.java + org.xbib.graphics.io.vector.util.Base64EncodeStream +org/xbib/graphics/io/vector/commands/ScaleCommand.java + org.xbib.graphics.io.vector.commands.ScaleCommand +org/xbib/graphics/io/vector/commands/FillShapeCommand.java + org.xbib.graphics.io.vector.commands.FillShapeCommand +org/xbib/graphics/io/vector/commands/SetCompositeCommand.java + org.xbib.graphics.io.vector.commands.SetCompositeCommand +org/xbib/graphics/io/vector/commands/CreateCommand.java + org.xbib.graphics.io.vector.commands.CreateCommand +module-info.java + module-info +org/xbib/graphics/io/vector/pdf/Resources.java + org.xbib.graphics.io.vector.pdf.Resources +org/xbib/graphics/io/vector/filters/Filter.java + org.xbib.graphics.io.vector.filters.Filter +org/xbib/graphics/io/vector/filters/GroupingFilter.java + org.xbib.graphics.io.vector.filters.GroupingFilter +org/xbib/graphics/io/vector/Processor.java + org.xbib.graphics.io.vector.Processor +org/xbib/graphics/io/vector/commands/SetBackgroundCommand.java + org.xbib.graphics.io.vector.commands.SetBackgroundCommand +org/xbib/graphics/io/vector/pdf/PDFObject.java + org.xbib.graphics.io.vector.pdf.PDFObject +org/xbib/graphics/io/vector/util/ASCII85EncodeStream.java + org.xbib.graphics.io.vector.util.ASCII85EncodeStream +org/xbib/graphics/io/vector/util/LineWrapOutputStream.java + org.xbib.graphics.io.vector.util.LineWrapOutputStream +org/xbib/graphics/io/vector/commands/SetHintCommand.java + org.xbib.graphics.io.vector.commands.SetHintCommand +org/xbib/graphics/io/vector/commands/TranslateCommand.java + org.xbib.graphics.io.vector.commands.TranslateCommand +org/xbib/graphics/io/vector/commands/Group.java + org.xbib.graphics.io.vector.commands.Group +org/xbib/graphics/io/vector/VectorGraphicsFormat.java + org.xbib.graphics.io.vector.VectorGraphicsFormat +org/xbib/graphics/io/vector/commands/DrawShapeCommand.java + org.xbib.graphics.io.vector.commands.DrawShapeCommand +org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.java + org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter +org/xbib/graphics/io/vector/commands/SetXORModeCommand.java + org.xbib.graphics.io.vector.commands.SetXORModeCommand +org/xbib/graphics/io/vector/commands/DrawImageCommand.java + org.xbib.graphics.io.vector.commands.DrawImageCommand +org/xbib/graphics/io/vector/filters/OptimizeFilter.java + org.xbib.graphics.io.vector.filters.OptimizeFilter +org/xbib/graphics/io/vector/commands/SetClipCommand.java + org.xbib.graphics.io.vector.commands.SetClipCommand +org/xbib/graphics/io/vector/pdf/Payload.java + org.xbib.graphics.io.vector.pdf.Payload +org/xbib/graphics/io/vector/commands/DrawStringCommand.java + org.xbib.graphics.io.vector.commands.DrawStringCommand +org/xbib/graphics/io/vector/util/FormattingWriter.java + org.xbib.graphics.io.vector.util.FormattingWriter +org/xbib/graphics/io/vector/pdf/SizePayload.java + org.xbib.graphics.io.vector.pdf.SizePayload +org/xbib/graphics/io/vector/eps/EPSProcessorResult.java + org.xbib.graphics.io.vector.eps.EPSProcessorResult +org/xbib/graphics/io/vector/commands/DisposeCommand.java + org.xbib.graphics.io.vector.commands.DisposeCommand +org/xbib/graphics/io/vector/VectorGraphics2D.java + org.xbib.graphics.io.vector.VectorGraphics2D +org/xbib/graphics/io/vector/svg/SVGGraphics2D.java + org.xbib.graphics.io.vector.svg.SVGGraphics2D +org/xbib/graphics/io/vector/pdf/PDFGraphics2D.java + org.xbib.graphics.io.vector.pdf.PDFGraphics2D +org/xbib/graphics/io/vector/pdf/PDFProcessor.java + org.xbib.graphics.io.vector.pdf.PDFProcessor +org/xbib/graphics/io/vector/pdf/PDFProcessorResult.java + org.xbib.graphics.io.vector.pdf.PDFProcessorResult +org/xbib/graphics/io/vector/util/DataUtils.java + org.xbib.graphics.io.vector.util.DataUtils +org/xbib/graphics/io/vector/svg/SVGProcessorResult.java + org.xbib.graphics.io.vector.svg.SVGProcessorResult +org/xbib/graphics/io/vector/eps/EPSGraphics2D.java + org.xbib.graphics.io.vector.eps.EPSGraphics2D +org/xbib/graphics/io/vector/eps/EPSProcessor.java + org.xbib.graphics.io.vector.eps.EPSProcessor +org/xbib/graphics/io/vector/svg/SVGProcessor.java + org.xbib.graphics.io.vector.svg.SVGProcessor diff --git a/io-vector/build/tmp/compileTestJava/source-classes-mapping.txt b/io-vector/build/tmp/compileTestJava/source-classes-mapping.txt new file mode 100644 index 0000000..a68b65b --- /dev/null +++ b/io-vector/build/tmp/compileTestJava/source-classes-mapping.txt @@ -0,0 +1,70 @@ +org/xbib/graphics/io/filters/GroupingFilterTest.java + org.xbib.graphics.io.filters.GroupingFilterTest + org.xbib.graphics.io.filters.GroupingFilterTest$1 +org/xbib/graphics/io/visual/PaintTest.java + org.xbib.graphics.io.visual.PaintTest +org/xbib/graphics/io/visual/FontTest.java + org.xbib.graphics.io.visual.FontTest +org/xbib/graphics/io/vector/util/Base64EncodeStreamTest.java + org.xbib.graphics.io.vector.util.Base64EncodeStreamTest +org/xbib/graphics/io/vector/util/DataUtilsTest.java + org.xbib.graphics.io.vector.util.DataUtilsTest +org/xbib/graphics/io/visual/StrokeTest.java + org.xbib.graphics.io.visual.StrokeTest +org/xbib/graphics/io/visual/TransformTest.java + org.xbib.graphics.io.visual.TransformTest +org/xbib/graphics/io/vector/TestUtils.java + org.xbib.graphics.io.vector.TestUtils + org.xbib.graphics.io.vector.TestUtils$Template + org.xbib.graphics.io.vector.TestUtils$XMLFragment + org.xbib.graphics.io.vector.TestUtils$XMLFragment$FragmentType +org/xbib/graphics/io/filters/FilterTest.java + org.xbib.graphics.io.filters.FilterTest + org.xbib.graphics.io.filters.FilterTest$1 + org.xbib.graphics.io.filters.FilterTest$2 + org.xbib.graphics.io.filters.FilterTest$3 +org/xbib/graphics/io/vector/TestUtilsTest.java + org.xbib.graphics.io.vector.TestUtilsTest +org/xbib/graphics/io/vector/GraphicsStateTest.java + org.xbib.graphics.io.vector.GraphicsStateTest +org/xbib/graphics/io/visual/ColorTest.java + org.xbib.graphics.io.visual.ColorTest +org/xbib/graphics/io/visual/ClippingTest.java + org.xbib.graphics.io.visual.ClippingTest +org/xbib/graphics/io/visual/EmptyFileTest.java + org.xbib.graphics.io.visual.EmptyFileTest +org/xbib/graphics/io/vector/util/ASCII85EncodeStreamTest.java + org.xbib.graphics.io.vector.util.ASCII85EncodeStreamTest +org/xbib/graphics/io/vector/util/GraphicsUtilsTest.java + org.xbib.graphics.io.vector.util.GraphicsUtilsTest + org.xbib.graphics.io.vector.util.GraphicsUtilsTest$1 +org/xbib/graphics/io/visual/ImageTest.java + org.xbib.graphics.io.visual.ImageTest +org/xbib/graphics/io/visual/SwingExportTest.java + org.xbib.graphics.io.visual.SwingExportTest +org/xbib/graphics/io/visual/ShapesTest.java + org.xbib.graphics.io.visual.ShapesTest +org/xbib/graphics/io/visual/TestBrowser.java + org.xbib.graphics.io.visual.TestBrowser + org.xbib.graphics.io.visual.TestBrowser$1 + org.xbib.graphics.io.visual.TestBrowser$2 + org.xbib.graphics.io.visual.TestBrowser$3 + org.xbib.graphics.io.visual.TestBrowser$ImageComparisonPanel + org.xbib.graphics.io.visual.TestBrowser$ImageDisplayPanel + org.xbib.graphics.io.visual.TestBrowser$ImageFormat +org/xbib/graphics/io/visual/CharacterTest.java + org.xbib.graphics.io.visual.CharacterTest +org/xbib/graphics/io/filters/FillPaintedShapeAsImageFilterTest.java + org.xbib.graphics.io.filters.FillPaintedShapeAsImageFilterTest +org/xbib/graphics/io/vector/pdf/PDFProcessorTest.java + org.xbib.graphics.io.vector.pdf.PDFProcessorTest +org/xbib/graphics/io/vector/VectorGraphics2DTest.java + org.xbib.graphics.io.vector.VectorGraphics2DTest +org/xbib/graphics/io/vector/eps/EPSProcessorTest.java + org.xbib.graphics.io.vector.eps.EPSProcessorTest +org/xbib/graphics/io/vector/svg/SVGProcessorTest.java + org.xbib.graphics.io.vector.svg.SVGProcessorTest +org/xbib/graphics/io/visual/AbstractTest.java + org.xbib.graphics.io.visual.AbstractTest +org/xbib/graphics/io/filters/AbsoluteToRelativeTransformsFilterTest.java + org.xbib.graphics.io.filters.AbsoluteToRelativeTransformsFilterTest diff --git a/io-vector/build/tmp/jar/MANIFEST.MF b/io-vector/build/tmp/jar/MANIFEST.MF new file mode 100644 index 0000000..0ea94c1 --- /dev/null +++ b/io-vector/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Implementation-Version: 3.0.0 + diff --git a/io-vector/build/tmp/javadoc/javadoc.options b/io-vector/build/tmp/javadoc/javadoc.options new file mode 100644 index 0000000..b5a992b --- /dev/null +++ b/io-vector/build/tmp/javadoc/javadoc.options @@ -0,0 +1,69 @@ +--module-path '/Users/joerg/Projects/github/jprante/graphics/io-vector/build/classes/java/main' +-Xdoclint:none '-quiet' +-d '/Users/joerg/Projects/github/jprante/graphics/io-vector/build/docs/javadoc' +-doctitle 'io-vector 3.0.0 API' +-notimestamp +-quiet +-windowtitle 'io-vector 3.0.0 API' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/module-info.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/Command.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/AffineTransformCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/CreateCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DisposeCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawImageCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawShapeCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawStringCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/FillShapeCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/Group.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/RotateCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ScaleCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetBackgroundCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetClipCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetColorCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetCompositeCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetFontCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetHintCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetPaintCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetStrokeCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetTransformCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetXORModeCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ShearCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/StateCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TransformCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TranslateCommand.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSGraphics2D.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessor.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessorResult.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/Filter.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/GroupingFilter.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/OptimizeFilter.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/GraphicsState.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/PageSize.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/GeneratedPayload.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Payload.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFGraphics2D.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFObject.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessor.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessorResult.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Resources.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/SizePayload.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/Processor.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/ProcessorResult.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGGraphics2D.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessor.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessorResult.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/AlphaToMaskOp.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ASCII85EncodeStream.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/Base64EncodeStream.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/DataUtils.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FlateEncodeStream.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FormattingWriter.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/GraphicsUtils.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ImageDataStream.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/LineWrapOutputStream.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/util/VectorHints.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphics2D.java' +'/Users/joerg/Projects/github/jprante/graphics/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphicsFormat.java' diff --git a/io-vector/build/tmp/javadocJar/MANIFEST.MF b/io-vector/build/tmp/javadocJar/MANIFEST.MF new file mode 100644 index 0000000..58630c0 --- /dev/null +++ b/io-vector/build/tmp/javadocJar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/io-vector/build/tmp/sourcesJar/MANIFEST.MF b/io-vector/build/tmp/sourcesJar/MANIFEST.MF new file mode 100644 index 0000000..58630c0 --- /dev/null +++ b/io-vector/build/tmp/sourcesJar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/io-vector/src/main/java/module-info.java b/io-vector/src/main/java/module-info.java new file mode 100644 index 0000000..d07255e --- /dev/null +++ b/io-vector/src/main/java/module-info.java @@ -0,0 +1,8 @@ +module org.xbib.graphics.io.vector { + exports org.xbib.graphics.io.vector; + exports org.xbib.graphics.io.vector.eps; + exports org.xbib.graphics.io.vector.pdf; + exports org.xbib.graphics.io.vector.svg; + + requires transitive java.desktop; +} \ No newline at end of file diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/Command.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/Command.java new file mode 100644 index 0000000..b452536 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/Command.java @@ -0,0 +1,29 @@ +package org.xbib.graphics.io.vector; + +public abstract class Command { + + private final T value; + + public Command(T value) { + this.value = value; + } + + public T getValue() { + return value; + } + + @Override + public String toString() { + return String.format("%s[value=%s]", getClass().getName(), getValue()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !getClass().equals(obj.getClass())) { + return false; + } + Command o = (Command) obj; + return value == o.value || value.equals(o.value); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/GraphicsState.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/GraphicsState.java new file mode 100644 index 0000000..c85ec8d --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/GraphicsState.java @@ -0,0 +1,274 @@ +package org.xbib.graphics.io.vector; + +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Rectangle2D; +import java.util.Objects; + +public class GraphicsState implements Cloneable { + /** + * Default background color. + */ + public static final Color DEFAULT_BACKGROUND = Color.BLACK; + /** + * Default color. + */ + public static final Color DEFAULT_COLOR = Color.WHITE; + /** + * Default clipping shape. + */ + public static final Shape DEFAULT_CLIP = null; + /** + * Default composite mode. + */ + public static final Composite DEFAULT_COMPOSITE = AlphaComposite.SrcOver; + /** + * Default font. + */ + public static final Font DEFAULT_FONT = Font.decode(null); + /** + * Default paint. + */ + public static final Color DEFAULT_PAINT = DEFAULT_COLOR; + /** + * Default stroke. + */ + public static final Stroke DEFAULT_STROKE = new BasicStroke(); + /** + * Default transformation. + */ + public static final AffineTransform DEFAULT_TRANSFORM = + new AffineTransform(); + /** + * Default XOR mode. + */ + public static final Color DEFAULT_XOR_MODE = Color.BLACK; + + /** + * Rendering hints. + */ + private RenderingHints hints; + /** + * Current background color. + */ + private Color background; + /** + * Current foreground color. + */ + private Color color; + /** + * Shape used for clipping paint operations. + */ + private Shape clip; + /** + * Method used for compositing. + */ + private Composite composite; + /** + * Current font. + */ + private Font font; + /** + * Paint used to fill shapes. + */ + private Paint paint; + /** + * Stroke used for drawing shapes. + */ + private Stroke stroke; + /** + * Current transformation matrix. + */ + private AffineTransform transform; + /** + * XOR mode used for rendering. + */ + private Color xorMode; + + public GraphicsState() { + hints = new RenderingHints(null); + background = DEFAULT_BACKGROUND; + color = DEFAULT_COLOR; + clip = DEFAULT_CLIP; + composite = DEFAULT_COMPOSITE; + font = DEFAULT_FONT; + paint = DEFAULT_PAINT; + stroke = DEFAULT_STROKE; + transform = new AffineTransform(DEFAULT_TRANSFORM); + xorMode = DEFAULT_XOR_MODE; + } + + private static Shape transformShape(Shape s, AffineTransform tx) { + if (s == null) { + return null; + } + if (tx == null || tx.isIdentity()) { + return GraphicsUtils.clone(s); + } + boolean isRectangle = s instanceof Rectangle2D; + int nonRectlinearTxMask = AffineTransform.TYPE_GENERAL_TRANSFORM | + AffineTransform.TYPE_GENERAL_ROTATION; + boolean isRectlinearTx = (tx.getType() & nonRectlinearTxMask) == 0; + if (isRectangle && isRectlinearTx) { + Rectangle2D rect = (Rectangle2D) s; + double[] corners = new double[]{ + rect.getMinX(), rect.getMinY(), + rect.getMaxX(), rect.getMaxY() + }; + tx.transform(corners, 0, corners, 0, 2); + rect = new Rectangle2D.Double(); + rect.setFrameFromDiagonal(corners[0], corners[1], corners[2], + corners[3]); + return rect; + } + return tx.createTransformedShape(s); + } + + private static Shape untransformShape(Shape s, AffineTransform tx) { + if (s == null) { + return null; + } + try { + AffineTransform inverse = tx.createInverse(); + return transformShape(s, inverse); + } catch (NoninvertibleTransformException e) { + return null; + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + GraphicsState clone = (GraphicsState) super.clone(); + clone.hints = (RenderingHints) hints.clone(); + clone.clip = GraphicsUtils.clone(clip); + clone.transform = new AffineTransform(transform); + return clone; + } + + public Shape transformShape(Shape shape) { + return transformShape(shape, transform); + } + + public Shape untransformShape(Shape shape) { + return untransformShape(shape, transform); + } + + public RenderingHints getHints() { + return hints; + } + + public Color getBackground() { + return background; + } + + public void setBackground(Color background) { + this.background = background; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public Shape getClip() { + return untransformShape(clip); + } + + public void setClip(Shape clip) { + this.clip = transformShape(clip); + } + + public Composite getComposite() { + return composite; + } + + public void setComposite(Composite composite) { + this.composite = composite; + } + + public Font getFont() { + return font; + } + + public void setFont(Font font) { + this.font = font; + } + + public Paint getPaint() { + return paint; + } + + public void setPaint(Paint paint) { + this.paint = paint; + } + + public Stroke getStroke() { + return stroke; + } + + public void setStroke(Stroke stroke) { + this.stroke = stroke; + } + + public AffineTransform getTransform() { + return new AffineTransform(transform); + } + + public void setTransform(AffineTransform tx) { + transform.setTransform(tx); + } + + public Color getXorMode() { + return xorMode; + } + + public void setXorMode(Color xorMode) { + this.xorMode = xorMode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GraphicsState)) { + return false; + } + GraphicsState o = (GraphicsState) obj; + return !(!hints.equals(o.hints) || !background.equals(o.background) || + !color.equals(o.color) || !composite.equals(o.composite) || + !font.equals(o.font) || !paint.equals(o.paint) || + !stroke.equals(o.stroke) || !transform.equals(o.transform) || + !xorMode.equals(o.xorMode) || + ((clip == null || o.clip == null) && clip != o.clip) || + (clip != null && !clip.equals(o.clip))); + } + + @Override + public int hashCode() { + return Objects.hash(hints, background, color, composite, font, paint, + stroke, transform, xorMode, clip); + } + + public boolean isDefault() { + return hints.isEmpty() && background.equals(DEFAULT_BACKGROUND) && + color.equals(DEFAULT_COLOR) && composite.equals(DEFAULT_COMPOSITE) && + font.equals(DEFAULT_FONT) && paint.equals(DEFAULT_PAINT) && + stroke.equals(DEFAULT_STROKE) && transform.equals(DEFAULT_TRANSFORM) && + xorMode.equals(DEFAULT_XOR_MODE) && clip == DEFAULT_CLIP; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/PageSize.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/PageSize.java new file mode 100644 index 0000000..8e452fa --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/PageSize.java @@ -0,0 +1,75 @@ +package org.xbib.graphics.io.vector; + +import java.awt.geom.Rectangle2D; + +public class PageSize { + + private static final double MM_PER_INCH = 2.54; + + public static final PageSize TABLOID = new PageSize(11.0 * MM_PER_INCH, 17.0 * MM_PER_INCH); + + public static final PageSize LETTER = new PageSize(8.5 * MM_PER_INCH, 11.0 * MM_PER_INCH); + + public static final PageSize LEGAL = new PageSize(8.5 * MM_PER_INCH, 14.0 * MM_PER_INCH); + + public static final PageSize LEDGER = TABLOID.getLandscape(); + + public static final PageSize A3 = new PageSize(297.0, 420.0); + + public static final PageSize A4 = new PageSize(210.0, 297.0); + + public static final PageSize A5 = new PageSize(148.0, 210.0); + + private final double x; + + private final double y; + + private final double width; + + private final double height; + + public PageSize(double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public PageSize(double width, double height) { + this(0.0, 0.0, width, height); + } + + public PageSize(Rectangle2D size) { + this(size.getX(), size.getY(), size.getWidth(), size.getHeight()); + } + + public PageSize getPortrait() { + if (width <= height) { + return this; + } + return new PageSize(x, y, height, width); + } + + public PageSize getLandscape() { + if (width >= height) { + return this; + } + return new PageSize(x, y, height, width); + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getHeight() { + return height; + } + + public double getWidth() { + return width; + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/Processor.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/Processor.java new file mode 100644 index 0000000..3496bb8 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/Processor.java @@ -0,0 +1,8 @@ +package org.xbib.graphics.io.vector; + +import java.io.IOException; + +public interface Processor { + + ProcessorResult process(Iterable> commands, PageSize pageSize) throws IOException; +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/ProcessorResult.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/ProcessorResult.java new file mode 100644 index 0000000..cbee656 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/ProcessorResult.java @@ -0,0 +1,13 @@ +package org.xbib.graphics.io.vector; + +import java.io.IOException; +import java.io.OutputStream; + +public interface ProcessorResult { + + void handle(Command command) throws IOException; + + void write(OutputStream out) throws IOException; + + void close() throws IOException; +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphics2D.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphics2D.java new file mode 100644 index 0000000..63751bf --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphics2D.java @@ -0,0 +1,896 @@ +package org.xbib.graphics.io.vector; + +import org.xbib.graphics.io.vector.commands.CreateCommand; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import org.xbib.graphics.io.vector.commands.DrawImageCommand; +import org.xbib.graphics.io.vector.commands.DrawShapeCommand; +import org.xbib.graphics.io.vector.commands.DrawStringCommand; +import org.xbib.graphics.io.vector.commands.FillShapeCommand; +import org.xbib.graphics.io.vector.commands.RotateCommand; +import org.xbib.graphics.io.vector.commands.ScaleCommand; +import org.xbib.graphics.io.vector.commands.SetBackgroundCommand; +import org.xbib.graphics.io.vector.commands.SetClipCommand; +import org.xbib.graphics.io.vector.commands.SetColorCommand; +import org.xbib.graphics.io.vector.commands.SetCompositeCommand; +import org.xbib.graphics.io.vector.commands.SetFontCommand; +import org.xbib.graphics.io.vector.commands.SetHintCommand; +import org.xbib.graphics.io.vector.commands.SetPaintCommand; +import org.xbib.graphics.io.vector.commands.SetStrokeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.commands.SetXORModeCommand; +import org.xbib.graphics.io.vector.commands.ShearCommand; +import org.xbib.graphics.io.vector.commands.TransformCommand; +import org.xbib.graphics.io.vector.commands.TranslateCommand; +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Paint; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.RenderingHints.Key; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.RenderableImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.AttributedCharacterIterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Base for classes that want to implement vector export. + */ +public class VectorGraphics2D extends Graphics2D implements Cloneable { + + private final Processor processor; + + private final PageSize pageSize; + /** + * List of operations that were performed on this graphics object and its + * derived objects. + */ + private final List> commands; + + private final GraphicsConfiguration deviceConfig; + + /** + * Context settings used to render fonts. + */ + private final FontRenderContext fontRenderContext; + /** + * Flag that tells whether this graphics object has been disposed. + */ + private boolean disposed; + + private GraphicsState state; + + private Graphics2D debugValidateGraphics; + + public VectorGraphics2D() { + this(null, null); + } + + public VectorGraphics2D(Processor processor, PageSize pageSize) { + this.processor = processor; + this.pageSize = pageSize; + this.commands = new LinkedList<>(); + emit(new CreateCommand(this)); + GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + if (!graphicsEnvironment.isHeadlessInstance()) { + GraphicsDevice graphicsDevice = graphicsEnvironment.getDefaultScreenDevice(); + deviceConfig = graphicsDevice.getDefaultConfiguration(); + } else { + deviceConfig = null; + } + fontRenderContext = new FontRenderContext(null, false, true); + state = new GraphicsState(); + BufferedImage _debug_validate_bimg = new BufferedImage(200, 250, BufferedImage.TYPE_INT_ARGB); + debugValidateGraphics = (Graphics2D) _debug_validate_bimg.getGraphics(); + debugValidateGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + + public PageSize getPageSize() { + return pageSize; + } + + public byte[] getBytes() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (out) { + writeTo(out); + } + return out.toByteArray(); + } + + public void writeTo(OutputStream out) throws IOException { + processor.process(getCommands(), pageSize).write(out); + } + + private static Shape intersectShapes(Shape s1, Shape s2) { + if (s1 instanceof Rectangle2D && s2 instanceof Rectangle2D) { + Rectangle2D r1 = (Rectangle2D) s1; + Rectangle2D r2 = (Rectangle2D) s2; + double x1 = Math.max(r1.getMinX(), r2.getMinX()); + double y1 = Math.max(r1.getMinY(), r2.getMinY()); + double x2 = Math.min(r1.getMaxX(), r2.getMaxX()); + double y2 = Math.min(r1.getMaxY(), r2.getMaxY()); + Rectangle2D intersection = new Rectangle2D.Double(); + if ((x2 < x1) || (y2 < y1)) { + intersection.setFrameFromDiagonal(0, 0, 0, 0); + } else { + intersection.setFrameFromDiagonal(x1, y1, x2, y2); + } + return intersection; + } else { + Area intersection = new Area(s1); + intersection.intersect(new Area(s2)); + return intersection; + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + VectorGraphics2D clone = (VectorGraphics2D) super.clone(); + clone.state = (GraphicsState) state.clone(); + return clone; + } + + @Override + public void addRenderingHints(Map hints) { + if (isDisposed()) { + return; + } + for (Entry entry : hints.entrySet()) { + setRenderingHint((Key) entry.getKey(), entry.getValue()); + } + } + + @Override + public void clip(Shape s) { + debugValidateGraphics.clip(s); + Shape clipOld = getClip(); + Shape clip = getClip(); + if ((clip != null) && (s != null)) { + s = intersectShapes(clip, s); + } + setClip(s); + Shape clipNew = getClip(); + if ((clipNew == null || debugValidateGraphics.getClip() == null) && clipNew != debugValidateGraphics.getClip()) { + throw new IllegalStateException("clip() validation failed: clip(" + clipOld + ", " + s + ") => " + clipNew + " != " + debugValidateGraphics.getClip()); + } + if (clipNew != null && !GraphicsUtils.equals(clipNew, debugValidateGraphics.getClip())) { + throw new IllegalStateException("clip() validation failed: clip(" + clipOld + ", " + s + ") => " + clipNew + " != " + debugValidateGraphics.getClip()); + } + } + + @Override + public void draw(Shape s) { + if (isDisposed() || s == null) { + return; + } + emit(new DrawShapeCommand(s)); + debugValidateGraphics.draw(s); + } + + @Override + public void drawGlyphVector(GlyphVector g, float x, float y) { + Shape s = g.getOutline(x, y); + draw(s); + } + + @Override + public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { + BufferedImage bimg = getTransformedImage(img, xform); + return drawImage(bimg, bimg.getMinX(), bimg.getMinY(), + bimg.getWidth(), bimg.getHeight(), null, null); + } + + /** + * Returns a transformed version of an image. + * + * @param image Image to be transformed + * @param xform Affine transform to be applied + * @return Image with transformed content + */ + private BufferedImage getTransformedImage(Image image, + AffineTransform xform) { + Integer interpolationType = + (Integer) getRenderingHint(RenderingHints.KEY_INTERPOLATION); + if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR + .equals(interpolationType)) { + interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; + } else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR + .equals(interpolationType)) { + interpolationType = AffineTransformOp.TYPE_BILINEAR; + } else { + interpolationType = AffineTransformOp.TYPE_BICUBIC; + } + AffineTransformOp op = new AffineTransformOp(xform, interpolationType); + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + return op.filter(bufferedImage, null); + } + + @Override + public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { + if (op != null) { + img = op.filter(img, null); + } + drawImage(img, x, y, img.getWidth(), img.getHeight(), null, null); + } + + @Override + public void drawRenderableImage(RenderableImage img, AffineTransform xform) { + drawRenderedImage(img.createDefaultRendering(), xform); + } + + @Override + public void drawRenderedImage(RenderedImage img, AffineTransform xform) { + BufferedImage bimg = GraphicsUtils.toBufferedImage(img); + drawImage(bimg, xform, null); + } + + @Override + public void drawString(String str, int x, int y) { + drawString(str, (float) x, (float) y); + } + + @Override + public void drawString(String str, float x, float y) { + if (isDisposed() || str == null || str.trim().length() == 0) { + return; + } + boolean isTextAsVectors = false; + if (isTextAsVectors) { + TextLayout layout = new TextLayout(str, getFont(), + getFontRenderContext()); + Shape s = layout.getOutline( + AffineTransform.getTranslateInstance(x, y)); + fill(s); + } else { + emit(new DrawStringCommand(str, x, y)); + debugValidateGraphics.drawString(str, x, y); + } + + } + + @Override + public void drawString(AttributedCharacterIterator iterator, int x, int y) { + drawString(iterator, (float) x, (float) y); + } + + @Override + public void drawString(AttributedCharacterIterator iterator, float x, float y) { + StringBuilder buf = new StringBuilder(); + for (char c = iterator.first(); c != AttributedCharacterIterator.DONE; + c = iterator.next()) { + buf.append(c); + } + drawString(buf.toString(), x, y); + } + + @Override + public void fill(Shape s) { + if (isDisposed() || s == null) { + return; + } + emit(new FillShapeCommand(s)); + debugValidateGraphics.fill(s); + } + + @Override + public Color getBackground() { + return state.getBackground(); + } + + @Override + public void setBackground(Color color) { + if (isDisposed() || color == null || getColor().equals(color)) { + return; + } + emit(new SetBackgroundCommand(color)); + state.setBackground(color); + debugValidateGraphics.setBackground(color); + if (!getBackground().equals(debugValidateGraphics.getBackground())) { + throw new IllegalStateException("setBackground() validation failed"); + } + } + + @Override + public Composite getComposite() { + return state.getComposite(); + } + + @Override + public void setComposite(Composite comp) { + if (isDisposed()) { + return; + } + if (comp == null) { + throw new IllegalArgumentException("Cannot set a null composite"); + } + emit(new SetCompositeCommand(comp)); + state.setComposite(comp); + debugValidateGraphics.setComposite(comp); + if (!getComposite().equals(debugValidateGraphics.getComposite())) { + throw new IllegalStateException("setComposite() validation failed"); + } + } + + @Override + public GraphicsConfiguration getDeviceConfiguration() { + return deviceConfig; + } + + @Override + public FontRenderContext getFontRenderContext() { + return fontRenderContext; + } + + @Override + public Paint getPaint() { + return state.getPaint(); + } + + @Override + public void setPaint(Paint paint) { + if (isDisposed() || paint == null) { + return; + } + if (paint instanceof Color) { + setColor((Color) paint); + return; + } + if (getPaint().equals(paint)) { + return; + } + emit(new SetPaintCommand(paint)); + state.setPaint(paint); + debugValidateGraphics.setPaint(paint); + if (!getPaint().equals(debugValidateGraphics.getPaint())) { + throw new IllegalStateException("setPaint() validation failed"); + } + } + + @Override + public Object getRenderingHint(Key hintKey) { + if (RenderingHints.KEY_ANTIALIASING.equals(hintKey)) { + return RenderingHints.VALUE_ANTIALIAS_OFF; + } else if (RenderingHints.KEY_TEXT_ANTIALIASING.equals(hintKey)) { + return RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; + } else if (RenderingHints.KEY_FRACTIONALMETRICS.equals(hintKey)) { + return RenderingHints.VALUE_FRACTIONALMETRICS_ON; + } + return state.getHints().get(hintKey); + } + + @Override + public RenderingHints getRenderingHints() { + return (RenderingHints) state.getHints().clone(); + } + + @Override + public void setRenderingHints(Map hints) { + if (isDisposed()) { + return; + } + state.getHints().clear(); + for (Entry hint : hints.entrySet()) { + setRenderingHint((Key) hint.getKey(), hint.getValue()); + } + } + + @Override + public Stroke getStroke() { + return state.getStroke(); + } + + @Override + public void setStroke(Stroke s) { + if (isDisposed()) { + return; + } + if (s == null) { + throw new IllegalArgumentException("Cannot set a null stroke."); + } + emit(new SetStrokeCommand(s)); + state.setStroke(s); + + debugValidateGraphics.setStroke(s); + if (!getStroke().equals(debugValidateGraphics.getStroke())) { + throw new IllegalStateException("setStroke() validation failed"); + } + } + + @Override + public boolean hit(Rectangle rect, Shape s, boolean onStroke) { + Shape hitShape = s; + if (onStroke) { + hitShape = getStroke().createStrokedShape(hitShape); + } + hitShape = state.transformShape(hitShape); + boolean hit = hitShape.intersects(rect); + + boolean _debug_hit = debugValidateGraphics.hit(rect, s, onStroke); + if (hit != _debug_hit) { + throw new IllegalStateException("setClip() validation failed"); + } + + return hit; + } + + @Override + public void setRenderingHint(Key hintKey, Object hintValue) { + if (isDisposed()) { + return; + } + state.getHints().put(hintKey, hintValue); + emit(new SetHintCommand(hintKey, hintValue)); + } + + @Override + public AffineTransform getTransform() { + return new AffineTransform(state.getTransform()); + } + + @Override + public void setTransform(AffineTransform tx) { + if (isDisposed() || tx == null || state.getTransform().equals(tx)) { + return; + } + emit(new SetTransformCommand(tx)); + state.setTransform(tx); + + debugValidateGraphics.setTransform(tx); + if (!getTransform().equals(debugValidateGraphics.getTransform())) { + throw new IllegalStateException("setTransform() validation failed"); + } + } + + @Override + public void shear(double shx, double shy) { + if (shx == 0.0 && shy == 0.0) { + return; + } + AffineTransform txNew = getTransform(); + txNew.shear(shx, shy); + emit(new ShearCommand(shx, shy)); + state.setTransform(txNew); + + debugValidateGraphics.shear(shx, shy); + if (!getTransform().equals(debugValidateGraphics.getTransform())) { + throw new IllegalStateException("shear() validation failed"); + } + } + + @Override + public void transform(AffineTransform tx) { + if (tx.isIdentity()) { + return; + } + AffineTransform txNew = getTransform(); + txNew.concatenate(tx); + emit(new TransformCommand(tx)); + state.setTransform(txNew); + + debugValidateGraphics.transform(tx); + if (!getTransform().equals(debugValidateGraphics.getTransform())) { + throw new IllegalStateException("transform() validation failed"); + } + } + + @Override + public void translate(int x, int y) { + translate((double) x, (double) y); + } + + @Override + public void translate(double tx, double ty) { + if (tx == 0.0 && ty == 0.0) { + return; + } + AffineTransform txNew = getTransform(); + txNew.translate(tx, ty); + emit(new TranslateCommand(tx, ty)); + state.setTransform(txNew); + debugValidateGraphics.translate(tx, ty); + if (!getTransform().equals(debugValidateGraphics.getTransform())) { + throw new IllegalStateException("translate() validation failed"); + } + } + + @Override + public void rotate(double theta) { + rotate(theta, 0.0, 0.0); + } + + @Override + public void rotate(double theta, double x, double y) { + if (theta == 0.0) { + return; + } + AffineTransform txNew = getTransform(); + if (x == 0.0 && y == 0.0) { + txNew.rotate(theta); + } else { + txNew.rotate(theta, x, y); + } + emit(new RotateCommand(theta, x, y)); + state.setTransform(txNew); + if (x == 0.0 && y == 0.0) { + debugValidateGraphics.rotate(theta); + if (!getTransform().equals(debugValidateGraphics.getTransform())) { + throw new IllegalStateException("rotate(theta) validation failed"); + } + } else { + debugValidateGraphics.rotate(theta, x, y); + if (!getTransform().equals(debugValidateGraphics.getTransform())) { + throw new IllegalStateException("rotate(theta,x,y) validation failed"); + } + } + } + + @Override + public void scale(double sx, double sy) { + if (sx == 1.0 && sy == 1.0) { + return; + } + AffineTransform txNew = getTransform(); + txNew.scale(sx, sy); + emit(new ScaleCommand(sx, sy)); + state.setTransform(txNew); + debugValidateGraphics.scale(sx, sy); + if (!getTransform().equals(debugValidateGraphics.getTransform())) { + throw new IllegalStateException("scale() validation failed"); + } + } + + @Override + public void clearRect(int x, int y, int width, int height) { + Color colorOld = getColor(); + setColor(getBackground()); + fillRect(x, y, width, height); + setColor(colorOld); + } + + @Override + public void clipRect(int x, int y, int width, int height) { + clip(new Rectangle(x, y, width, height)); + } + + @Override + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + // TODO Implement + //throw new UnsupportedOperationException("copyArea() isn't supported by VectorGraphics2D."); + } + + @Override + public Graphics create() { + if (isDisposed()) { + return null; + } + VectorGraphics2D clone = null; + try { + clone = (VectorGraphics2D) this.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + emit(new CreateCommand(clone)); + clone.debugValidateGraphics = (Graphics2D) debugValidateGraphics.create(); + return clone; + } + + @Override + public void dispose() { + if (isDisposed()) { + return; + } + emit(new DisposeCommand(this)); + disposed = true; + debugValidateGraphics.dispose(); + } + + @Override + public void drawArc(int x, int y, int width, int height, int startAngle, + int arcAngle) { + draw(new Arc2D.Double(x, y, width, height, + startAngle, arcAngle, Arc2D.OPEN)); + } + + @Override + public boolean drawImage(Image img, int x, int y, ImageObserver observer) { + return drawImage(img, x, y, img.getWidth(observer), + img.getHeight(observer), null, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, Color bgcolor, + ImageObserver observer) { + return drawImage(img, x, y, img.getWidth(observer), + img.getHeight(observer), bgcolor, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, + ImageObserver observer) { + return drawImage(img, x, y, width, height, null, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, + Color bgcolor, ImageObserver observer) { + if (isDisposed() || img == null) { + return true; + } + + int imageWidth = img.getWidth(observer); + int imageHeight = img.getHeight(observer); + Rectangle bounds = new Rectangle(x, y, width, height); + + if (bgcolor != null) { + // Fill rectangle with bgcolor + Color bgcolorOld = getColor(); + setColor(bgcolor); + fill(bounds); + setColor(bgcolorOld); + } + + emit(new DrawImageCommand(img, imageWidth, imageHeight, x, y, width, height)); + + debugValidateGraphics.drawImage(img, x, y, width, height, bgcolor, observer); + + return true; + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { + return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, + observer); + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, Color bgcolor, + ImageObserver observer) { + if (img == null) { + return true; + } + + int sx = Math.min(sx1, sx2); + int sy = Math.min(sy1, sy2); + int sw = Math.abs(sx2 - sx1); + int sh = Math.abs(sy2 - sy1); + int dx = Math.min(dx1, dx2); + int dy = Math.min(dy1, dy2); + int dw = Math.abs(dx2 - dx1); + int dh = Math.abs(dy2 - dy1); + + // Draw image on rectangle + BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img); + Image cropped = bufferedImg.getSubimage(sx, sy, sw, sh); + return drawImage(cropped, dx, dy, dw, dh, bgcolor, observer); + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + draw(new Line2D.Double(x1, y1, x2, y2)); + } + + @Override + public void drawOval(int x, int y, int width, int height) { + draw(new Ellipse2D.Double(x, y, width, height)); + } + + @Override + public void drawPolygon(Polygon p) { + draw(p); + } + + @Override + public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { + draw(new Polygon(xPoints, yPoints, nPoints)); + } + + @Override + public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { + Path2D p = new Path2D.Float(); + for (int i = 0; i < nPoints; i++) { + if (i > 0) { + p.lineTo(xPoints[i], yPoints[i]); + } else { + p.moveTo(xPoints[i], yPoints[i]); + } + } + draw(p); + } + + @Override + public void drawRect(int x, int y, int width, int height) { + draw(new Rectangle(x, y, width, height)); + } + + @Override + public void drawRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) { + draw(new RoundRectangle2D.Double(x, y, width, height, + arcWidth, arcHeight)); + } + + @Override + public void fillArc(int x, int y, int width, int height, + int startAngle, int arcAngle) { + fill(new Arc2D.Double(x, y, width, height, + startAngle, arcAngle, Arc2D.PIE)); + } + + @Override + public void fillOval(int x, int y, int width, int height) { + fill(new Ellipse2D.Double(x, y, width, height)); + } + + @Override + public void fillPolygon(Polygon p) { + fill(p); + } + + @Override + public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { + fill(new Polygon(xPoints, yPoints, nPoints)); + } + + @Override + public void fillRect(int x, int y, int width, int height) { + fill(new Rectangle(x, y, width, height)); + } + + @Override + public void fillRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) { + fill(new RoundRectangle2D.Double(x, y, width, height, + arcWidth, arcHeight)); + } + + @Override + public Shape getClip() { + return state.getClip(); + } + + @Override + public void setClip(Shape clip) { + if (isDisposed()) { + return; + } + emit(new SetClipCommand(clip)); + state.setClip(clip); + + debugValidateGraphics.setClip(clip); + if (getClip() == null) { + if (debugValidateGraphics.getClip() != null) { + throw new IllegalStateException("setClip() validation failed: clip=null, validation=" + + debugValidateGraphics.getClip()); + } + } else if (!GraphicsUtils.equals(getClip(), debugValidateGraphics.getClip())) { + throw new IllegalStateException("setClip() validation failed: clip=" + getClip() + ", validation=" + + debugValidateGraphics.getClip()); + } + } + + @Override + public Rectangle getClipBounds() { + if (getClip() == null) { + return null; + } + return getClip().getBounds(); + } + + @Override + public Color getColor() { + return state.getColor(); + } + + @Override + public void setColor(Color c) { + if (isDisposed() || c == null || getColor().equals(c)) { + return; + } + emit(new SetColorCommand(c)); + state.setColor(c); + state.setPaint(c); + debugValidateGraphics.setColor(c); + if (!getColor().equals(debugValidateGraphics.getColor())) { + throw new IllegalStateException("setColor() validation failed"); + } + } + + @Override + public Font getFont() { + return state.getFont(); + } + + @Override + public void setFont(Font font) { + if (isDisposed() || (font != null && getFont().equals(font))) { + return; + } + emit(new SetFontCommand(font)); + state.setFont(font); + debugValidateGraphics.setFont(font); + if (!getFont().equals(debugValidateGraphics.getFont())) { + throw new IllegalStateException("setFont() validation failed"); + } + } + + @Override + public FontMetrics getFontMetrics(Font f) { + BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); + Graphics g = bi.getGraphics(); + FontMetrics fontMetrics = g.getFontMetrics(getFont()); + g.dispose(); + return fontMetrics; + } + + @Override + public void setClip(int x, int y, int width, int height) { + setClip(new Rectangle(x, y, width, height)); + } + + @Override + public void setPaintMode() { + setComposite(AlphaComposite.SrcOver); + debugValidateGraphics.setPaintMode(); + } + + public Color getXORMode() { + return state.getXorMode(); + } + + @Override + public void setXORMode(Color c1) { + if (isDisposed() || c1 == null) { + return; + } + emit(new SetXORModeCommand(c1)); + state.setXorMode(c1); + debugValidateGraphics.setXORMode(c1); + } + + private void emit(Command command) { + commands.add(command); + } + + protected Iterable> getCommands() { + return commands; + } + + protected boolean isDisposed() { + return disposed; + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphicsFormat.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphicsFormat.java new file mode 100644 index 0000000..887abcb --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/VectorGraphicsFormat.java @@ -0,0 +1,5 @@ +package org.xbib.graphics.io.vector; + +public enum VectorGraphicsFormat { + EPS, PDF, SVG +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/AffineTransformCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/AffineTransformCommand.java new file mode 100644 index 0000000..b2d726f --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/AffineTransformCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.geom.AffineTransform; + +public abstract class AffineTransformCommand extends StateCommand { + public AffineTransformCommand(AffineTransform transform) { + super(transform); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/CreateCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/CreateCommand.java new file mode 100644 index 0000000..73323d3 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/CreateCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.VectorGraphics2D; + +public class CreateCommand extends StateCommand { + public CreateCommand(VectorGraphics2D graphics) { + super(graphics); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DisposeCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DisposeCommand.java new file mode 100644 index 0000000..d11e178 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DisposeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.VectorGraphics2D; + +public class DisposeCommand extends StateCommand { + public DisposeCommand(VectorGraphics2D graphics) { + super(graphics); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawImageCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawImageCommand.java new file mode 100644 index 0000000..613e994 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawImageCommand.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.Command; +import java.awt.Image; +import java.util.Locale; + +public class DrawImageCommand extends Command { + private final int imageWidth; + private final int imageHeight; + private final double x; + private final double y; + private final double width; + private final double height; + + public DrawImageCommand(Image image, int imageWidth, int imageHeight, + double x, double y, double width, double height) { + super(image); + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public int getImageWidth() { + return imageWidth; + } + + public int getImageHeight() { + return imageHeight; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getWidth() { + return width; + } + + public double getHeight() { + return height; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[value=%s, imageWidth=%d, imageHeight=%d, x=%f, y=%f, width=%f, height=%f]", + getClass().getName(), getValue(), + getImageWidth(), getImageHeight(), + getX(), getY(), getWidth(), getHeight()); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawShapeCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawShapeCommand.java new file mode 100644 index 0000000..cbfff32 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawShapeCommand.java @@ -0,0 +1,13 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import java.awt.Shape; + +public class DrawShapeCommand extends Command { + + public DrawShapeCommand(Shape shape) { + super(GraphicsUtils.clone(shape)); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawStringCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawStringCommand.java new file mode 100644 index 0000000..7a23ebb --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/DrawStringCommand.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.Command; +import java.util.Locale; + + +public class DrawStringCommand extends Command { + private final double x; + private final double y; + + public DrawStringCommand(String string, double x, double y) { + super(string); + this.x = x; + this.y = y; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + @Override + public String toString() { + return String.format((Locale) null, "%s[value=%s, x=%f, y=%f]", + getClass().getName(), getValue(), getX(), getY()); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/FillShapeCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/FillShapeCommand.java new file mode 100644 index 0000000..ce91250 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/FillShapeCommand.java @@ -0,0 +1,13 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import java.awt.Shape; + +public class FillShapeCommand extends Command { + + public FillShapeCommand(Shape shape) { + super(GraphicsUtils.clone(shape)); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/Group.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/Group.java new file mode 100644 index 0000000..9143e3c --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/Group.java @@ -0,0 +1,17 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.Command; +import java.util.LinkedList; +import java.util.List; + +public class Group extends Command>> { + public Group() { + super(new LinkedList>()); + } + + public void add(Command command) { + List> group = getValue(); + group.add(command); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/RotateCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/RotateCommand.java new file mode 100644 index 0000000..433d22b --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/RotateCommand.java @@ -0,0 +1,38 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class RotateCommand extends AffineTransformCommand { + private final double theta; + private final double centerX; + private final double centerY; + + public RotateCommand(double theta, double centerX, double centerY) { + super(AffineTransform.getRotateInstance(theta, centerX, centerY)); + this.theta = theta; + this.centerX = centerX; + this.centerY = centerY; + } + + public double getTheta() { + return theta; + } + + public double getCenterX() { + return centerX; + } + + public double getCenterY() { + return centerY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[theta=%f, centerX=%f, centerY=%f, value=%s]", + getClass().getName(), getTheta(), getCenterX(), getCenterY(), + getValue()); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ScaleCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ScaleCommand.java new file mode 100644 index 0000000..6d436cb --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ScaleCommand.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class ScaleCommand extends AffineTransformCommand { + private final double scaleX; + private final double scaleY; + + public ScaleCommand(double scaleX, double scaleY) { + super(AffineTransform.getScaleInstance(scaleX, scaleY)); + this.scaleX = scaleX; + this.scaleY = scaleY; + } + + public double getScaleX() { + return scaleX; + } + + public double getScaleY() { + return scaleY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[scaleX=%f, scaleY=%f, value=%s]", getClass().getName(), + getScaleX(), getScaleY(), getValue()); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetBackgroundCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetBackgroundCommand.java new file mode 100644 index 0000000..2a0c497 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetBackgroundCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Color; + +public class SetBackgroundCommand extends StateCommand { + public SetBackgroundCommand(Color color) { + super(color); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetClipCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetClipCommand.java new file mode 100644 index 0000000..8bc0d28 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetClipCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Shape; + +public class SetClipCommand extends StateCommand { + public SetClipCommand(Shape shape) { + super(shape); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetColorCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetColorCommand.java new file mode 100644 index 0000000..eb41bcf --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetColorCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Color; + +public class SetColorCommand extends StateCommand { + public SetColorCommand(Color color) { + super(color); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetCompositeCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetCompositeCommand.java new file mode 100644 index 0000000..143ec4a --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetCompositeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Composite; + +public class SetCompositeCommand extends StateCommand { + public SetCompositeCommand(Composite composite) { + super(composite); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetFontCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetFontCommand.java new file mode 100644 index 0000000..996bbdc --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetFontCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Font; + +public class SetFontCommand extends StateCommand { + public SetFontCommand(Font font) { + super(font); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetHintCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetHintCommand.java new file mode 100644 index 0000000..a056790 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetHintCommand.java @@ -0,0 +1,24 @@ +package org.xbib.graphics.io.vector.commands; + +import java.util.Locale; + +public class SetHintCommand extends StateCommand { + private final Object key; + + public SetHintCommand(Object hintKey, Object hintValue) { + super(hintValue); + key = hintKey; + } + + public Object getKey() { + return key; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[key=%s, value=%s]", getClass().getName(), + getKey(), getValue()); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetPaintCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetPaintCommand.java new file mode 100644 index 0000000..f3d3ab8 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetPaintCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Paint; + +public class SetPaintCommand extends StateCommand { + public SetPaintCommand(Paint paint) { + super(paint); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetStrokeCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetStrokeCommand.java new file mode 100644 index 0000000..da91cef --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetStrokeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Stroke; + +public class SetStrokeCommand extends StateCommand { + public SetStrokeCommand(Stroke stroke) { + super(stroke); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetTransformCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetTransformCommand.java new file mode 100644 index 0000000..180ecdf --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetTransformCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.geom.AffineTransform; + +public class SetTransformCommand extends StateCommand { + public SetTransformCommand(AffineTransform transform) { + super(new AffineTransform(transform)); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetXORModeCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetXORModeCommand.java new file mode 100644 index 0000000..e472bdf --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/SetXORModeCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.Color; + +public class SetXORModeCommand extends StateCommand { + public SetXORModeCommand(Color mode) { + super(mode); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ShearCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ShearCommand.java new file mode 100644 index 0000000..25c9d60 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/ShearCommand.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class ShearCommand extends AffineTransformCommand { + private final double shearX; + private final double shearY; + + public ShearCommand(double shearX, double shearY) { + super(AffineTransform.getShearInstance(shearX, shearY)); + this.shearX = shearX; + this.shearY = shearY; + } + + public double getShearX() { + return shearX; + } + + public double getShearY() { + return shearY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[shearX=%f, shearY=%f, value=%s]", getClass().getName(), + getShearX(), getShearY(), getValue()); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/StateCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/StateCommand.java new file mode 100644 index 0000000..cc4b0b8 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/StateCommand.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.io.vector.commands; + +import org.xbib.graphics.io.vector.Command; + +public abstract class StateCommand extends Command { + public StateCommand(T value) { + super(value); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TransformCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TransformCommand.java new file mode 100644 index 0000000..0e7ba54 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TransformCommand.java @@ -0,0 +1,17 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.geom.AffineTransform; + +public class TransformCommand extends AffineTransformCommand { + private final AffineTransform transform; + + public TransformCommand(AffineTransform transform) { + super(transform); + this.transform = new AffineTransform(transform); + } + + public AffineTransform getTransform() { + return transform; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TranslateCommand.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TranslateCommand.java new file mode 100644 index 0000000..6b48654 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/commands/TranslateCommand.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.io.vector.commands; + +import java.awt.geom.AffineTransform; +import java.util.Locale; + +public class TranslateCommand extends AffineTransformCommand { + private final double deltaX; + private final double deltaY; + + public TranslateCommand(double x, double y) { + super(AffineTransform.getTranslateInstance(x, y)); + this.deltaX = x; + this.deltaY = y; + } + + public double getDeltaX() { + return deltaX; + } + + public double getDeltaY() { + return deltaY; + } + + @Override + public String toString() { + return String.format((Locale) null, + "%s[deltaX=%f, deltaY=%f, value=%s]", getClass().getName(), + getDeltaX(), getDeltaY(), getValue()); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSGraphics2D.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSGraphics2D.java new file mode 100644 index 0000000..1bd4542 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSGraphics2D.java @@ -0,0 +1,34 @@ +package org.xbib.graphics.io.vector.eps; + +import org.xbib.graphics.io.vector.VectorGraphics2D; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.BasicStroke; +import java.awt.Color; + +/** + * {@code Graphics2D} implementation that saves all operations to a string + * in the Encapsulated PostScript® (EPS) format. + */ +public class EPSGraphics2D extends VectorGraphics2D { + + /** + * Initializes a new VectorGraphics2D pipeline for translating Graphics2D + * commands to EPS data. The document dimensions must be specified as + * parameters. + * + * @param x Left offset. + * @param y Top offset + * @param width Width. + * @param height Height. + */ + public EPSGraphics2D(double x, double y, double width, double height) { + super(new EPSProcessor(), new PageSize(x, y, width, height)); + /* + * The following are the default settings for the graphics state in an EPS file. + * Although they currently appear in the document output, they do not have to be set explicitly. + */ + // TODO: Default graphics state does not need to be printed in the document + setColor(Color.BLACK); + setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, null, 0f)); + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessor.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessor.java new file mode 100644 index 0000000..775f653 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessor.java @@ -0,0 +1,22 @@ +package org.xbib.graphics.io.vector.eps; + +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.Processor; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter; +import org.xbib.graphics.io.vector.PageSize; +import java.io.IOException; + +public class EPSProcessor implements Processor { + + @Override + public ProcessorResult process(Iterable> commands, PageSize pageSize) throws IOException { + FillPaintedShapeAsImageFilter paintedShapeAsImageFilter = new FillPaintedShapeAsImageFilter(commands); + EPSProcessorResult epsDocument = new EPSProcessorResult(pageSize); + for (Command command : paintedShapeAsImageFilter) { + epsDocument.handle(command); + } + epsDocument.close(); + return epsDocument; + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessorResult.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessorResult.java new file mode 100644 index 0000000..2158b6a --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/eps/EPSProcessorResult.java @@ -0,0 +1,470 @@ +package org.xbib.graphics.io.vector.eps; + +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.GraphicsState; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.CreateCommand; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import org.xbib.graphics.io.vector.commands.DrawImageCommand; +import org.xbib.graphics.io.vector.commands.DrawShapeCommand; +import org.xbib.graphics.io.vector.commands.DrawStringCommand; +import org.xbib.graphics.io.vector.commands.FillShapeCommand; +import org.xbib.graphics.io.vector.commands.RotateCommand; +import org.xbib.graphics.io.vector.commands.ScaleCommand; +import org.xbib.graphics.io.vector.commands.SetClipCommand; +import org.xbib.graphics.io.vector.commands.SetColorCommand; +import org.xbib.graphics.io.vector.commands.SetCompositeCommand; +import org.xbib.graphics.io.vector.commands.SetFontCommand; +import org.xbib.graphics.io.vector.commands.SetPaintCommand; +import org.xbib.graphics.io.vector.commands.SetStrokeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.commands.ShearCommand; +import org.xbib.graphics.io.vector.commands.TransformCommand; +import org.xbib.graphics.io.vector.commands.TranslateCommand; +import org.xbib.graphics.io.vector.util.ASCII85EncodeStream; +import org.xbib.graphics.io.vector.util.AlphaToMaskOp; +import org.xbib.graphics.io.vector.util.DataUtils; +import org.xbib.graphics.io.vector.util.FlateEncodeStream; +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import org.xbib.graphics.io.vector.util.ImageDataStream; +import org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving; +import org.xbib.graphics.io.vector.util.LineWrapOutputStream; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Image; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Arc2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EPSProcessorResult implements ProcessorResult { + /** + * Constant to convert values from millimeters to PostScript® units + * (1/72th inch). + */ + private static final double UNITS_PER_MM = 72.0 / 25.4; + + private static final String CHARSET = "ISO-8859-1"; + + private static final String EOL = "\n"; + + private static final int MAX_LINE_WIDTH = 255; + + private static final Pattern ELEMENT_SEPARATION_PATTERN = Pattern.compile("(.{1," + MAX_LINE_WIDTH + "})(\\s+|$)"); + + /** + * Mapping of stroke endcap values from Java to PostScript®. + */ + private static final Map STROKE_ENDCAPS = DataUtils.map( + new Integer[]{BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE}, + new Integer[]{0, 1, 2} + ); + + /** + * Mapping of line join values for path drawing from Java to + * PostScript®. + */ + private static final Map STROKE_LINEJOIN = DataUtils.map( + new Integer[]{BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL}, + new Integer[]{0, 1, 2} + ); + + private static final String FONT_LATIN1_SUFFIX = "Lat"; + + private final PageSize pageSize; + + private final List elements; + + public EPSProcessorResult(PageSize pageSize) { + this.pageSize = pageSize; + this.elements = new LinkedList<>(); + addHeader(); + } + + private static String getOutput(Color c) { + // TODO Handle transparency + return String.valueOf(c.getRed() / 255.0) + " " + c.getGreen() / 255.0 + " " + c.getBlue() / 255.0 + " rgb"; + } + + private static String getOutput(Shape s) { + StringBuilder out = new StringBuilder(); + out.append("newpath "); + if (s instanceof Line2D) { + Line2D l = (Line2D) s; + out.append(l.getX1()).append(" ").append(l.getY1()).append(" M ") + .append(l.getX2()).append(" ").append(l.getY2()).append(" L"); + } else if (s instanceof Rectangle2D) { + Rectangle2D r = (Rectangle2D) s; + out.append(r.getX()).append(" ").append(r.getY()).append(" ") + .append(r.getWidth()).append(" ").append(r.getHeight()) + .append(" rect Z"); + } else if (s instanceof Ellipse2D) { + Ellipse2D e = (Ellipse2D) s; + double x = e.getX() + e.getWidth() / 2.0; + double y = e.getY() + e.getHeight() / 2.0; + double rx = e.getWidth() / 2.0; + double ry = e.getHeight() / 2.0; + out.append(x).append(" ").append(y).append(" ") + .append(rx).append(" ").append(ry).append(" ") + .append(360.0).append(" ").append(0.0) + .append(" ellipse Z"); + } else if (s instanceof Arc2D) { + Arc2D e = (Arc2D) s; + double x = (e.getX() + e.getWidth() / 2.0); + double y = (e.getY() + e.getHeight() / 2.0); + double rx = e.getWidth() / 2.0; + double ry = e.getHeight() / 2.0; + double startAngle = -e.getAngleStart(); + double endAngle = -(e.getAngleStart() + e.getAngleExtent()); + out.append(x).append(" ").append(y).append(" ") + .append(rx).append(" ").append(ry).append(" ") + .append(startAngle).append(" ").append(endAngle) + .append(" ellipse"); + if (e.getArcType() == Arc2D.CHORD) { + out.append(" Z"); + } else if (e.getArcType() == Arc2D.PIE) { + out.append(" ").append(x).append(" ").append(y).append(" L Z"); + } + } else { + PathIterator segments = s.getPathIterator(null); + double[] coordsCur = new double[6]; + double[] pointPrev = new double[2]; + for (int i = 0; !segments.isDone(); i++, segments.next()) { + if (i > 0) { + out.append(" "); + } + int segmentType = segments.currentSegment(coordsCur); + switch (segmentType) { + case PathIterator.SEG_MOVETO: + out.append(coordsCur[0]).append(" ").append(coordsCur[1]) + .append(" M"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_LINETO: + out.append(coordsCur[0]).append(" ").append(coordsCur[1]) + .append(" L"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_CUBICTO: + out.append(coordsCur[0]).append(" ").append(coordsCur[1]) + .append(" ").append(coordsCur[2]).append(" ") + .append(coordsCur[3]).append(" ").append(coordsCur[4]) + .append(" ").append(coordsCur[5]).append(" C"); + pointPrev[0] = coordsCur[4]; + pointPrev[1] = coordsCur[5]; + break; + case PathIterator.SEG_QUADTO: + double x1 = pointPrev[0] + 2.0 / 3.0 * (coordsCur[0] - pointPrev[0]); + double y1 = pointPrev[1] + 2.0 / 3.0 * (coordsCur[1] - pointPrev[1]); + double x2 = coordsCur[0] + 1.0 / 3.0 * (coordsCur[2] - coordsCur[0]); + double y2 = coordsCur[1] + 1.0 / 3.0 * (coordsCur[3] - coordsCur[1]); + double x3 = coordsCur[2]; + double y3 = coordsCur[3]; + out.append(x1).append(" ").append(y1).append(" ") + .append(x2).append(" ").append(y2).append(" ") + .append(x3).append(" ").append(y3).append(" C"); + pointPrev[0] = x3; + pointPrev[1] = y3; + break; + case PathIterator.SEG_CLOSE: + out.append("Z"); + break; + default: + throw new IllegalStateException("Unknown path operation."); + } + } + } + return out.toString(); + } + + private static String getOutput(Image image, int imageWidth, int imageHeight, + double x, double y, double width, double height) throws IOException { + StringBuilder out = new StringBuilder(); + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + int bands = bufferedImage.getSampleModel().getNumBands(); + int bitsPerSample = DataUtils.max(bufferedImage.getSampleModel().getSampleSize()); + bitsPerSample = (int) (Math.ceil(bitsPerSample / 8.0) * 8.0); + if (bands > 3) { + bands = 3; + } + out.append("gsave").append(EOL); + if (x != 0.0 || y != 0.0) { + out.append(x).append(" ").append(y).append(" translate").append(EOL); + } + if (width != 1.0 || height != 1.0) { + out.append(width).append(" ").append(height).append(" scale").append(EOL); + } + int decodeScale = 1; + if (bufferedImage.getColorModel().hasAlpha()) { + // TODO Use different InterleaveType (2 or 3) for more efficient compression + out.append("<< /ImageType 3 /InterleaveType 1 ") + .append("/MaskDict ") + .append(imageWidth).append(" ").append(imageHeight).append(" ") + .append(1).append(" ").append(bitsPerSample).append(" ").append(decodeScale).append(" ") + .append(false).append(" ").append(0).append(" imgdict ") + .append("/DataDict ") + .append(imageWidth).append(" ").append(imageHeight).append(" ") + .append(bands).append(" ").append(bitsPerSample).append(" ").append(decodeScale).append(" ") + .append(true).append(" currentfile /ASCII85Decode filter ") + .append("<< /BitsPerComponent ").append(bitsPerSample).append(" >> ") + .append("/FlateDecode filter ") + .append("imgdict ") + .append(">> image").append(EOL); + + // Convert alpha values to binary mask + // FIXME Do alpha conversion in a preprocessing step on commands + bufferedImage = new AlphaToMaskOp(true).filter(bufferedImage, null); + output(bufferedImage, out); + } else { + if (bands == 1) { + out.append("/DeviceGray setcolorspace").append(EOL); + } + if (bufferedImage.getType() == BufferedImage.TYPE_BYTE_BINARY) { + decodeScale = 255; + } + out.append(imageWidth).append(" ").append(imageHeight).append(" ") + .append(bands).append(" ").append(bitsPerSample).append(" ").append(decodeScale).append(" ") + .append(true).append(" currentfile /ASCII85Decode filter ") + .append("<< /BitsPerComponent ").append(bitsPerSample).append(" >> ") + .append("/FlateDecode filter ") + .append("imgdict ") + .append("image").append(EOL); + output(bufferedImage, out); + } + out.append("grestore"); + return out.toString(); + } + + private static void output(BufferedImage image, StringBuilder out) throws IOException { + InputStream imageDataStream = new ImageDataStream(image, Interleaving.SAMPLE); + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + OutputStream compressionStream = new FlateEncodeStream( + new ASCII85EncodeStream(new LineWrapOutputStream(outBytes, 80))); + DataUtils.transfer(imageDataStream, compressionStream, 4096); + compressionStream.close(); + String compressed = outBytes.toString(CHARSET); + out.append(compressed).append(EOL); + } + + private static String getOutput(String str, double x, double y) { + return "gsave 1 -1 scale " + x + " " + -y + " M " + getOutput(str) + " show " + "grestore"; + } + + private static StringBuilder getOutput(String str) { + StringBuilder out = new StringBuilder(); + // Escape text + str = str.replaceAll("\\\\", "\\\\\\\\") + .replaceAll("\t", "\\\\t") + .replaceAll("\b", "\\\\b") + .replaceAll("\f", "\\\\f") + .replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)") + .replaceAll("[\r\n]", ""); + out.append("(").append(str).append(")"); + return out; + } + + private static String getOutput(Stroke s) { + StringBuilder out = new StringBuilder(); + if (s instanceof BasicStroke) { + BasicStroke bs = (BasicStroke) s; + out.append(bs.getLineWidth()).append(" setlinewidth ") + .append(STROKE_LINEJOIN.get(bs.getLineJoin())).append(" setlinejoin ") + .append(STROKE_ENDCAPS.get(bs.getEndCap())).append(" setlinecap ") + .append("[").append(DataUtils.join(" ", bs.getDashArray())).append("] ") + .append(bs.getDashPhase()).append(" setdash"); + } else { + out.append("% Custom strokes aren't supported at the moment"); + } + return out.toString(); + } + + private static String getOutput(Font font) { + StringBuilder out = new StringBuilder(); + font = GraphicsUtils.getPhysicalFont(font); + String fontName = font.getPSName(); + + // Convert font to ISO-8859-1 encoding + String fontNameLatin1 = fontName + FONT_LATIN1_SUFFIX; + out.append("/").append(fontNameLatin1).append(" ") + .append("/").append(font.getPSName()).append(" latinize "); + + // Use encoded font + out.append("/").append(fontNameLatin1).append(" ") + .append(font.getSize2D()).append(" selectfont"); + + return out.toString(); + } + + private void addHeader() { + double x = pageSize.getX() * UNITS_PER_MM, + y = pageSize.getY() * UNITS_PER_MM, + width = pageSize.getWidth() * UNITS_PER_MM, + height = pageSize.getHeight() * UNITS_PER_MM; + elements.addAll(Arrays.asList( + "%!PS-Adobe-3.0 EPSF-3.0", + "%%BoundingBox: " + ((int) Math.floor(x)) + " " + ((int) Math.floor(y)) + " " + ((int) Math.ceil(x + width)) + " " + ((int) Math.ceil(y + height)), + "%%HiResBoundingBox: " + x + " " + y + " " + (x + width) + " " + (y + height), + "%%LanguageLevel: 3", + "%%Pages: 1", + "%%EndComments", + "%%Page: 1 1", + "/M /moveto load def", + "/L /lineto load def", + "/C /curveto load def", + "/Z /closepath load def", + "/RL /rlineto load def", + "/rgb /setrgbcolor load def", + "/rect { /height exch def /width exch def /y exch def /x exch def x y M width 0 RL 0 height RL width neg 0 RL } bind def", + "/ellipse { /endangle exch def /startangle exch def /ry exch def /rx exch def /y exch def /x exch def /savematrix matrix currentmatrix def x y translate rx ry scale 0 0 1 startangle endangle arcn savematrix setmatrix } bind def", + "/imgdict { /datastream exch def /hasdata exch def /decodeScale exch def /bits exch def /bands exch def /imgheight exch def /imgwidth exch def << /ImageType 1 /Width imgwidth /Height imgheight /BitsPerComponent bits /Decode [bands {0 decodeScale} repeat] /ImageMatrix [imgwidth 0 0 imgheight 0 0] hasdata { /DataSource datastream } if >> } bind def", + "/latinize { /fontName exch def /fontNameNew exch def fontName findfont 0 dict copy begin /Encoding ISOLatin1Encoding def fontNameNew /FontName def currentdict end dup /FID undef fontNameNew exch definefont pop } bind def", + getOutput(GraphicsState.DEFAULT_FONT), + "gsave", + "clipsave", + "/DeviceRGB setcolorspace", + "0 " + height + " translate", + UNITS_PER_MM + " " + (-UNITS_PER_MM) + " scale", + "/basematrix matrix currentmatrix def" + )); + } + + public void write(OutputStream out) throws IOException { + OutputStreamWriter o = new OutputStreamWriter(out, CHARSET); + for (String element : elements) { + if (element == null) { + continue; + } + // Write current element in lines of 255 bytes (excluding line terminators) + // Numbers must not be separated by line breaks or errors will occur + // TODO: Integrate functionality into LineWrapOutputStream + Matcher chunkMatcher = ELEMENT_SEPARATION_PATTERN.matcher(element); + boolean chunkFound = false; + while (chunkMatcher.find()) { + chunkFound = true; + String chunk = chunkMatcher.group(); + o.write(chunk, 0, chunk.length()); + o.append(EOL); + } + if (!chunkFound) { + // TODO: Exception, if no whitespace can be found in the chunk + throw new IllegalStateException("Unable to divide eps element into lines: " + element); + } + } + o.append("%%EOF"); + o.flush(); + } + + @Override + public void close() { + // nothing to do + } + + @Override + public void handle(Command command) throws IOException { + if (command instanceof SetClipCommand) { + SetClipCommand c = (SetClipCommand) command; + Shape clip = c.getValue(); + elements.add("cliprestore"); + if (clip != null) { + elements.add(getOutput(clip) + " clip"); + } + } else if (command instanceof SetColorCommand) { + SetColorCommand c = (SetColorCommand) command; + elements.add(getOutput(c.getValue())); + } else if (command instanceof SetCompositeCommand) { + SetCompositeCommand c = (SetCompositeCommand) command; + // TODO Implement composite rendering for EPS + elements.add("% composite not yet implemented: " + c.getValue()); + } else if (command instanceof SetFontCommand) { + SetFontCommand c = (SetFontCommand) command; + elements.add(getOutput(c.getValue())); + } else if (command instanceof SetPaintCommand) { + SetPaintCommand c = (SetPaintCommand) command; + // TODO Implement paint rendering for EPS + elements.add("% paint not yet implemented: " + c.getValue()); + } else if (command instanceof SetStrokeCommand) { + SetStrokeCommand c = (SetStrokeCommand) command; + elements.add(getOutput(c.getValue())); + } else if (command instanceof SetTransformCommand) { + SetTransformCommand c = (SetTransformCommand) command; + StringBuilder e = new StringBuilder(); + double[] matrix = new double[6]; + c.getValue().getMatrix(matrix); + e.append("basematrix setmatrix [") + .append(DataUtils.join(" ", matrix)).append("] concat"); + elements.add(e.toString()); + } else if (command instanceof RotateCommand) { + RotateCommand c = (RotateCommand) command; + StringBuilder e = new StringBuilder(); + double x = c.getCenterX(); + double y = c.getCenterY(); + boolean translated = x != 0.0 || y != 0.0; + if (translated) { + e.append(x).append(" ").append(y).append(" translate "); + } + e.append(Math.toDegrees(c.getTheta())).append(" rotate"); + if (translated) { + e.append(" "); + e.append(-x).append(" ").append(-y).append(" translate"); + } + elements.add(e.toString()); + } else if (command instanceof ScaleCommand) { + ScaleCommand c = (ScaleCommand) command; + elements.add(DataUtils.format(c.getScaleX()) + " " + DataUtils.format(c.getScaleY()) + " scale"); + } else if (command instanceof ShearCommand) { + ShearCommand c = (ShearCommand) command; + elements.add("[1 " + DataUtils.format(c.getShearY()) + " " + DataUtils.format(c.getShearX()) + " 1 0 0] concat"); + } else if (command instanceof TransformCommand) { + TransformCommand c = (TransformCommand) command; + StringBuilder e = new StringBuilder(); + double[] matrix = new double[6]; + c.getValue().getMatrix(matrix); + e.append("[").append(DataUtils.join(" ", matrix)) + .append("] concat"); + elements.add(e.toString()); + } else if (command instanceof TranslateCommand) { + TranslateCommand c = (TranslateCommand) command; + elements.add(String.valueOf(c.getDeltaX()) + " " + c.getDeltaY() + " translate"); + } else if (command instanceof DrawImageCommand) { + DrawImageCommand c = (DrawImageCommand) command; + String e = getOutput(c.getValue(), + c.getImageWidth(), c.getImageHeight(), + c.getX(), c.getY(), c.getWidth(), c.getHeight()); + elements.add(e); + } else if (command instanceof DrawShapeCommand) { + DrawShapeCommand c = (DrawShapeCommand) command; + elements.add(getOutput(c.getValue()) + " stroke"); + } else if (command instanceof DrawStringCommand) { + DrawStringCommand c = (DrawStringCommand) command; + elements.add(getOutput(c.getValue(), c.getX(), c.getY())); + } else if (command instanceof FillShapeCommand) { + FillShapeCommand c = (FillShapeCommand) command; + elements.add(getOutput(c.getValue()) + " fill"); + } else if (command instanceof CreateCommand) { + elements.add("gsave"); + } else if (command instanceof DisposeCommand) { + elements.add("grestore"); + } + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.java new file mode 100644 index 0000000..1e30f38 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/AbsoluteToRelativeTransformsFilter.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.io.vector.filters; + +import org.xbib.graphics.io.vector.commands.AffineTransformCommand; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.CreateCommand; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.commands.TransformCommand; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +public class AbsoluteToRelativeTransformsFilter extends Filter { + + private final Deque transforms; + + public AbsoluteToRelativeTransformsFilter(Iterable> stream) { + super(stream); + transforms = new LinkedList<>(); + } + + @Override + public Command next() { + Command nextCommand = super.next(); + if (nextCommand instanceof AffineTransformCommand) { + AffineTransformCommand affineTransformCommand = (AffineTransformCommand) nextCommand; + getCurrentTransform().concatenate(affineTransformCommand.getValue()); + } else if (nextCommand instanceof CreateCommand) { + AffineTransform newTransform = transforms.isEmpty() ? new AffineTransform() : new AffineTransform(getCurrentTransform()); + transforms.push(newTransform); + } else if (nextCommand instanceof DisposeCommand) { + transforms.pop(); + } + return nextCommand; + } + + @Override + protected List> filter(Command command) { + if (command instanceof SetTransformCommand) { + SetTransformCommand setTransformCommand = (SetTransformCommand) command; + AffineTransform absoluteTransform = setTransformCommand.getValue(); + AffineTransform relativeTransform = new AffineTransform(); + try { + AffineTransform invertedOldTransformation = getCurrentTransform().createInverse(); + relativeTransform.concatenate(invertedOldTransformation); + } catch (NoninvertibleTransformException e) { + // ignore + } + relativeTransform.concatenate(absoluteTransform); + TransformCommand transformCommand = new TransformCommand(relativeTransform); + return Collections.singletonList(transformCommand); + } + return Collections.singletonList(command); + } + + private AffineTransform getCurrentTransform() { + return transforms.peek(); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.java new file mode 100644 index 0000000..653bc69 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/FillPaintedShapeAsImageFilter.java @@ -0,0 +1,69 @@ +package org.xbib.graphics.io.vector.filters; + +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import org.xbib.graphics.io.vector.commands.DrawImageCommand; +import org.xbib.graphics.io.vector.commands.FillShapeCommand; +import org.xbib.graphics.io.vector.commands.SetPaintCommand; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.List; + +public class FillPaintedShapeAsImageFilter extends Filter { + private SetPaintCommand lastSetPaintCommand; + + public FillPaintedShapeAsImageFilter(Iterable> stream) { + super(stream); + } + + @Override + public Command next() { + Command nextCommand = super.next(); + + if (nextCommand instanceof SetPaintCommand) { + lastSetPaintCommand = (SetPaintCommand) nextCommand; + } else if (nextCommand instanceof DisposeCommand) { + lastSetPaintCommand = null; + } + + return nextCommand; + } + + private DrawImageCommand getDrawImageCommand(FillShapeCommand shapeCommand, SetPaintCommand paintCommand) { + Shape shape = shapeCommand.getValue(); + Rectangle2D shapeBounds = shape.getBounds2D(); + double x = shapeBounds.getX(); + double y = shapeBounds.getY(); + double width = shapeBounds.getWidth(); + double height = shapeBounds.getHeight(); + int imageWidth = (int) Math.round(width); + int imageHeight = (int) Math.round(height); + BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D imageGraphics = (Graphics2D) image.getGraphics(); + imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + imageGraphics.scale(imageWidth / width, imageHeight / height); + imageGraphics.translate(-shapeBounds.getX(), -shapeBounds.getY()); + imageGraphics.setPaint(paintCommand.getValue()); + imageGraphics.fill(shape); + imageGraphics.dispose(); + + DrawImageCommand drawImageCommand = new DrawImageCommand(image, imageWidth, imageHeight, x, y, width, height); + return drawImageCommand; + } + + @Override + protected List> filter(Command command) { + if (lastSetPaintCommand != null && command instanceof FillShapeCommand) { + FillShapeCommand fillShapeCommand = (FillShapeCommand) command; + DrawImageCommand drawImageCommand = getDrawImageCommand(fillShapeCommand, lastSetPaintCommand); + return Arrays.>asList(drawImageCommand); + } + + return Arrays.>asList(command); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/Filter.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/Filter.java new file mode 100644 index 0000000..87e772a --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/Filter.java @@ -0,0 +1,47 @@ +package org.xbib.graphics.io.vector.filters; + +import org.xbib.graphics.io.vector.Command; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public abstract class Filter implements Iterable>, Iterator> { + private final Queue> buffer; + private final Iterator> iterator; + + public Filter(Iterable> stream) { + buffer = new LinkedList>(); + iterator = stream.iterator(); + } + + public Iterator> iterator() { + return this; + } + + public boolean hasNext() { + findNextCommand(); + return !buffer.isEmpty(); + } + + private void findNextCommand() { + while (buffer.isEmpty() && iterator.hasNext()) { + Command command = iterator.next(); + List> commands = filter(command); + if (commands != null) { + buffer.addAll(commands); + } + } + } + + public Command next() { + findNextCommand(); + return buffer.poll(); + } + + public void remove() { + } + + protected abstract List> filter(Command command); +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/GroupingFilter.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/GroupingFilter.java new file mode 100644 index 0000000..d3e871c --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/GroupingFilter.java @@ -0,0 +1,46 @@ +package org.xbib.graphics.io.vector.filters; + +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.Group; +import java.util.Arrays; +import java.util.List; + + +public abstract class GroupingFilter extends Filter { + private Group group; + + public GroupingFilter(Iterable> stream) { + super(stream); + } + + @Override + public boolean hasNext() { + return group != null || super.hasNext(); + } + + @Override + public Command next() { + if (group == null) { + return super.next(); + } + Group g = group; + group = null; + return g; + } + + @Override + protected List> filter(Command command) { + boolean grouped = isGrouped(command); + if (grouped) { + if (group == null) { + group = new Group(); + } + group.add(command); + return null; + } + return Arrays.>asList(command); + } + + protected abstract boolean isGrouped(Command command); +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/OptimizeFilter.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/OptimizeFilter.java new file mode 100644 index 0000000..49323d4 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/OptimizeFilter.java @@ -0,0 +1,56 @@ +package org.xbib.graphics.io.vector.filters; + +import org.xbib.graphics.io.vector.commands.AffineTransformCommand; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.SetHintCommand; +import org.xbib.graphics.io.vector.commands.StateCommand; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class OptimizeFilter extends Filter { + private final Queue> buffer; + + public OptimizeFilter(Iterable> stream) { + super(stream); + buffer = new LinkedList>(); + } + + private static boolean isStateChange(Command command) { + return (command instanceof StateCommand) && + !(command instanceof AffineTransformCommand) && + !(command instanceof SetHintCommand); + } + + @Override + public boolean hasNext() { + return super.hasNext(); + } + + @Override + public Command next() { + if (buffer.isEmpty()) { + return super.next(); + } + return buffer.poll(); + } + + @Override + protected List> filter(Command command) { + if (!isStateChange(command)) { + return Arrays.>asList(command); + } + Iterator> i = buffer.iterator(); + Class cls = command.getClass(); + while (i.hasNext()) { + if (cls.equals(i.next().getClass())) { + i.remove(); + } + } + buffer.add(command); + return null; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.java new file mode 100644 index 0000000..097e749 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/filters/StateChangeGroupingFilter.java @@ -0,0 +1,17 @@ +package org.xbib.graphics.io.vector.filters; + +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.StateCommand; + +public class StateChangeGroupingFilter extends GroupingFilter { + + public StateChangeGroupingFilter(Iterable> stream) { + super(stream); + } + + @Override + protected boolean isGrouped(Command command) { + return command instanceof StateCommand; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/GeneratedPayload.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/GeneratedPayload.java new file mode 100644 index 0000000..2cf8229 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/GeneratedPayload.java @@ -0,0 +1,26 @@ +package org.xbib.graphics.io.vector.pdf; + +import java.io.IOException; + +public abstract class GeneratedPayload extends Payload { + + public GeneratedPayload(boolean stream) { + super(stream); + } + + @Override + public byte[] getBytes() throws IOException { + for (byte b : generatePayload()) { + super.write(b); + } + return super.getBytes(); + } + + @Override + public void write(int b) throws IOException { + throw new UnsupportedOperationException("Payload will be calculated and is read only."); + } + + protected abstract byte[] generatePayload() throws IOException; +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFGraphics2D.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFGraphics2D.java new file mode 100644 index 0000000..b41ba8c --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFGraphics2D.java @@ -0,0 +1,30 @@ +package org.xbib.graphics.io.vector.pdf; + +import org.xbib.graphics.io.vector.VectorGraphics2D; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.BasicStroke; +import java.awt.Color; + +/** + * {@code Graphics2D} implementation that saves all operations to a string + * in the Portable Document Format (PDF). + */ +public class PDFGraphics2D extends VectorGraphics2D { + + /** + * Initializes a new VectorGraphics2D pipeline for translating Graphics2D + * commands to PDF data. The document dimensions must be specified as + * parameters. + * + * @param x Left offset. + * @param y Top offset + * @param width Width. + * @param height Height. + */ + public PDFGraphics2D(double x, double y, double width, double height) { + super(new PDFProcessor(), new PageSize(x, y, width, height)); + // TODO: Default graphics state does not need to be printed in the document + setColor(Color.BLACK); + setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, null, 0f)); + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFObject.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFObject.java new file mode 100644 index 0000000..d831a64 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFObject.java @@ -0,0 +1,22 @@ +package org.xbib.graphics.io.vector.pdf; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class PDFObject { + public final int id; + public final int version; + public final Map dict; + public final Payload payload; + + public PDFObject(int id, int version, Map dict, Payload payload) { + this.dict = new LinkedHashMap<>(); + this.id = id; + this.version = version; + this.payload = payload; + if (dict != null) { + this.dict.putAll(dict); + } + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessor.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessor.java new file mode 100644 index 0000000..e3589f8 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessor.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.io.vector.pdf; + +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.Processor; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.filters.AbsoluteToRelativeTransformsFilter; +import org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter; +import org.xbib.graphics.io.vector.filters.StateChangeGroupingFilter; +import org.xbib.graphics.io.vector.PageSize; +import java.io.IOException; + +public class PDFProcessor implements Processor { + + private final boolean compressed; + + public PDFProcessor() { + this(false); + } + + public PDFProcessor(boolean compressed) { + this.compressed = compressed; + } + + @Override + public ProcessorResult process(Iterable> commands, PageSize pageSize) throws IOException { + AbsoluteToRelativeTransformsFilter absoluteToRelativeTransformsFilter = new AbsoluteToRelativeTransformsFilter(commands); + FillPaintedShapeAsImageFilter paintedShapeAsImageFilter = new FillPaintedShapeAsImageFilter(absoluteToRelativeTransformsFilter); + Iterable> filtered = new StateChangeGroupingFilter(paintedShapeAsImageFilter); + PDFProcessorResult doc = new PDFProcessorResult(pageSize); + doc.setCompressed(compressed); + for (Command command : filtered) { + doc.handle(command); + } + doc.close(); + return doc; + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessorResult.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessorResult.java new file mode 100644 index 0000000..bd37ad1 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/PDFProcessorResult.java @@ -0,0 +1,585 @@ +package org.xbib.graphics.io.vector.pdf; + +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.GraphicsState; +import org.xbib.graphics.io.vector.commands.AffineTransformCommand; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.CreateCommand; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import org.xbib.graphics.io.vector.commands.DrawImageCommand; +import org.xbib.graphics.io.vector.commands.DrawShapeCommand; +import org.xbib.graphics.io.vector.commands.DrawStringCommand; +import org.xbib.graphics.io.vector.commands.FillShapeCommand; +import org.xbib.graphics.io.vector.commands.Group; +import org.xbib.graphics.io.vector.commands.SetBackgroundCommand; +import org.xbib.graphics.io.vector.commands.SetClipCommand; +import org.xbib.graphics.io.vector.commands.SetColorCommand; +import org.xbib.graphics.io.vector.commands.SetFontCommand; +import org.xbib.graphics.io.vector.commands.SetHintCommand; +import org.xbib.graphics.io.vector.commands.SetPaintCommand; +import org.xbib.graphics.io.vector.commands.SetStrokeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.util.DataUtils; +import org.xbib.graphics.io.vector.util.FlateEncodeStream; +import org.xbib.graphics.io.vector.util.FormattingWriter; +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import org.xbib.graphics.io.vector.util.ImageDataStream; +import org.xbib.graphics.io.vector.util.ImageDataStream.Interleaving; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Image; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class PDFProcessorResult implements ProcessorResult { + + private static final String EOL = "\n"; + + private static final String CHARSET = "ISO-8859-1"; + + private static final String HEADER = "%PDF-1.4"; + + private static final String FOOTER = "%%EOF"; + + /** + * Constant to convert values from millimeters to PDF units (1/72th inch). + */ + private static final double MM_IN_UNITS = 72.0 / 25.4; + + /** + * Mapping of stroke endcap values from Java to PDF. + */ + private static final Map STROKE_ENDCAPS = DataUtils.map( + new Integer[]{BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE}, + new Integer[]{0, 1, 2} + ); + + /** + * Mapping of line join values for path drawing from Java to PDF. + */ + private static final Map STROKE_LINEJOIN = DataUtils.map( + new Integer[]{BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL}, + new Integer[]{0, 1, 2} + ); + + private final PageSize pageSize; + + private final List objects; + + private final Map xref; + + private final Map images; + + private final Deque states; + + private int objectIdCounter; + + private PDFObject contents; + + private Resources resources; + + private boolean transformed; + + private boolean compressed; + + public PDFProcessorResult(PageSize pageSize) throws IOException { + this.pageSize = pageSize; + states = new LinkedList<>(); + states.push(new GraphicsState()); + objects = new LinkedList<>(); + objectIdCounter = 1; + xref = new HashMap<>(); + images = new HashMap<>(); + compressed = false; // disable compress by default + initPage(); + } + + public void setCompressed(boolean compressed) { + this.compressed = compressed; + } + + public static String toString(PDFObject obj) throws IOException { + StringBuilder out = new StringBuilder(); + out.append(obj.id).append(" ").append(obj.version).append(" obj") + .append(EOL); + if (!obj.dict.isEmpty()) { + out.append(serialize(obj.dict)).append(EOL); + } + if (obj.payload != null) { + String content = new String(obj.payload.getBytes(), CHARSET); + if (content.length() > 0) { + if (obj.payload.isStream()) { + out.append("stream").append(EOL); + } + out.append(content); + if (obj.payload.isStream()) { + out.append("endstream"); + } + out.append(EOL); + } + } + out.append("endobj"); + return out.toString(); + } + + private static String serialize(Object obj) { + if (obj instanceof String) { + return "/" + obj.toString(); + } else if (obj instanceof float[]) { + return serialize(DataUtils.asList((float[]) obj)); + } else if (obj instanceof double[]) { + return serialize(DataUtils.asList((double[]) obj)); + } else if (obj instanceof Object[]) { + return serialize(Arrays.asList((Object[]) obj)); + } else if (obj instanceof List) { + List list = (List) obj; + StringBuilder out = new StringBuilder(); + out.append("["); + int i = 0; + for (Object elem : list) { + if (i++ > 0) { + out.append(" "); + } + out.append(serialize(elem)); + } + out.append("]"); + return out.toString(); + } else if (obj instanceof Map) { + Map dict = (Map) obj; + StringBuilder out = new StringBuilder(); + out.append("<<").append(EOL); + for (Map.Entry entry : dict.entrySet()) { + String key = entry.getKey().toString(); + out.append(serialize(key)).append(" "); + Object value = entry.getValue(); + out.append(serialize(value)).append(EOL); + } + out.append(">>"); + return out.toString(); + } else if (obj instanceof PDFObject) { + PDFObject pdfObj = (PDFObject) obj; + return pdfObj.id + " " + pdfObj.version + " R"; + } else { + return DataUtils.format(obj); + } + } + + private static String getOutput(Color c) { + StringBuilder out = new StringBuilder(); + String r = serialize(c.getRed() / 255.0); + String g = serialize(c.getGreen() / 255.0); + String b = serialize(c.getBlue() / 255.0); + out.append(r).append(" ").append(g).append(" ").append(b).append(" rg ") + .append(r).append(" ").append(g).append(" ").append(b).append(" RG"); + return out.toString(); + } + + private static String getOutput(Shape s) { + StringBuilder out = new StringBuilder(); + PathIterator segments = s.getPathIterator(null); + double[] coordsCur = new double[6]; + double[] pointPrev = new double[2]; + for (int i = 0; !segments.isDone(); i++, segments.next()) { + if (i > 0) { + out.append(" "); + } + int segmentType = segments.currentSegment(coordsCur); + switch (segmentType) { + case PathIterator.SEG_MOVETO: + out.append(serialize(coordsCur[0])).append(" ") + .append(serialize(coordsCur[1])).append(" m"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_LINETO: + out.append(serialize(coordsCur[0])).append(" ") + .append(serialize(coordsCur[1])).append(" l"); + pointPrev[0] = coordsCur[0]; + pointPrev[1] = coordsCur[1]; + break; + case PathIterator.SEG_CUBICTO: + out.append(serialize(coordsCur[0])).append(" ") + .append(serialize(coordsCur[1])).append(" ") + .append(serialize(coordsCur[2])).append(" ") + .append(serialize(coordsCur[3])).append(" ") + .append(serialize(coordsCur[4])).append(" ") + .append(serialize(coordsCur[5])).append(" c"); + pointPrev[0] = coordsCur[4]; + pointPrev[1] = coordsCur[5]; + break; + case PathIterator.SEG_QUADTO: + double x1 = pointPrev[0] + 2.0 / 3.0 * (coordsCur[0] - pointPrev[0]); + double y1 = pointPrev[1] + 2.0 / 3.0 * (coordsCur[1] - pointPrev[1]); + double x2 = coordsCur[0] + 1.0 / 3.0 * (coordsCur[2] - coordsCur[0]); + double y2 = coordsCur[1] + 1.0 / 3.0 * (coordsCur[3] - coordsCur[1]); + double x3 = coordsCur[2]; + double y3 = coordsCur[3]; + out.append(serialize(x1)).append(" ") + .append(serialize(y1)).append(" ") + .append(serialize(x2)).append(" ") + .append(serialize(y2)).append(" ") + .append(serialize(x3)).append(" ") + .append(serialize(y3)).append(" c"); + pointPrev[0] = x3; + pointPrev[1] = y3; + break; + case PathIterator.SEG_CLOSE: + out.append("h"); + break; + default: + throw new IllegalStateException("Unknown path operation."); + } + } + + return out.toString(); + } + + private static String getOutput(GraphicsState state, Resources resources, boolean first) { + StringBuilder out = new StringBuilder(); + if (!first) { + out.append("Q").append(EOL); + } + out.append("q").append(EOL); + if (!state.getColor().equals(GraphicsState.DEFAULT_COLOR)) { + if (state.getColor().getAlpha() != GraphicsState.DEFAULT_COLOR.getAlpha()) { + double a = state.getColor().getAlpha() / 255.0; + String resourceId = resources.getId(a); + out.append("/").append(resourceId).append(" gs").append(EOL); + } + out.append(getOutput(state.getColor())).append(EOL); + } + if (!state.getTransform().equals(GraphicsState.DEFAULT_TRANSFORM)) { + out.append(getOutput(state.getTransform())).append(" cm").append(EOL); + } + if (!state.getStroke().equals(GraphicsState.DEFAULT_STROKE)) { + out.append(getOutput(state.getStroke())).append(EOL); + } + if (state.getClip() != GraphicsState.DEFAULT_CLIP) { + out.append(getOutput(state.getClip())).append(" W n").append(EOL); + } + if (!state.getFont().equals(GraphicsState.DEFAULT_FONT)) { + Font font = state.getFont(); + String fontResourceId = resources.getId(font); + float fontSize = font.getSize2D(); + out.append("/").append(fontResourceId).append(" ").append(fontSize) + .append(" Tf").append(EOL); + } + + return DataUtils.stripTrailing(out.toString(), EOL); + } + + private static String getOutput(Stroke s) { + StringBuilder out = new StringBuilder(); + if (s instanceof BasicStroke) { + BasicStroke strokeDefault = (BasicStroke) GraphicsState.DEFAULT_STROKE; + BasicStroke strokeNew = (BasicStroke) s; + if (strokeNew.getLineWidth() != strokeDefault.getLineWidth()) { + out.append(serialize(strokeNew.getLineWidth())) + .append(" w").append(EOL); + } + if (strokeNew.getLineJoin() == BasicStroke.JOIN_MITER && strokeNew.getMiterLimit() != strokeDefault.getMiterLimit()) { + out.append(serialize(strokeNew.getMiterLimit())) + .append(" M").append(EOL); + } + if (strokeNew.getLineJoin() != strokeDefault.getLineJoin()) { + out.append(serialize(STROKE_LINEJOIN.get(strokeNew.getLineJoin()))) + .append(" j").append(EOL); + } + if (strokeNew.getEndCap() != strokeDefault.getEndCap()) { + out.append(serialize(STROKE_ENDCAPS.get(strokeNew.getEndCap()))) + .append(" J").append(EOL); + } + if (strokeNew.getDashArray() != strokeDefault.getDashArray()) { + if (strokeNew.getDashArray() != null) { + out.append(serialize(strokeNew.getDashArray())).append(" ") + .append(serialize(strokeNew.getDashPhase())) + .append(" d").append(EOL); + } else { + out.append(EOL).append("[] 0 d").append(EOL); + } + } + } + return out.toString(); + } + + private static String getOutput(AffineTransform transform) { + double[] matrix = new double[6]; + transform.getMatrix(matrix); + return DataUtils.join(" ", matrix); + } + + private static String getOutput(String str, double x, double y) { + + // Save current graphics state + // Undo swapping of y axis + // Render text + // Restore previous graphics state + + return "q " + "1 0 0 -1 " + x + " " + y + " cm " + "BT " + getOutput(str) + " Tj ET " + "Q"; + } + + private static StringBuilder getOutput(String str) { + StringBuilder out = new StringBuilder(); + + // Escape string + str = str.replaceAll("\\\\", "\\\\\\\\") + .replaceAll("\t", "\\\\t") + .replaceAll("\b", "\\\\b") + .replaceAll("\f", "\\\\f") + .replaceAll("\\(", "\\\\(") + .replaceAll("\\)", "\\\\)") + .replaceAll("[\r\n]", ""); + + out.append("(").append(str).append(")"); + + return out; + } + + private static String getOutput(PDFObject image, double x, double y, + double width, double height, Resources resources) { + // Query image resource id + String resourceId = resources.getId(image); + + // Save graphics state + // Move image to correct position and scale it to (width, height) + // Swap y axis + // Draw image + // Restore old graphics state + + return "q " + width + " 0 0 " + height + " " + x + " " + y + " cm " + "1 0 0 -1 0 1 cm " + "/" + resourceId + " Do " + "Q"; + } + + private GraphicsState getCurrentState() { + return states.peek(); + } + + private void initPage() throws IOException { + Map dict; + dict = DataUtils.map(new String[]{"Type"}, new Object[]{"Catalog"}); + PDFObject catalog = addObject(dict, null); + List pagesKids = new LinkedList<>(); + dict = DataUtils.map( + new String[]{"Type", "Kids", "Count"}, + new Object[]{"Pages", pagesKids, 1}); + PDFObject pages = addObject(dict, null); + catalog.dict.put("Pages", pages); + double x = pageSize.getX() * MM_IN_UNITS; + double y = pageSize.getY() * MM_IN_UNITS; + double width = pageSize.getWidth() * MM_IN_UNITS; + double height = pageSize.getHeight() * MM_IN_UNITS; + dict = DataUtils.map( + new String[]{"Type", "Parent", "MediaBox"}, + new Object[]{"Page", pages, new double[]{x, y, width, height}}); + PDFObject page = addObject(dict, null); + pagesKids.add(page); + Payload contentsPayload = new Payload(true); + contents = addObject(null, contentsPayload); + page.dict.put("Contents", contents); + if (compressed) { + try { + contentsPayload.addFilter(FlateEncodeStream.class); + contents.dict.put("Filter", new Object[]{"FlateDecode"}); + } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { + // ignore + } + } + contentsPayload.write(DataUtils.join("", new Object[]{ + "q", EOL, + getOutput(getCurrentState().getColor()), EOL, + MM_IN_UNITS, " 0 0 ", -MM_IN_UNITS, " 0 ", height, " cm", EOL + }).getBytes(CHARSET)); + Payload contentLengthPayload = new SizePayload(contents, CHARSET, false); + PDFObject contentLength = addObject(null, contentLengthPayload); + contents.dict.put("Length", contentLength); + resources = new Resources(objectIdCounter++, 0); + objects.add(resources); + page.dict.put("Resources", resources); + Font font = getCurrentState().getFont(); + String fontResourceId = resources.getId(font); + float fontSize = font.getSize2D(); + contentsPayload.write(("/" + fontResourceId + " " + fontSize + " Tf" + EOL).getBytes(CHARSET)); + } + + private PDFObject addObject(Map dict, Payload payload) { + final int id = objectIdCounter++; + final int version = 0; + PDFObject object = new PDFObject(id, version, dict, payload); + objects.add(object); + return object; + } + + private PDFObject addObject(Image image) throws IOException { + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + int width = bufferedImage.getWidth(); + int height = bufferedImage.getHeight(); + int bitsPerSample = DataUtils.max(bufferedImage.getSampleModel().getSampleSize()); + int bands = bufferedImage.getSampleModel().getNumBands(); + String colorSpaceName = (bands == 1) ? "DeviceGray" : "DeviceRGB"; + Payload imagePayload = new Payload(true); + String[] imageFilters = {}; + if (compressed) { + try { + imagePayload.addFilter(FlateEncodeStream.class); + imageFilters = new String[]{"FlateDecode"}; + } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { + // ignore + } + } + InputStream imageDataStream = new ImageDataStream(bufferedImage, Interleaving.WITHOUT_ALPHA); + DataUtils.transfer(imageDataStream, imagePayload, 1024); + imagePayload.close(); + int length = imagePayload.getBytes().length; + Map imageDict = DataUtils.map( + new String[]{"Type", "Subtype", "Width", "Height", "ColorSpace", + "BitsPerComponent", "Length", "Filter"}, + new Object[]{"XObject", "Image", width, height, colorSpaceName, + bitsPerSample, length, imageFilters} + ); + PDFObject imageObject = addObject(imageDict, imagePayload); + boolean hasAlpha = bufferedImage.getColorModel().hasAlpha(); + if (hasAlpha) { + BufferedImage mask = GraphicsUtils.getAlphaImage(bufferedImage); + PDFObject maskObject = addObject(mask); + boolean isBitmask = mask.getSampleModel().getSampleSize(0) == 1; + if (isBitmask) { + maskObject.dict.put("ImageMask", true); + maskObject.dict.remove("ColorSpace"); + imageObject.dict.put("Mask", maskObject); + } else { + imageObject.dict.put("SMask", maskObject); + } + } + return imageObject; + } + + public void write(OutputStream out) throws IOException { + FormattingWriter o = new FormattingWriter(out, CHARSET, EOL); + o.writeln(HEADER); + for (PDFObject obj : objects) { + xref.put(obj, o.tell()); + o.writeln(toString(obj)); + o.flush(); + } + long xrefPos = o.tell(); + o.writeln("xref"); + o.write(0).write(" ").writeln(objects.size() + 1); + o.format("%010d %05d f ", 0, 65535).writeln(); + for (PDFObject obj : objects) { + o.format("%010d %05d n ", xref.get(obj), 0).writeln(); + } + o.flush(); + o.writeln("trailer"); + o.writeln(serialize(DataUtils.map( + new String[]{"Size", "Root"}, + new Object[]{objects.size() + 1, objects.get(0)} + ))); + + o.writeln("startxref"); + o.writeln(xrefPos); + o.writeln(FOOTER); + o.flush(); + } + + public void handle(Command command) throws IOException { + String s = ""; + if (command instanceof Group) { + Group c = (Group) command; + applyStateCommands(c.getValue()); + s = getOutput(getCurrentState(), resources, !transformed); + transformed = true; + } else if (command instanceof DrawShapeCommand) { + DrawShapeCommand c = (DrawShapeCommand) command; + s = getOutput(c.getValue()) + " S"; + } else if (command instanceof FillShapeCommand) { + FillShapeCommand c = (FillShapeCommand) command; + s = getOutput(c.getValue()) + " f"; + } else if (command instanceof DrawStringCommand) { + DrawStringCommand c = (DrawStringCommand) command; + s = getOutput(c.getValue(), c.getX(), c.getY()); + } else if (command instanceof DrawImageCommand) { + DrawImageCommand c = (DrawImageCommand) command; + // Create object for image data + Image image = c.getValue(); + PDFObject imageObject = images.get(image.hashCode()); + if (imageObject == null) { + imageObject = addObject(image); + images.put(image.hashCode(), imageObject); + } + s = getOutput(imageObject, c.getX(), c.getY(), + c.getWidth(), c.getHeight(), resources); + } + Payload contentsPayload = contents.payload; + contentsPayload.write(s.getBytes(CHARSET)); + contentsPayload.write(EOL.getBytes(CHARSET)); + } + + private void applyStateCommands(List> commands) { + for (Command command : commands) { + if (command instanceof SetHintCommand) { + SetHintCommand c = (SetHintCommand) command; + getCurrentState().getHints().put(c.getKey(), c.getValue()); + } else if (command instanceof SetBackgroundCommand) { + SetBackgroundCommand c = (SetBackgroundCommand) command; + getCurrentState().setBackground(c.getValue()); + } else if (command instanceof SetColorCommand) { + SetColorCommand c = (SetColorCommand) command; + getCurrentState().setColor(c.getValue()); + } else if (command instanceof SetPaintCommand) { + SetPaintCommand c = (SetPaintCommand) command; + getCurrentState().setPaint(c.getValue()); + } else if (command instanceof SetStrokeCommand) { + SetStrokeCommand c = (SetStrokeCommand) command; + getCurrentState().setStroke(c.getValue()); + } else if (command instanceof SetFontCommand) { + SetFontCommand c = (SetFontCommand) command; + getCurrentState().setFont(c.getValue()); + } else if (command instanceof SetTransformCommand) { + throw new UnsupportedOperationException("The PDF format has no means of setting the transformation matrix."); + } else if (command instanceof AffineTransformCommand) { + AffineTransformCommand c = (AffineTransformCommand) command; + AffineTransform stateTransform = getCurrentState().getTransform(); + AffineTransform transformToBeApplied = c.getValue(); + stateTransform.concatenate(transformToBeApplied); + getCurrentState().setTransform(stateTransform); + } else if (command instanceof SetClipCommand) { + SetClipCommand c = (SetClipCommand) command; + getCurrentState().setClip(c.getValue()); + } else if (command instanceof CreateCommand) { + try { + states.push((GraphicsState) getCurrentState().clone()); + } catch (CloneNotSupportedException e) { + // do nothing + } + } else if (command instanceof DisposeCommand) { + states.pop(); + } + } + } + + @Override + public void close() throws IOException { + String footer = "Q" + EOL; + if (transformed) { + footer += "Q" + EOL; + } + Payload contentsPayload = contents.payload; + contentsPayload.write(footer.getBytes(CHARSET)); + contentsPayload.close(); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Payload.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Payload.java new file mode 100644 index 0000000..3daed17 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Payload.java @@ -0,0 +1,54 @@ +package org.xbib.graphics.io.vector.pdf; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; + +public class Payload extends OutputStream { + + private final ByteArrayOutputStream byteStream; + + private final boolean stream; + + private OutputStream filteredStream; + + private boolean empty; + + public Payload(boolean stream) { + this.byteStream = new ByteArrayOutputStream(); + this.stream = stream; + this.filteredStream = byteStream; + this.empty = true; + } + + public byte[] getBytes() throws IOException { + return byteStream.toByteArray(); + } + + public boolean isStream() { + return stream; + } + + @Override + public void write(int b) throws IOException { + filteredStream.write(b); + empty = false; + } + + @Override + public void close() throws IOException { + super.close(); + filteredStream.close(); + } + + public void addFilter(Class filterClass) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + if (!empty) { + throw new IllegalStateException("unable to add filter after writing to payload"); + } + filteredStream = filterClass.getConstructor(OutputStream.class).newInstance(filteredStream); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Resources.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Resources.java new file mode 100644 index 0000000..82ffaa8 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/Resources.java @@ -0,0 +1,98 @@ +package org.xbib.graphics.io.vector.pdf; + +import org.xbib.graphics.io.vector.util.DataUtils; +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import java.awt.Font; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class Resources extends PDFObject { + + private static final String KEY_PROC_SET = "ProcSet"; + private static final String KEY_TRANSPARENCY = "ExtGState"; + private static final String KEY_FONT = "Font"; + private static final String KEY_IMAGE = "XObject"; + + private static final String[] VALUE_PROC_SET = {"PDF", "Text", "ImageB", "ImageC", "ImageI"}; + + private static final String PREFIX_FONT = "Fnt"; + private static final String PREFIX_IMAGE = "Img"; + private static final String PREFIX_TRANSPARENCY = "Trp"; + + private final Map fonts; + private final Map images; + private final Map transparencies; + + private final AtomicInteger currentFontId = new AtomicInteger(); + private final AtomicInteger currentImageId = new AtomicInteger(); + private final AtomicInteger currentTransparencyId = new AtomicInteger(); + + public Resources(int id, int version) { + super(id, version, null, null); + fonts = new HashMap<>(); + images = new HashMap<>(); + transparencies = new HashMap<>(); + dict.put(KEY_PROC_SET, VALUE_PROC_SET); + } + + private String getResourceId(Map resources, T resource, + String idPrefix, AtomicInteger idCounter) { + String id = resources.get(resource); + if (id == null) { + id = String.format("%s%d", idPrefix, idCounter.getAndIncrement()); + resources.put(resource, id); + } + return id; + } + + @SuppressWarnings("unchecked") + public String getId(Font font) { + Map> dictEntry = + (Map>) dict.get(KEY_FONT); + if (dictEntry == null) { + dictEntry = new LinkedHashMap<>(); + dict.put(KEY_FONT, dictEntry); + } + font = GraphicsUtils.getPhysicalFont(font); + String resourceId = getResourceId(fonts, font, PREFIX_FONT, currentFontId); + String fontName = font.getPSName(); + // TODO: Determine font encoding (e.g. MacRomanEncoding, MacExpertEncoding, WinAnsiEncoding) + String fontEncoding = "WinAnsiEncoding"; + dictEntry.put(resourceId, DataUtils.map( + new String[]{"Type", "Subtype", "Encoding", "BaseFont"}, + new Object[]{"Font", "TrueType", fontEncoding, fontName} + )); + return resourceId; + } + + @SuppressWarnings("unchecked") + public String getId(PDFObject image) { + Map dictEntry = (Map) dict.get(KEY_IMAGE); + if (dictEntry == null) { + dictEntry = new LinkedHashMap<>(); + dict.put(KEY_IMAGE, dictEntry); + } + String resourceId = getResourceId(images, image, PREFIX_IMAGE, currentImageId); + dictEntry.put(resourceId, image); + return resourceId; + } + + @SuppressWarnings("unchecked") + public String getId(Double transparency) { + Map> dictEntry = + (Map>) dict.get(KEY_TRANSPARENCY); + if (dictEntry == null) { + dictEntry = new LinkedHashMap<>(); + dict.put(KEY_TRANSPARENCY, dictEntry); + } + String resourceId = getResourceId(transparencies, transparency, + PREFIX_TRANSPARENCY, currentTransparencyId); + dictEntry.put(resourceId, DataUtils.map( + new String[]{"Type", "ca", "CA"}, + new Object[]{"ExtGState", transparency, transparency} + )); + return resourceId; + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/SizePayload.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/SizePayload.java new file mode 100644 index 0000000..907963e --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/pdf/SizePayload.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.io.vector.pdf; + +import org.xbib.graphics.io.vector.util.DataUtils; +import java.io.IOException; + +public class SizePayload extends GeneratedPayload { + + private final PDFObject object; + + private final String charset; + + public SizePayload(PDFObject object, String charset, boolean stream) { + super(stream); + this.object = object; + this.charset = charset; + } + + @Override + protected byte[] generatePayload() throws IOException { + object.payload.close(); + String content = DataUtils.format(object.payload.getBytes().length); + return content.getBytes(charset); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGGraphics2D.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGGraphics2D.java new file mode 100644 index 0000000..4ea3c04 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGGraphics2D.java @@ -0,0 +1,27 @@ +package org.xbib.graphics.io.vector.svg; + +import org.xbib.graphics.io.vector.VectorGraphics2D; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.Color; + +/** + * {@code Graphics2D} implementation that saves all operations to a string + * in the Scaled Vector Graphics (SVG) format. + */ +public class SVGGraphics2D extends VectorGraphics2D { + + /** + * Initializes a new VectorGraphics2D pipeline for translating Graphics2D + * commands to SVG data. The document dimensions must be specified as + * parameters. + * + * @param x Left offset. + * @param y Top offset + * @param width Width. + * @param height Height. + */ + public SVGGraphics2D(double x, double y, double width, double height) { + super(new SVGProcessor(), new PageSize(x, y, width, height)); + setColor(Color.BLACK); + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessor.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessor.java new file mode 100644 index 0000000..6ebc4ad --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessor.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.io.vector.svg; + +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.Processor; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter; +import org.xbib.graphics.io.vector.filters.StateChangeGroupingFilter; +import org.xbib.graphics.io.vector.PageSize; + +public class SVGProcessor implements Processor { + public ProcessorResult process(Iterable> commands, PageSize pageSize) { + FillPaintedShapeAsImageFilter shapesAsImages = new FillPaintedShapeAsImageFilter(commands); + Iterable> filtered = new StateChangeGroupingFilter(shapesAsImages); + SVGProcessorResult doc = new SVGProcessorResult(pageSize); + for (Command command : filtered) { + doc.handle(command); + } + doc.close(); + return doc; + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessorResult.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessorResult.java new file mode 100644 index 0000000..c9d6353 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/svg/SVGProcessorResult.java @@ -0,0 +1,547 @@ +package org.xbib.graphics.io.vector.svg; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.xbib.graphics.io.vector.GraphicsState; +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.util.VectorHints; +import org.xbib.graphics.io.vector.commands.AffineTransformCommand; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.CreateCommand; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import org.xbib.graphics.io.vector.commands.DrawImageCommand; +import org.xbib.graphics.io.vector.commands.DrawShapeCommand; +import org.xbib.graphics.io.vector.commands.DrawStringCommand; +import org.xbib.graphics.io.vector.commands.FillShapeCommand; +import org.xbib.graphics.io.vector.commands.Group; +import org.xbib.graphics.io.vector.commands.SetBackgroundCommand; +import org.xbib.graphics.io.vector.commands.SetClipCommand; +import org.xbib.graphics.io.vector.commands.SetColorCommand; +import org.xbib.graphics.io.vector.commands.SetCompositeCommand; +import org.xbib.graphics.io.vector.commands.SetFontCommand; +import org.xbib.graphics.io.vector.commands.SetHintCommand; +import org.xbib.graphics.io.vector.commands.SetPaintCommand; +import org.xbib.graphics.io.vector.commands.SetStrokeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.util.Base64EncodeStream; +import org.xbib.graphics.io.vector.util.DataUtils; +import org.xbib.graphics.io.vector.util.GraphicsUtils; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Image; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +public class SVGProcessorResult implements ProcessorResult { + + private static final String SVG_DOCTYPE_QNAME = "svg"; + + private static final String SVG_DOCTYPE_PUBLIC_ID = "-//W3C//DTD SVG 1.1//EN"; + + private static final String SVG_DOCTYPE_SYSTEM_ID = "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"; + + private static final String SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg"; + + private static final String XLINK_NAMESPACE = "xlink"; + + private static final String XLINK_NAMESPACE_URI = "http://www.w3.org/1999/xlink"; + + private static final String PREFIX_CLIP = "clip"; + + private static final String CHARSET = "UTF-8"; + + private static final double DOTS_PER_MM = 2.834646; // 72 dpi + //private static final double DOTS_PER_MM = 11.811024; // 300 dpi + + /** + * Mapping of stroke endcap values from Java to SVG. + */ + private static final Map STROKE_ENDCAPS = + DataUtils.map(new Integer[]{BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE}, + new String[]{"butt", "round", "square"} + ); + /** + * Mapping of line join values for path drawing from Java to SVG. + */ + private static final Map STROKE_LINEJOIN = + DataUtils.map(new Integer[]{BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL}, + new String[]{"miter", "round", "bevel"} + ); + + private final PageSize pageSize; + + private final Deque states; + + private final Document doc; + + private final Element root; + + private final Map clippingPathElements; + + private Element group; + + private boolean groupAdded; + + private Element defs; + + public SVGProcessorResult(PageSize pageSize) { + this.pageSize = pageSize; + states = new LinkedList<>(); + states.push(new GraphicsState()); + clippingPathElements = new HashMap<>(); + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + docFactory.setValidating(false); + DocumentBuilder docBuilder; + try { + docBuilder = docFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException("Could not create XML builder."); + } + DOMImplementation domImpl = docBuilder.getDOMImplementation(); + DocumentType docType = domImpl.createDocumentType(SVG_DOCTYPE_QNAME, SVG_DOCTYPE_PUBLIC_ID, SVG_DOCTYPE_SYSTEM_ID); + doc = domImpl.createDocument(SVG_NAMESPACE_URI, "svg", docType); + // FIXME: Some XML parsers don't support setting standalone to "false" + try { + doc.setXmlStandalone(false); + } catch (AbstractMethodError e) { + throw new IllegalStateException("Your XML parser does not support standalone XML documents."); + } + root = doc.getDocumentElement(); + initRoot(); + + group = root; + } + + private static void appendStyle(StringBuilder style, String attribute, Object value) { + style.append(attribute).append(":") + .append(DataUtils.format(value)).append(";"); + } + + private static String getOutput(AffineTransform tx) { + StringBuilder out = new StringBuilder(); + if (AffineTransform.getTranslateInstance(tx.getTranslateX(), tx.getTranslateY()).equals(tx)) { + out.append("translate(") + .append(DataUtils.format(tx.getTranslateX())).append(" ") + .append(DataUtils.format(tx.getTranslateY())).append(")"); + } else { + double[] matrix = new double[6]; + tx.getMatrix(matrix); + out.append("matrix(").append(DataUtils.join(" ", matrix)).append(")"); + } + return out.toString(); + } + + private static String getOutput(Color color) { + return String.format((Locale) null, "rgb(%d,%d,%d)", + color.getRed(), color.getGreen(), color.getBlue()); + } + + private static String getOutput(Shape shape) { + StringBuilder out = new StringBuilder(); + PathIterator segments = shape.getPathIterator(null); + double[] coords = new double[6]; + for (int i = 0; !segments.isDone(); i++, segments.next()) { + if (i > 0) { + out.append(" "); + } + int segmentType = segments.currentSegment(coords); + switch (segmentType) { + case PathIterator.SEG_MOVETO: + out.append("M").append(coords[0]).append(",").append(coords[1]); + break; + case PathIterator.SEG_LINETO: + out.append("L").append(coords[0]).append(",").append(coords[1]); + break; + case PathIterator.SEG_CUBICTO: + out.append("C") + .append(coords[0]).append(",").append(coords[1]).append(" ") + .append(coords[2]).append(",").append(coords[3]).append(" ") + .append(coords[4]).append(",").append(coords[5]); + break; + case PathIterator.SEG_QUADTO: + out.append("Q") + .append(coords[0]).append(",").append(coords[1]).append(" ") + .append(coords[2]).append(",").append(coords[3]); + break; + case PathIterator.SEG_CLOSE: + out.append("Z"); + break; + default: + throw new IllegalStateException("Unknown path operation."); + } + } + return out.toString(); + } + + private static String getOutput(Font font) { + StringBuilder out = new StringBuilder(); + if (!GraphicsState.DEFAULT_FONT.getFamily().equals(font.getFamily())) { + String physicalFamily = GraphicsUtils.getPhysicalFont(font).getFamily(); + out.append("font-family:\"").append(physicalFamily).append("\";"); + } + if (font.getSize2D() != GraphicsState.DEFAULT_FONT.getSize2D()) { + out.append("font-size:").append(DataUtils.format(font.getSize2D())).append("px;"); + } + if ((font.getStyle() & Font.ITALIC) != 0) { + out.append("font-style:italic;"); + } + if ((font.getStyle() & Font.BOLD) != 0) { + out.append("font-weight:bold;"); + } + return out.toString(); + } + + private static String getOutput(Image image, boolean lossyAllowed) { + BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image); + String encoded = encodeImage(bufferedImage, "png"); + if (!GraphicsUtils.usesAlpha(bufferedImage) && lossyAllowed) { + String encodedLossy = encodeImage(bufferedImage, "jpeg"); + if (encodedLossy.length() > 0 && encodedLossy.length() < encoded.length()) { + encoded = encodedLossy; + } + } + return encoded; + } + + private static String encodeImage(BufferedImage bufferedImage, String format) { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + Base64EncodeStream encodeStream = new Base64EncodeStream(byteStream); + try { + ImageIO.write(bufferedImage, format, encodeStream); + encodeStream.close(); + String encoded = byteStream.toString(StandardCharsets.ISO_8859_1); + return String.format("data:image/%s;base64,%s", format, encoded); + } catch (IOException e) { + return ""; + } + } + + private GraphicsState getCurrentState() { + return states.peek(); + } + + private void initRoot() { + double x = pageSize.getX(); + double y = pageSize.getY(); + double width = pageSize.getWidth(); + double height = pageSize.getHeight(); + root.setAttribute("xmlns:" + XLINK_NAMESPACE, XLINK_NAMESPACE_URI); + root.setAttribute("version", "1.1"); + root.setAttribute("x", DataUtils.format(x / DOTS_PER_MM) + "mm"); + root.setAttribute("y", DataUtils.format(y / DOTS_PER_MM) + "mm"); + root.setAttribute("width", DataUtils.format(width / DOTS_PER_MM) + "mm"); + root.setAttribute("height", DataUtils.format(height / DOTS_PER_MM) + "mm"); + root.setAttribute("viewBox", DataUtils.join(" ", new double[]{x, y, width, height})); + } + + public void write(OutputStream out) throws IOException { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + try { + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.STANDALONE, "no"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.setOutputProperty(OutputKeys.ENCODING, CHARSET); + transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, + doc.getDoctype().getPublicId()); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, + doc.getDoctype().getSystemId()); + transformer.transform(new DOMSource(doc), new StreamResult(out)); + } catch (TransformerException e) { + throw new IOException(e.getMessage()); + } + } + + @Override + public void close() { + // nothing to do + } + + @Override + public String toString() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + write(out); + return out.toString(CHARSET); + } catch (IOException e) { + return ""; + } + } + + private void newGroup() { + group = doc.createElement("g"); + groupAdded = false; + Shape clip = getCurrentState().getClip(); + if (clip != GraphicsState.DEFAULT_CLIP) { + Element clipElem = getClipElement(clip); + String ref = "url(#" + clipElem.getAttribute("id") + ")"; + group.setAttribute("clip-path", ref); + } + AffineTransform tx = getCurrentState().getTransform(); + if (!GraphicsState.DEFAULT_TRANSFORM.equals(tx)) { + group.setAttribute("transform", getOutput(tx)); + } + } + + private Element getClipElement(Shape clip) { + Element path = clippingPathElements.get(clip.hashCode()); + if (path != null) { + return path; + } + if (defs == null) { + defs = doc.createElement("defs"); + root.insertBefore(defs, root.getFirstChild()); + } + path = doc.createElement("clipPath"); + path.setAttribute("id", PREFIX_CLIP + clip.hashCode()); + Element shape = getElement(clip); + shape.removeAttribute("style"); + path.appendChild(shape); + defs.appendChild(path); + clippingPathElements.put(clip.hashCode(), path); + return path; + } + + private void addToGroup(Element e) { + group.appendChild(e); + if (!groupAdded && group != root) { + root.appendChild(group); + groupAdded = true; + } + } + + public void handle(Command command) { + if (command instanceof Group) { + Group c = (Group) command; + applyStateCommands(c.getValue()); + if (containsGroupCommand(c.getValue())) { + newGroup(); + } + } else if (command instanceof DrawImageCommand) { + DrawImageCommand c = (DrawImageCommand) command; + Element e = getElement(c.getValue(), + c.getX(), c.getY(), c.getWidth(), c.getHeight()); + addToGroup(e); + } else if (command instanceof DrawShapeCommand) { + DrawShapeCommand c = (DrawShapeCommand) command; + Element e = getElement(c.getValue()); + e.setAttribute("style", getStyle(false)); + addToGroup(e); + } else if (command instanceof DrawStringCommand) { + DrawStringCommand c = (DrawStringCommand) command; + Element e = getElement(c.getValue(), c.getX(), c.getY()); + e.setAttribute("style", getStyle(getCurrentState().getFont())); + addToGroup(e); + } else if (command instanceof FillShapeCommand) { + FillShapeCommand c = (FillShapeCommand) command; + Element e = getElement(c.getValue()); + e.setAttribute("style", getStyle(true)); + addToGroup(e); + } + } + + private void applyStateCommands(List> commands) { + for (Command command : commands) { + GraphicsState state = getCurrentState(); + if (command instanceof SetBackgroundCommand) { + SetBackgroundCommand c = (SetBackgroundCommand) command; + state.setBackground(c.getValue()); + } else if (command instanceof SetClipCommand) { + SetClipCommand c = (SetClipCommand) command; + state.setClip(c.getValue()); + } else if (command instanceof SetColorCommand) { + SetColorCommand c = (SetColorCommand) command; + state.setColor(c.getValue()); + } else if (command instanceof SetCompositeCommand) { + SetCompositeCommand c = (SetCompositeCommand) command; + state.setComposite(c.getValue()); + } else if (command instanceof SetFontCommand) { + SetFontCommand c = (SetFontCommand) command; + state.setFont(c.getValue()); + } else if (command instanceof SetPaintCommand) { + SetPaintCommand c = (SetPaintCommand) command; + state.setPaint(c.getValue()); + } else if (command instanceof SetStrokeCommand) { + SetStrokeCommand c = (SetStrokeCommand) command; + state.setStroke(c.getValue()); + } else if (command instanceof SetTransformCommand) { + SetTransformCommand c = (SetTransformCommand) command; + state.setTransform(c.getValue()); + } else if (command instanceof AffineTransformCommand) { + AffineTransformCommand c = (AffineTransformCommand) command; + AffineTransform stateTransform = state.getTransform(); + AffineTransform transformToBeApplied = c.getValue(); + stateTransform.concatenate(transformToBeApplied); + state.setTransform(stateTransform); + } else if (command instanceof SetHintCommand) { + SetHintCommand c = (SetHintCommand) command; + state.getHints().put(c.getKey(), c.getValue()); + } else if (command instanceof CreateCommand) { + try { + states.push((GraphicsState) getCurrentState().clone()); + } catch (CloneNotSupportedException e) { + // ignore + } + } else if (command instanceof DisposeCommand) { + states.pop(); + } + } + } + + private boolean containsGroupCommand(List> commands) { + for (Command command : commands) { + if ((command instanceof SetClipCommand) || + (command instanceof SetTransformCommand) || + (command instanceof AffineTransformCommand)) { + return true; + } + } + return false; + } + + private String getStyle(boolean filled) { + StringBuilder style = new StringBuilder(); + Color color = getCurrentState().getColor(); + String colorOutput = getOutput(color); + double opacity = color.getAlpha() / 255.0; + if (filled) { + appendStyle(style, "fill", colorOutput); + if (color.getAlpha() < 255) { + appendStyle(style, "fill-opacity", opacity); + } + } else { + appendStyle(style, "fill", "none"); + } + if (!filled) { + appendStyle(style, "stroke", colorOutput); + if (color.getAlpha() < 255) { + appendStyle(style, "stroke-opacity", opacity); + } + Stroke stroke = getCurrentState().getStroke(); + if (stroke instanceof BasicStroke) { + BasicStroke bs = (BasicStroke) stroke; + if (bs.getLineWidth() != 1f) { + appendStyle(style, "stroke-width", bs.getLineWidth()); + } + if (bs.getMiterLimit() != 4f) { + appendStyle(style, "stroke-miterlimit", bs.getMiterLimit()); + } + if (bs.getEndCap() != BasicStroke.CAP_BUTT) { + appendStyle(style, "stroke-linecap", STROKE_ENDCAPS.get(bs.getEndCap())); + } + if (bs.getLineJoin() != BasicStroke.JOIN_MITER) { + appendStyle(style, "stroke-linejoin", STROKE_LINEJOIN.get(bs.getLineJoin())); + } + if (bs.getDashArray() != null) { + appendStyle(style, "stroke-dasharray", DataUtils.join(",", bs.getDashArray())); + if (bs.getDashPhase() != 0f) { + appendStyle(style, "stroke-dashoffset", bs.getDashPhase()); + } + } + } + } else { + appendStyle(style, "stroke", "none"); + } + return style.toString(); + } + + private String getStyle(Font font) { + String style = getStyle(true); + if (!GraphicsState.DEFAULT_FONT.equals(font)) { + style += getOutput(font); + } + return style; + } + + private Element getElement(Shape shape) { + Element elem; + if (shape instanceof Line2D) { + Line2D s = (Line2D) shape; + elem = doc.createElement("line"); + elem.setAttribute("x1", DataUtils.format(s.getX1())); + elem.setAttribute("y1", DataUtils.format(s.getY1())); + elem.setAttribute("x2", DataUtils.format(s.getX2())); + elem.setAttribute("y2", DataUtils.format(s.getY2())); + } else if (shape instanceof Rectangle2D) { + Rectangle2D s = (Rectangle2D) shape; + elem = doc.createElement("rect"); + elem.setAttribute("x", DataUtils.format(s.getX())); + elem.setAttribute("y", DataUtils.format(s.getY())); + elem.setAttribute("width", DataUtils.format(s.getWidth())); + elem.setAttribute("height", DataUtils.format(s.getHeight())); + } else if (shape instanceof RoundRectangle2D) { + RoundRectangle2D s = (RoundRectangle2D) shape; + elem = doc.createElement("rect"); + elem.setAttribute("x", DataUtils.format(s.getX())); + elem.setAttribute("y", DataUtils.format(s.getY())); + elem.setAttribute("width", DataUtils.format(s.getWidth())); + elem.setAttribute("height", DataUtils.format(s.getHeight())); + elem.setAttribute("rx", DataUtils.format(s.getArcWidth() / 2.0)); + elem.setAttribute("ry", DataUtils.format(s.getArcHeight() / 2.0)); + } else if (shape instanceof Ellipse2D) { + Ellipse2D s = (Ellipse2D) shape; + elem = doc.createElement("ellipse"); + elem.setAttribute("cx", DataUtils.format(s.getCenterX())); + elem.setAttribute("cy", DataUtils.format(s.getCenterY())); + elem.setAttribute("rx", DataUtils.format(s.getWidth() / 2.0)); + elem.setAttribute("ry", DataUtils.format(s.getHeight() / 2.0)); + } else { + elem = doc.createElement("path"); + elem.setAttribute("d", getOutput(shape)); + } + return elem; + } + + private Element getElement(String text, double x, double y) { + Element elem = doc.createElement("text"); + elem.appendChild(doc.createTextNode(text)); + elem.setAttribute("x", DataUtils.format(x)); + elem.setAttribute("y", DataUtils.format(y)); + return elem; + } + + private Element getElement(Image image, double x, double y, double width, double height) { + Element elem = doc.createElement("image"); + elem.setAttribute("x", DataUtils.format(x)); + elem.setAttribute("y", DataUtils.format(y)); + elem.setAttribute("width", DataUtils.format(width)); + elem.setAttribute("height", DataUtils.format(height)); + elem.setAttribute("preserveAspectRatio", "none"); + boolean lossyAllowed = getCurrentState().getHints().get(VectorHints.KEY_EXPORT) == + VectorHints.VALUE_EXPORT_SIZE; + elem.setAttribute("xlink:href", getOutput(image, lossyAllowed)); + return elem; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ASCII85EncodeStream.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ASCII85EncodeStream.java new file mode 100644 index 0000000..7bb0976 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ASCII85EncodeStream.java @@ -0,0 +1,102 @@ +package org.xbib.graphics.io.vector.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Arrays; + +public class ASCII85EncodeStream extends FilterOutputStream { + private static final Charset ISO88591 = Charset.forName("ISO-8859-1"); + private static final int BASE = 85; + private static final int[] POW_85 = + {BASE * BASE * BASE * BASE, BASE * BASE * BASE, BASE * BASE, BASE, 1}; + private static final char[] CHAR_MAP = + "!\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu" + .toCharArray(); + private final byte[] data; + private final byte[] prefixBytes; + private final byte[] suffixBytes; + private final byte[] encoded; + private boolean closed; + private int dataSize; + private boolean prefixDone; + + public ASCII85EncodeStream(OutputStream out, String prefix, String suffix) { + super(out); + prefixBytes = (prefix != null ? prefix : "").getBytes(ISO88591); + suffixBytes = (suffix != null ? suffix : "").getBytes(ISO88591); + data = new byte[4]; + encoded = new byte[5]; + } + + public ASCII85EncodeStream(OutputStream out) { + this(out, "", "~>"); + } + + private static long toUInt32(byte[] bytes, int size) { + long uint32 = 0L; + for (int i = 0; i < 4 && i < size; i++) { + uint32 |= (bytes[i] & 0xff) << (3 - i) * 8; + } + return toUnsignedInt(uint32); + } + + private static long toUnsignedInt(long x) { + return x & 0x00000000ffffffffL; + } + + @Override + public void write(int b) throws IOException { + if (closed) { + return; + } + if (!prefixDone) { + out.write(prefixBytes); + prefixDone = true; + } + if (dataSize == data.length) { + writeChunk(); + dataSize = 0; + } + data[dataSize++] = (byte) (b & 0xff); + } + + private void writeChunk() throws IOException { + if (dataSize == 0) { + return; + } + long uint32 = toUInt32(data, dataSize); + int padByteCount = data.length - dataSize; + int encodedSize = encodeChunk(uint32, padByteCount); + out.write(encoded, 0, encodedSize); + } + + private int encodeChunk(long uint32, int padByteCount) { + Arrays.fill(encoded, (byte) 0); + if (uint32 == 0L && padByteCount == 0) { + encoded[0] = 'z'; + return 1; + } + int size = encoded.length - padByteCount; + for (int i = 0; i < size; i++) { + encoded[i] = (byte) CHAR_MAP[(int) (uint32 / POW_85[i] % BASE)]; + } + return size; + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + writeChunk(); + + out.write(suffixBytes); + + super.close(); + closed = true; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/AlphaToMaskOp.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/AlphaToMaskOp.java new file mode 100644 index 0000000..98f4979 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/AlphaToMaskOp.java @@ -0,0 +1,101 @@ +package org.xbib.graphics.io.vector.util; + +import java.awt.RenderingHints; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.util.Hashtable; + +public class AlphaToMaskOp implements BufferedImageOp { + private final boolean inverted; + + public AlphaToMaskOp(boolean inverted) { + this.inverted = inverted; + } + + public AlphaToMaskOp() { + this(false); + } + + public boolean isInverted() { + return inverted; + } + + public BufferedImage filter(BufferedImage src, BufferedImage dest) { + ColorModel cm = src.getColorModel(); + + if (dest == null) { + dest = createCompatibleDestImage(src, cm); + } else if (dest.getWidth() != src.getWidth() || dest.getHeight() != src.getHeight()) { + throw new IllegalArgumentException("Source and destination images have different dimensions."); + } else if (dest.getColorModel() != cm) { + throw new IllegalArgumentException("Color models don't match."); + } + + if (cm.hasAlpha()) { + Raster srcRaster = src.getRaster(); + WritableRaster destRaster = dest.getRaster(); + + for (int y = 0; y < srcRaster.getHeight(); y++) { + for (int x = 0; x < srcRaster.getWidth(); x++) { + int argb = cm.getRGB(srcRaster.getDataElements(x, y, null)); + int alpha = argb >>> 24; + if (alpha >= 127 && !isInverted() || alpha < 127 && isInverted()) { + argb |= 0xff000000; + } else { + argb &= 0x00ffffff; + } + destRaster.setDataElements(x, y, cm.getDataElements(argb, null)); + } + } + } + + return dest; + } + + public Rectangle2D getBounds2D(BufferedImage src) { + Rectangle2D bounds = new Rectangle2D.Double(); + bounds.setRect(src.getRaster().getBounds()); + return bounds; + } + + public BufferedImage createCompatibleDestImage(BufferedImage src, + ColorModel destCM) { + if (destCM == null) { + destCM = src.getColorModel(); + } + WritableRaster raster = destCM.createCompatibleWritableRaster( + src.getWidth(), src.getHeight()); + boolean isRasterPremultiplied = destCM.isAlphaPremultiplied(); + Hashtable properties = null; + if (src.getPropertyNames() != null) { + properties = new Hashtable(); + for (String key : src.getPropertyNames()) { + properties.put(key, src.getProperty(key)); + } + } + + BufferedImage bimage = new BufferedImage(destCM, raster, + isRasterPremultiplied, properties); + src.copyData(raster); + return bimage; + } + + public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { + if (dstPt == null) { + dstPt = new Point2D.Double(); + } + dstPt.setLocation(srcPt); + return dstPt; + } + + public RenderingHints getRenderingHints() { + return null; + } + +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/Base64EncodeStream.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/Base64EncodeStream.java new file mode 100644 index 0000000..4c4ae4e --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/Base64EncodeStream.java @@ -0,0 +1,83 @@ +package org.xbib.graphics.io.vector.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +public class Base64EncodeStream extends FilterOutputStream { + private static final int BASE = 64; + private static final int[] POW_64 = + {BASE * BASE * BASE, BASE * BASE, BASE, 1}; + private static final char[] CHAR_MAP = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + .toCharArray(); + private final byte[] data; + private final byte[] encoded; + private boolean closed; + private int dataSize; + + public Base64EncodeStream(OutputStream out) { + super(out); + data = new byte[3]; + encoded = new byte[4]; + } + + private static long toUInt32(byte[] bytes, int size) { + long uint32 = 0L; + int offset = (3 - size) * 8; + for (int i = size - 1; i >= 0; i--) { + uint32 |= (bytes[i] & 0xff) << offset; + offset += 8; + } + return toUnsignedInt(uint32); + } + + private static long toUnsignedInt(long x) { + return x & 0x00000000ffffffffL; + } + + @Override + public void write(int b) throws IOException { + if (closed) { + return; + } + if (dataSize == data.length) { + writeChunk(); + dataSize = 0; + } + data[dataSize++] = (byte) (b & 0xff); + } + + private void writeChunk() throws IOException { + if (dataSize == 0) { + return; + } + long uint32 = toUInt32(data, dataSize); + int padByteCount = data.length - dataSize; + int encodedSize = encodeChunk(uint32, padByteCount); + out.write(encoded, 0, encodedSize); + } + + private int encodeChunk(long uint32, int padByteCount) { + Arrays.fill(encoded, (byte) '='); + int size = encoded.length - padByteCount; + for (int i = 0; i < size; i++) { + encoded[i] = (byte) CHAR_MAP[(int) (uint32 / POW_64[i] % BASE)]; + } + return encoded.length; + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + writeChunk(); + + super.close(); + closed = true; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/DataUtils.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/DataUtils.java new file mode 100644 index 0000000..357aa01 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/DataUtils.java @@ -0,0 +1,240 @@ +package org.xbib.graphics.io.vector.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Abstract class that contains utility functions for working with data + * collections like maps or lists. + */ +public abstract class DataUtils { + /** + * Default constructor that prevents creation of class. + */ + protected DataUtils() { + throw new UnsupportedOperationException(); + } + + /** + * Creates a mapping from two arrays, one with keys, one with values. + * + * @param Data type of the keys. + * @param Data type of the values. + * @param keys Array containing the keys. + * @param values Array containing the values. + * @return Map with keys and values from the specified arrays. + */ + public static Map map(K[] keys, V[] values) { + // Check for valid parameters + if (keys.length != values.length) { + throw new IllegalArgumentException( + "Cannot create a Map: " + + "The number of keys and values differs."); + } + // Fill map with keys and values + Map map = new LinkedHashMap(keys.length); + for (int i = 0; i < keys.length; i++) { + K key = keys[i]; + V value = values[i]; + map.put(key, value); + } + return map; + } + + /** + * Returns a string containing all elements concatenated by a specified + * separator. + * + * @param separator Separator string. + * @param elements List of elements that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, List elements) { + if (elements == null || elements.size() == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(elements.size() * 3); + int i = 0; + for (Object elem : elements) { + if (separator.length() > 0 && i++ > 0) { + sb.append(separator); + } + sb.append(format(elem)); + } + return sb.toString(); + } + + /** + * Returns a string containing all elements concatenated by a specified + * separator. + * + * @param separator Separator string. + * @param elements Array of elements that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, Object[] elements) { + if (elements == null || elements.length == 0) { + return ""; + } + return join(separator, Arrays.asList(elements)); + } + + /** + * Returns a string containing all double numbers concatenated by a + * specified separator. + * + * @param separator Separator string. + * @param elements Array of double numbers that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, double[] elements) { + if (elements == null || elements.length == 0) { + return ""; + } + List list = new ArrayList(elements.length); + for (Double element : elements) { + list.add(element); + } + return join(separator, list); + } + + /** + * Returns a string containing all float numbers concatenated by a + * specified separator. + * + * @param separator Separator string. + * @param elements Array of float numbers that should be concatenated. + * @return a concatenated string. + */ + public static String join(String separator, float[] elements) { + if (elements == null || elements.length == 0) { + return ""; + } + List list = new ArrayList(elements.length); + for (Float element : elements) { + list.add(element); + } + return join(separator, list); + } + + /** + * Returns the largest of all specified values. + * + * @param values Several integer values. + * @return largest value. + */ + public static int max(int... values) { + int max = values[0]; + for (int i = 1; i < values.length; i++) { + if (values[i] > max) { + max = values[i]; + } + } + return max; + } + + /** + * Copies data from an input stream to an output stream using a buffer of + * specified size. + * + * @param in Input stream. + * @param out Output stream. + * @param bufferSize Size of the copy buffer. + * @throws IOException when an error occurs while copying. + */ + public static void transfer(InputStream in, OutputStream out, int bufferSize) + throws IOException { + byte[] buffer = new byte[bufferSize]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + + /** + * Returns a formatted string of the specified number. All trailing zeroes + * or decimal points will be stripped. + * + * @param number Number to convert to a string. + * @return A formatted string. + */ + public static String format(Number number) { + String formatted; + if (number instanceof Double || number instanceof Float) { + formatted = Double.toString(number.doubleValue()) + .replaceAll("\\.0+$", "") + .replaceAll("(\\.[0-9]*[1-9])0+$", "$1"); + } else { + formatted = number.toString(); + } + return formatted; + } + + /** + * Returns a formatted string of the specified object. + * + * @param obj Object to convert to a string. + * @return A formatted string. + */ + public static String format(Object obj) { + if (obj instanceof Number) { + return format((Number) obj); + } else { + return obj.toString(); + } + } + + /** + * Converts an array of {@code float} numbers to a list of {@code Float}s. + * The list will be empty if the array is empty or {@code null}. + * + * @param elements Array of float numbers. + * @return A list with all numbers as {@code Float}. + */ + public static List asList(float[] elements) { + int size = (elements != null) ? elements.length : 0; + List list = new ArrayList<>(size); + if (elements != null) { + for (Float elem : elements) { + list.add(elem); + } + } + return list; + } + + /** + * Converts an array of {@code double} numbers to a list of {@code Double}s. + * The list will be empty if the array is empty or {@code null}. + * + * @param elements Array of double numbers. + * @return A list with all numbers as {@code Double}. + */ + public static List asList(double[] elements) { + int size = (elements != null) ? elements.length : 0; + List list = new ArrayList<>(size); + if (elements != null) { + for (Double elem : elements) { + list.add(elem); + } + } + return list; + } + + /** + * Removes the specified trailing pattern from a string. + * + * @param s string. + * @param substr trailing pattern. + * @return A string without the trailing pattern. + */ + public static String stripTrailing(String s, String substr) { + return s.replaceAll("(" + Pattern.quote(substr) + ")+$", ""); + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FlateEncodeStream.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FlateEncodeStream.java new file mode 100644 index 0000000..7877982 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FlateEncodeStream.java @@ -0,0 +1,11 @@ +package org.xbib.graphics.io.vector.util; + +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; + +public class FlateEncodeStream extends DeflaterOutputStream { + public FlateEncodeStream(OutputStream out) { + super(out); + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FormattingWriter.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FormattingWriter.java new file mode 100644 index 0000000..5ca14d0 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/FormattingWriter.java @@ -0,0 +1,66 @@ +package org.xbib.graphics.io.vector.util; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.OutputStream; + +public class FormattingWriter implements Closeable, Flushable { + private final OutputStream out; + private final String encoding; + private final String eolString; + private long position; + + public FormattingWriter(OutputStream out, String encoding, String eol) { + this.out = out; + this.encoding = encoding; + this.eolString = eol; + } + + public FormattingWriter write(String string) throws IOException { + byte[] bytes = string.getBytes(encoding); + out.write(bytes, 0, bytes.length); + position += bytes.length; + return this; + } + + public FormattingWriter write(Number number) throws IOException { + write(DataUtils.format(number)); + return this; + } + + public FormattingWriter writeln() throws IOException { + write(eolString); + return this; + } + + public FormattingWriter writeln(String string) throws IOException { + write(string); + write(eolString); + return this; + } + + public FormattingWriter writeln(Number number) throws IOException { + write(number); + write(eolString); + return this; + } + + public FormattingWriter format(String format, Object... args) throws IOException { + write(String.format(null, format, args)); + return this; + } + + public void flush() throws IOException { + out.flush(); + } + + public void close() throws IOException { + out.close(); + } + + public long tell() { + return position; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/GraphicsUtils.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/GraphicsUtils.java new file mode 100644 index 0000000..35a0e67 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/GraphicsUtils.java @@ -0,0 +1,411 @@ +package org.xbib.graphics.io.vector.util; + +import java.awt.Font; +import java.awt.Graphics; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.HeadlessException; +import java.awt.Image; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.Arc2D; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.QuadCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.PixelGrabber; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.WritableRaster; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import javax.swing.ImageIcon; + +/** + * Abstract class that contains utility functions for working with graphics. + * For example, this includes font handling. + */ +public abstract class GraphicsUtils { + private static final FontRenderContext FONT_RENDER_CONTEXT = + new FontRenderContext(null, false, true); + private static final String FONT_TEST_STRING = + "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg"; + private static final FontExpressivenessComparator FONT_EXPRESSIVENESS_COMPARATOR = + new FontExpressivenessComparator(); + + /** + * Default constructor that prevents creation of class. + */ + protected GraphicsUtils() { + throw new UnsupportedOperationException(); + } + + /** + * This method returns {@code true} if the specified image has the + * possibility to store transparent pixels. + * Inspired by http://www.exampledepot.com/egs/java.awt.image/HasAlpha.html + * + * @param image Image that should be checked for alpha channel. + * @return {@code true} if the specified image can have transparent pixels, + * {@code false} otherwise + */ + public static boolean hasAlpha(Image image) { + ColorModel cm; + // If buffered image, the color model is readily available + if (image instanceof BufferedImage) { + BufferedImage bimage = (BufferedImage) image; + cm = bimage.getColorModel(); + } else { + // Use a pixel grabber to retrieve the image's color model; + // grabbing a single pixel is usually sufficient + PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); + try { + pg.grabPixels(); + } catch (InterruptedException e) { + return false; + } + // Get the image's color model + cm = pg.getColorModel(); + } + return cm.hasAlpha(); + } + + /** + * This method returns {@code true} if the specified image has at least one + * pixel that is not fully opaque. + * + * @param image Image that should be checked for non-opaque pixels. + * @return {@code true} if the specified image has transparent pixels, + * {@code false} otherwise + */ + public static boolean usesAlpha(Image image) { + if (image == null) { + return false; + } + BufferedImage bimage = toBufferedImage(image); + Raster alphaRaster = bimage.getAlphaRaster(); + if (alphaRaster == null) { + return false; + } + DataBuffer dataBuffer = alphaRaster.getDataBuffer(); + for (int i = 0; i < dataBuffer.getSize(); i++) { + int alpha = dataBuffer.getElem(i); + if (alpha < 255) { + return true; + } + } + return false; + } + + /** + * Converts an arbitrary image to a {@code BufferedImage}. + * + * @param image Image that should be converted. + * @return a buffered image containing the image pixels, or the original + * instance if the image already was of type {@code BufferedImage}. + */ + public static BufferedImage toBufferedImage(RenderedImage image) { + if (image instanceof BufferedImage) { + return (BufferedImage) image; + } + + ColorModel cm = image.getColorModel(); + WritableRaster raster = cm.createCompatibleWritableRaster( + image.getWidth(), image.getHeight()); + boolean isRasterPremultiplied = cm.isAlphaPremultiplied(); + Hashtable properties = null; + if (image.getPropertyNames() != null) { + properties = new Hashtable(); + for (String key : image.getPropertyNames()) { + properties.put(key, image.getProperty(key)); + } + } + + BufferedImage bimage = new BufferedImage(cm, raster, + isRasterPremultiplied, properties); + image.copyData(raster); + return bimage; + } + + /** + * This method returns a buffered image with the contents of an image. + * Taken from http://www.exampledepot.com/egs/java.awt.image/Image2Buf.html + * + * @param image Image to be converted + * @return a buffered image with the contents of the specified image + */ + public static BufferedImage toBufferedImage(Image image) { + if (image instanceof BufferedImage) { + return (BufferedImage) image; + } + // This code ensures that all the pixels in the image are loaded + image = new ImageIcon(image).getImage(); + // Determine if the image has transparent pixels + boolean hasAlpha = hasAlpha(image); + + // Create a buffered image with a format that's compatible with the + // screen + BufferedImage bimage; + GraphicsEnvironment ge = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + try { + // Determine the type of transparency of the new buffered image + int transparency = Transparency.OPAQUE; + if (hasAlpha) { + transparency = Transparency.TRANSLUCENT; + } + // Create the buffered image + GraphicsDevice gs = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gs.getDefaultConfiguration(); + bimage = gc.createCompatibleImage( + image.getWidth(null), image.getHeight(null), transparency); + } catch (HeadlessException e) { + // The system does not have a screen + bimage = null; + } + if (bimage == null) { + // Create a buffered image using the default color model + int type = BufferedImage.TYPE_INT_RGB; + if (hasAlpha) { + type = BufferedImage.TYPE_INT_ARGB; + } + bimage = new BufferedImage( + image.getWidth(null), image.getHeight(null), type); + } + // Copy image to buffered image + Graphics g = bimage.createGraphics(); + // Paint the image onto the buffered image + g.drawImage(image, 0, 0, null); + g.dispose(); + return bimage; + } + + public static Shape clone(Shape shape) { + if (shape == null) { + return null; + } + Shape clone; + if (shape instanceof Line2D) { + clone = (shape instanceof Line2D.Float) ? + new Line2D.Float() : new Line2D.Double(); + ((Line2D) clone).setLine((Line2D) shape); + } else if (shape instanceof Rectangle) { + clone = new Rectangle((Rectangle) shape); + } else if (shape instanceof Rectangle2D) { + clone = (shape instanceof Rectangle2D.Float) ? + new Rectangle2D.Float() : new Rectangle2D.Double(); + ((Rectangle2D) clone).setRect((Rectangle2D) shape); + } else if (shape instanceof RoundRectangle2D) { + clone = (shape instanceof RoundRectangle2D.Float) ? + new RoundRectangle2D.Float() : new RoundRectangle2D.Double(); + ((RoundRectangle2D) clone).setRoundRect((RoundRectangle2D) shape); + } else if (shape instanceof Ellipse2D) { + clone = (shape instanceof Ellipse2D.Float) ? + new Ellipse2D.Float() : new Ellipse2D.Double(); + ((Ellipse2D) clone).setFrame(((Ellipse2D) shape).getFrame()); + } else if (shape instanceof Arc2D) { + clone = (shape instanceof Arc2D.Float) ? + new Arc2D.Float() : new Arc2D.Double(); + ((Arc2D) clone).setArc((Arc2D) shape); + } else if (shape instanceof Polygon) { + Polygon p = (Polygon) shape; + clone = new Polygon(p.xpoints, p.ypoints, p.npoints); + } else if (shape instanceof CubicCurve2D) { + clone = (shape instanceof CubicCurve2D.Float) ? + new CubicCurve2D.Float() : new CubicCurve2D.Double(); + ((CubicCurve2D) clone).setCurve((CubicCurve2D) shape); + } else if (shape instanceof QuadCurve2D) { + clone = (shape instanceof QuadCurve2D.Float) ? + new QuadCurve2D.Float() : new QuadCurve2D.Double(); + ((QuadCurve2D) clone).setCurve((QuadCurve2D) shape); + } else if (shape instanceof Path2D.Float) { + clone = new Path2D.Float(shape); + } else { + clone = new Path2D.Double(shape); + } + return clone; + } + + private static boolean isLogicalFontFamily(String family) { + return (Font.DIALOG.equals(family) || + Font.DIALOG_INPUT.equals(family) || + Font.SANS_SERIF.equals(family) || + Font.SERIF.equals(family) || + Font.MONOSPACED.equals(family)); + } + + /** + * Try to guess physical font from the properties of a logical font, like + * "Dialog", "Serif", "Monospaced" etc. + * + * @param logicalFont Logical font object. + * @param testText Text used to determine font properties. + * @return An object of the first matching physical font. The original font + * object is returned if it was a physical font or no font matched. + */ + public static Font getPhysicalFont(Font logicalFont, String testText) { + String logicalFamily = logicalFont.getFamily(); + if (!isLogicalFontFamily(logicalFamily)) { + return logicalFont; + } + + final TextLayout logicalLayout = + new TextLayout(testText, logicalFont, FONT_RENDER_CONTEXT); + + // Create a list of matches sorted by font expressiveness (in descending order) + Queue physicalFonts = + new PriorityQueue(1, FONT_EXPRESSIVENESS_COMPARATOR); + + Font[] allPhysicalFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); + for (Font physicalFont : allPhysicalFonts) { + String physicalFamily = physicalFont.getFamily(); + // Skip logical fonts + if (isLogicalFontFamily(physicalFamily)) { + continue; + } + + // Derive identical variant of physical font + physicalFont = physicalFont.deriveFont( + logicalFont.getStyle(), logicalFont.getSize2D()); + TextLayout physicalLayout = + new TextLayout(testText, physicalFont, FONT_RENDER_CONTEXT); + + // Compare various properties of physical and logical font + if (physicalLayout.getBounds().equals(logicalLayout.getBounds()) && + physicalLayout.getAscent() == logicalLayout.getAscent() && + physicalLayout.getDescent() == logicalLayout.getDescent() && + physicalLayout.getLeading() == logicalLayout.getLeading() && + physicalLayout.getAdvance() == logicalLayout.getAdvance() && + physicalLayout.getVisibleAdvance() == logicalLayout.getVisibleAdvance()) { + // Store matching font in list + physicalFonts.add(physicalFont); + } + } + + // Return a valid font even when no matching font could be found + if (physicalFonts.isEmpty()) { + return logicalFont; + } + + return physicalFonts.poll(); + } + + public static Font getPhysicalFont(Font logicalFont) { + return getPhysicalFont(logicalFont, FONT_TEST_STRING); + } + + public static BufferedImage getAlphaImage(BufferedImage image) { + WritableRaster alphaRaster = image.getAlphaRaster(); + int width = image.getWidth(); + int height = image.getHeight(); + + ColorModel cm; + WritableRaster raster; + // TODO Handle bitmap masks (work on ImageDataStream is necessary) + /* + if (image.getTransparency() == BufferedImage.BITMASK) { + byte[] arr = {(byte) 0, (byte) 255}; + + cm = new IndexColorModel(1, 2, arr, arr, arr); + raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, + width, height, 1, 1, null); + } else {*/ + ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); + int[] bits = {8}; + cm = new ComponentColorModel(colorSpace, bits, false, true, + Transparency.OPAQUE, DataBuffer.TYPE_BYTE); + raster = cm.createCompatibleWritableRaster(width, height); + //} + + BufferedImage alphaImage = new BufferedImage(cm, raster, false, null); + + int[] alphaValues = new int[image.getWidth() * alphaRaster.getNumBands()]; + for (int y = 0; y < image.getHeight(); y++) { + alphaRaster.getPixels(0, y, image.getWidth(), 1, alphaValues); + // FIXME Don't force 8-bit alpha channel (see TODO above) + if (image.getTransparency() == BufferedImage.BITMASK) { + for (int i = 0; i < alphaValues.length; i++) { + if (alphaValues[i] > 0) { + alphaValues[i] = 255; + } + } + } + alphaImage.getRaster().setPixels(0, y, image.getWidth(), 1, alphaValues); + } + + return alphaImage; + } + + public static boolean equals(Shape shapeA, Shape shapeB) { + PathIterator pathAIterator = shapeA.getPathIterator(null); + PathIterator pathBIterator = shapeB.getPathIterator(null); + + if (pathAIterator.getWindingRule() != pathBIterator.getWindingRule()) { + return false; + } + double[] pathASegment = new double[6]; + double[] pathBSegment = new double[6]; + while (!pathAIterator.isDone()) { + int pathASegmentType = pathAIterator.currentSegment(pathASegment); + int pathBSegmentType = pathBIterator.currentSegment(pathBSegment); + if (pathASegmentType != pathBSegmentType) { + return false; + } + for (int segmentIndex = 0; segmentIndex < pathASegment.length; segmentIndex++) { + if (pathASegment[segmentIndex] != pathBSegment[segmentIndex]) { + return false; + } + } + + pathAIterator.next(); + pathBIterator.next(); + } + // When the iterator of shapeA is done and shapeA equals shapeB, the iterator of shapeB must also be done + if (!pathBIterator.isDone()) { + return false; + } + return true; + } + + private static class FontExpressivenessComparator implements Comparator { + private static final int[] STYLES = { + Font.PLAIN, Font.ITALIC, Font.BOLD, Font.BOLD | Font.ITALIC + }; + + public int compare(Font font1, Font font2) { + if (font1 == font2) { + return 0; + } + Set variantNames1 = new HashSet(); + Set variantNames2 = new HashSet(); + for (int style : STYLES) { + variantNames1.add(font1.deriveFont(style).getPSName()); + variantNames2.add(font2.deriveFont(style).getPSName()); + } + if (variantNames1.size() < variantNames2.size()) { + return 1; + } else if (variantNames1.size() > variantNames2.size()) { + return -1; + } + return font1.getName().compareTo(font2.getName()); + } + } +} diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ImageDataStream.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ImageDataStream.java new file mode 100644 index 0000000..6e98d56 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/ImageDataStream.java @@ -0,0 +1,131 @@ +package org.xbib.graphics.io.vector.util; + +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.Queue; + +public class ImageDataStream extends InputStream { + private final BufferedImage image; + private final int width; + private final int height; + private final Interleaving interleaving; + private final Raster raster; + private final boolean opaque; + private final Queue byteBuffer; + private final int[] sampleValues; + private final int[] sampleSizes; + private int x; + private int y; + + public ImageDataStream(BufferedImage image, Interleaving interleaving) { + this.image = image; + this.interleaving = interleaving; + + width = image.getWidth(); + height = image.getHeight(); + x = -1; + y = 0; + + Raster alphaRaster = image.getAlphaRaster(); + if (interleaving == Interleaving.ALPHA_ONLY) { + raster = alphaRaster; + } else { + raster = image.getRaster(); + } + opaque = alphaRaster == null; + + byteBuffer = new LinkedList(); + sampleValues = new int[raster.getNumBands()]; + sampleSizes = raster.getSampleModel().getSampleSize(); + } + + public BufferedImage getImage() { + return image; + } + + public Interleaving getInterleaving() { + return interleaving; + } + + @Override + public int read() throws IOException { + if (!byteBuffer.isEmpty()) { + return byteBuffer.poll(); + } else { + if (!nextSample()) { + return -1; + } + int bands = sampleValues.length; + if (interleaving == Interleaving.WITHOUT_ALPHA || + interleaving == Interleaving.ALPHA_ONLY) { + if (interleaving == Interleaving.WITHOUT_ALPHA && !opaque) { + // Ignore alpha band + bands--; + } + for (int band = 0; band < bands; band++) { + bufferSampleValue(band); + } + } else { + if (opaque) { + for (int band = 0; band < bands; band++) { + bufferSampleValue(band); + } + } else { + for (int band = 0; band < bands; band++) { + // Fix order to be ARGB instead of RGBA + if (band == 0) { + bufferSampleValue(bands - 1); + } else { + bufferSampleValue(band - 1); + } + } + } + } + if (!byteBuffer.isEmpty()) { + return byteBuffer.poll(); + } else { + return -1; + } + } + } + + private void bufferSampleValue(int band) { + if (sampleSizes[band] < 8) { + int byteValue = sampleValues[band] & 0xFF; + byteBuffer.offer(byteValue); + } else { + int byteCount = sampleSizes[band] / 8; + for (int i = byteCount - 1; i >= 0; i--) { + int byteValue = (sampleValues[band] >> i * 8) & 0xFF; + byteBuffer.offer(byteValue); + } + } + } + + private boolean nextSample() { + if (interleaving == Interleaving.SAMPLE || interleaving == Interleaving.WITHOUT_ALPHA) { + x++; + if (x >= width) { + x = 0; + y++; + } + } + if (x < 0 || x >= width || y < 0 || y >= height) { + return false; + } else { + raster.getPixel(x, y, sampleValues); + return true; + } + } + + public enum Interleaving { + SAMPLE, + ROW, + WITHOUT_ALPHA, + ALPHA_ONLY + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/LineWrapOutputStream.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/LineWrapOutputStream.java new file mode 100644 index 0000000..2518e5d --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/LineWrapOutputStream.java @@ -0,0 +1,40 @@ +package org.xbib.graphics.io.vector.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class LineWrapOutputStream extends FilterOutputStream { + + public static final String STANDARD_EOL = "\r\n"; + + private final int lineWidth; + + private final byte[] eolBytes; + + private int written; + + public LineWrapOutputStream(OutputStream sink, int lineWidth, String eol) { + super(sink); + this.lineWidth = lineWidth; + this.eolBytes = eol.getBytes(); + if (lineWidth <= 0) { + throw new IllegalArgumentException("Width must be at least 0."); + } + } + + public LineWrapOutputStream(OutputStream sink, int lineWidth) { + this(sink, lineWidth, STANDARD_EOL); + } + + @Override + public void write(int b) throws IOException { + if (written == lineWidth) { + out.write(eolBytes); + written = 0; + } + out.write(b); + written++; + } +} + diff --git a/io-vector/src/main/java/org/xbib/graphics/io/vector/util/VectorHints.java b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/VectorHints.java new file mode 100644 index 0000000..6792789 --- /dev/null +++ b/io-vector/src/main/java/org/xbib/graphics/io/vector/util/VectorHints.java @@ -0,0 +1,83 @@ +package org.xbib.graphics.io.vector.util; + +import java.awt.RenderingHints; +import java.util.HashSet; +import java.util.Set; + +public abstract class VectorHints { + public static final Key KEY_EXPORT = new Key(0, "Vector export mode"); + public static final Object VALUE_EXPORT_READABILITY = new Value(KEY_EXPORT, 0, "Maximize readability for humans"); + public static final Object VALUE_EXPORT_QUALITY = new Value(KEY_EXPORT, 1, "Maximize render quality"); + public static final Object VALUE_EXPORT_SIZE = new Value(KEY_EXPORT, 2, "Minimize data size"); + public static final Key KEY_TEXT = new Key(1, "Text export mode"); + public static final Object VALUE_TEXT_DEFAULT = new Value(KEY_TEXT, 0, "Keep text"); + public static final Object VALUE_TEXT_VECTOR = new Value(KEY_TEXT, 1, "Convert text to vector shapes"); + + protected VectorHints() { + throw new UnsupportedOperationException(); + } + + public static class Key extends RenderingHints.Key { + private final String description; + + public Key(int privateKey, String description) { + super(privateKey); + this.description = description; + } + + public int getIndex() { + return intKey(); + } + + @Override + public boolean isCompatibleValue(Object val) { + return val instanceof Value && ((Value) val).isCompatibleKey(this); + } + + @Override + public String toString() { + return description; + } + } + + public static class Value { + private static final Set values = new HashSet(); + private final Key key; + private final int index; + private final String description; + + public Value(Key key, int index, String description) { + this.key = key; + this.index = index; + this.description = description; + register(this); + } + + private synchronized static void register(Value value) { + String id = value.getId(); + if (values.contains(id)) { + throw new ExceptionInInitializerError( + "Duplicate index: " + value.getIndex()); + } + values.add(id); + } + + public boolean isCompatibleKey(RenderingHints.Key key) { + return this.key == key; + } + + public int getIndex() { + return index; + } + + public String getId() { + return key.getIndex() + ":" + getIndex(); + } + + @Override + public String toString() { + return description; + } + } +} + diff --git a/io-vector/src/test/java/org/xbib/graphics/io/filters/AbsoluteToRelativeTransformsFilterTest.java b/io-vector/src/test/java/org/xbib/graphics/io/filters/AbsoluteToRelativeTransformsFilterTest.java new file mode 100644 index 0000000..77d4d1f --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/filters/AbsoluteToRelativeTransformsFilterTest.java @@ -0,0 +1,90 @@ +package org.xbib.graphics.io.filters; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.CreateCommand; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.commands.TransformCommand; +import org.xbib.graphics.io.vector.commands.TranslateCommand; +import org.xbib.graphics.io.vector.filters.AbsoluteToRelativeTransformsFilter; +import java.awt.geom.AffineTransform; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class AbsoluteToRelativeTransformsFilterTest { + + @Test + public void testAbsoluteAndRelativeTransformsIdentical() { + AffineTransform absoluteTransform = new AffineTransform(); + absoluteTransform.rotate(42.0); + absoluteTransform.translate(4.0, 2.0); + List> commands = wrapCommands( + new SetTransformCommand(absoluteTransform) + ); + + AbsoluteToRelativeTransformsFilter filter = new AbsoluteToRelativeTransformsFilter(commands); + + filter.next(); + AffineTransform relativeTransform = ((TransformCommand) filter.next()).getValue(); + assertThat(relativeTransform, is(absoluteTransform)); + } + + @Test + public void testTranslateCorrect() { + AffineTransform absoluteTransform = new AffineTransform(); + absoluteTransform.scale(2.0, 2.0); + absoluteTransform.translate(4.2, 4.2); // (8.4, 8.4) + List> commands = wrapCommands( + new TranslateCommand(4.0, 2.0), + new SetTransformCommand(absoluteTransform) + ); + + AbsoluteToRelativeTransformsFilter filter = new AbsoluteToRelativeTransformsFilter(commands); + + TransformCommand transformCommand = null; + while (filter.hasNext()) { + Command filteredCommand = filter.next(); + if (filteredCommand instanceof TransformCommand) { + transformCommand = (TransformCommand) filteredCommand; + } + } + AffineTransform relativeTransform = transformCommand.getValue(); + assertThat(relativeTransform.getTranslateX(), is(4.4)); + assertThat(relativeTransform.getTranslateY(), is(6.4)); + } + + @Test + public void testRelativeTransformAfterDispose() { + AffineTransform absoluteTransform = new AffineTransform(); + absoluteTransform.rotate(42.0); + absoluteTransform.translate(4.0, 2.0); + List> commands = wrapCommands( + new CreateCommand(null), + new TransformCommand(absoluteTransform), + new DisposeCommand(null), + new SetTransformCommand(absoluteTransform) + ); + + AbsoluteToRelativeTransformsFilter filter = new AbsoluteToRelativeTransformsFilter(commands); + TransformCommand lastTransformCommand = null; + for (Command filteredCommand : filter) { + if (filteredCommand instanceof TransformCommand) { + lastTransformCommand = (TransformCommand) filteredCommand; + } + } + assertThat(lastTransformCommand.getValue(), is(absoluteTransform)); + } + + private List> wrapCommands(Command... commands) { + List> commandList = new ArrayList>(commands.length + 2); + commandList.add(new CreateCommand(null)); + commandList.addAll(Arrays.asList(commands)); + commandList.add(new DisposeCommand(null)); + return commandList; + } +} + diff --git a/io-vector/src/test/java/org/xbib/graphics/io/filters/FillPaintedShapeAsImageFilterTest.java b/io-vector/src/test/java/org/xbib/graphics/io/filters/FillPaintedShapeAsImageFilterTest.java new file mode 100644 index 0000000..b442ad0 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/filters/FillPaintedShapeAsImageFilterTest.java @@ -0,0 +1,53 @@ +package org.xbib.graphics.io.filters; + +import static org.hamcrest.CoreMatchers.any; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.DrawImageCommand; +import org.xbib.graphics.io.vector.commands.FillShapeCommand; +import org.xbib.graphics.io.vector.commands.RotateCommand; +import org.xbib.graphics.io.vector.commands.SetPaintCommand; +import org.xbib.graphics.io.vector.filters.FillPaintedShapeAsImageFilter; +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.geom.Rectangle2D; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class FillPaintedShapeAsImageFilterTest { + + @Test + public void testFillShapeReplacedWithDrawImage() { + List> commands = new LinkedList>(); + commands.add(new SetPaintCommand(new GradientPaint(0.0f, 0.0f, Color.BLACK, 100.0f, 100.0f, Color.WHITE))); + commands.add(new RotateCommand(10.0, 4.0, 2.0)); + commands.add(new FillShapeCommand(new Rectangle2D.Double(10.0, 10.0, 100.0, 100.0))); + + FillPaintedShapeAsImageFilter filter = new FillPaintedShapeAsImageFilter(commands); + + assertThat(filter, hasItem(any(DrawImageCommand.class))); + assertThat(filter, not(hasItem(any(FillShapeCommand.class)))); + } + + @Test + public void testFillShapeNotReplacedWithoutPaintCommand() { + List> commands = new LinkedList>(); + commands.add(new RotateCommand(10.0, 4.0, 2.0)); + commands.add(new FillShapeCommand(new Rectangle2D.Double(10.0, 10.0, 100.0, 100.0))); + + FillPaintedShapeAsImageFilter filter = new FillPaintedShapeAsImageFilter(commands); + + Iterator> filterIterator = filter.iterator(); + for (Command command : commands) { + assertEquals(command, filterIterator.next()); + } + assertFalse(filterIterator.hasNext()); + } +} + diff --git a/io-vector/src/test/java/org/xbib/graphics/io/filters/FilterTest.java b/io-vector/src/test/java/org/xbib/graphics/io/filters/FilterTest.java new file mode 100644 index 0000000..f0f7281 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/filters/FilterTest.java @@ -0,0 +1,99 @@ +package org.xbib.graphics.io.filters; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.DrawShapeCommand; +import org.xbib.graphics.io.vector.commands.SetColorCommand; +import org.xbib.graphics.io.vector.commands.SetStrokeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.filters.Filter; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + + +public class FilterTest { + + @Test + public void filterNone() { + List> stream = new LinkedList>(); + stream.add(new SetColorCommand(Color.BLACK)); + stream.add(new SetStrokeCommand(new BasicStroke(1f))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + stream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + + Iterator> unfiltered = stream.iterator(); + + Filter filtered = new Filter(stream) { + @Override + protected List> filter(Command command) { + return Collections.singletonList(command); + } + }; + + while (filtered.hasNext() || unfiltered.hasNext()) { + Command expected = unfiltered.next(); + Command result = filtered.next(); + assertEquals(expected, result); + } + } + + @Test + public void filterAll() { + List> stream = new LinkedList>(); + stream.add(new SetColorCommand(Color.BLACK)); + stream.add(new SetStrokeCommand(new BasicStroke(1f))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + stream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + + Iterator> unfiltered = stream.iterator(); + + Filter filtered = new Filter(stream) { + @Override + protected List> filter(Command command) { + return null; + } + }; + assertTrue(unfiltered.hasNext()); + assertFalse(filtered.hasNext()); + } + + @Test + public void duplicate() { + List> stream = new LinkedList>(); + stream.add(new SetColorCommand(Color.BLACK)); + stream.add(new SetStrokeCommand(new BasicStroke(1f))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + stream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + stream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + + Iterator> unfiltered = stream.iterator(); + + Filter filtered = new Filter(stream) { + @Override + protected List> filter(Command command) { + return Arrays.asList(command, command); + } + }; + + while (filtered.hasNext() || unfiltered.hasNext()) { + Command expected = unfiltered.next(); + Command result1 = filtered.next(); + Command result2 = filtered.next(); + assertEquals(expected, result1); + assertEquals(expected, result2); + } + } +} + diff --git a/io-vector/src/test/java/org/xbib/graphics/io/filters/GroupingFilterTest.java b/io-vector/src/test/java/org/xbib/graphics/io/filters/GroupingFilterTest.java new file mode 100644 index 0000000..16edfd5 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/filters/GroupingFilterTest.java @@ -0,0 +1,56 @@ +package org.xbib.graphics.io.filters; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.DrawShapeCommand; +import org.xbib.graphics.io.vector.commands.Group; +import org.xbib.graphics.io.vector.commands.SetColorCommand; +import org.xbib.graphics.io.vector.commands.SetStrokeCommand; +import org.xbib.graphics.io.vector.commands.SetTransformCommand; +import org.xbib.graphics.io.vector.commands.StateCommand; +import org.xbib.graphics.io.vector.filters.Filter; +import org.xbib.graphics.io.vector.filters.GroupingFilter; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class GroupingFilterTest { + + @Test + public void filtered() { + List> resultStream = new LinkedList>(); + resultStream.add(new SetColorCommand(Color.BLACK)); + resultStream.add(new SetStrokeCommand(new BasicStroke(1f))); + resultStream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 10.0, 11.0))); + resultStream.add(new SetTransformCommand(AffineTransform.getTranslateInstance(5.0, 5.0))); + resultStream.add(new DrawShapeCommand(new Line2D.Double(0.0, 1.0, 5.0, 6.0))); + List> expectedStream = new LinkedList>(); + Iterator> resultCloneIterator = resultStream.iterator(); + Group group1 = new Group(); + group1.add(resultCloneIterator.next()); + group1.add(resultCloneIterator.next()); + expectedStream.add(group1); + expectedStream.add(resultCloneIterator.next()); + Group group2 = new Group(); + group2.add(resultCloneIterator.next()); + expectedStream.add(group2); + expectedStream.add(resultCloneIterator.next()); + Iterator> expectedIterator = expectedStream.iterator(); + Filter resultIterator = new GroupingFilter(resultStream) { + @Override + protected boolean isGrouped(Command command) { + return command instanceof StateCommand; + } + }; + while (resultIterator.hasNext() || expectedIterator.hasNext()) { + Command result = resultIterator.next(); + Command expected = expectedIterator.next(); + assertEquals(expected, result); + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/GraphicsStateTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/GraphicsStateTest.java new file mode 100644 index 0000000..0155651 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/GraphicsStateTest.java @@ -0,0 +1,55 @@ +package org.xbib.graphics.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import org.junit.jupiter.api.Test; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; + +public class GraphicsStateTest { + + @Test + public void testInitialStateIsEqualToGraphics2D() { + BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) image.getGraphics(); + GraphicsState state = new GraphicsState(); + assertEquals(state.getBackground(), g2d.getBackground()); + assertEquals(state.getColor(), g2d.getColor()); + assertEquals(state.getClip(), g2d.getClip()); + assertEquals(state.getComposite(), g2d.getComposite()); + assertEquals(state.getFont(), g2d.getFont()); + assertEquals(state.getPaint(), g2d.getPaint()); + assertEquals(state.getStroke(), g2d.getStroke()); + assertEquals(state.getTransform(), g2d.getTransform()); + } + + @Test + public void testEquals() { + GraphicsState state1 = new GraphicsState(); + state1.setBackground(Color.WHITE); + state1.setColor(Color.BLACK); + state1.setClip(new Rectangle2D.Double(0, 0, 10, 10)); + GraphicsState state2 = new GraphicsState(); + state2.setBackground(Color.WHITE); + state2.setColor(Color.BLACK); + state2.setClip(new Rectangle2D.Double(0, 0, 10, 10)); + assertEquals(state1, state2); + state2.setTransform(AffineTransform.getTranslateInstance(5, 5)); + assertNotEquals(state2, state1); + } + + @Test + public void testClone() throws CloneNotSupportedException { + GraphicsState state = new GraphicsState(); + state.setBackground(Color.BLUE); + state.setColor(Color.GREEN); + state.setClip(new Rectangle2D.Double(2, 3, 4, 2)); + GraphicsState clone = (GraphicsState) state.clone(); + assertNotSame(state, clone); + assertEquals(state, clone); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/TestUtils.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/TestUtils.java new file mode 100644 index 0000000..12840f5 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/TestUtils.java @@ -0,0 +1,265 @@ +package org.xbib.graphics.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class TestUtils { + + protected TestUtils() { + throw new UnsupportedOperationException(); + } + + public static void assertTemplateEquals(Template expected, Template actual) { + Iterator itExpected = expected.iterator(); + Iterator itActual = actual.iterator(); + while (itExpected.hasNext() && itActual.hasNext()) { + Object lineExpected = itExpected.next(); + Object lineActual = itActual.next(); + if (lineExpected == null) { + continue; + } + assertTrue(lineActual instanceof String, + String.format("Line is of type %s, expected String.", lineActual.getClass())); + if (lineExpected instanceof String) { + assertEquals(lineExpected, lineActual); + } else if (lineExpected instanceof Pattern) { + Pattern expectedPattern = (Pattern) lineExpected; + Matcher matcher = expectedPattern.matcher((String) lineActual); + assertTrue(matcher.matches(), + String.format("Line didn't match pattern.\nExpected: \"%s\"\nActual: \"%s\"", matcher.pattern(), lineActual)); + } + } + assertEquals(expected.size(), actual.size(), "Wrong number of lines in template."); + } + + private static List parseXML(String xmlString) { + XMLFragment frag; + List fragments = new LinkedList(); + int startPos = 0; + while ((frag = XMLFragment.parse(xmlString, startPos)) != null) { + fragments.add(frag); + startPos = frag.matchEnd; + } + return fragments; + } + + public static void assertXMLEquals(String expected, String actual) { + List expectedFrags = parseXML(expected); + List actualFrags = parseXML(actual); + + Iterator itExpected = expectedFrags.iterator(); + Iterator itActual = actualFrags.iterator(); + while (itExpected.hasNext() && itActual.hasNext()) { + XMLFragment expectedFrag = itExpected.next(); + XMLFragment actualFrag = itActual.next(); + assertEquals(expectedFrag, actualFrag); + } + + assertEquals(expectedFrags.size(), actualFrags.size()); + } + + @SuppressWarnings("serial") + public static class Template extends LinkedList { + public Template(Object[] lines) { + Collections.addAll(this, lines); + } + + public Template(Template[] templates) { + for (Template template : templates) { + addAll(template); + } + } + } + + public static class XMLFragment { + + private static final Pattern CDATA = Pattern.compile("\\s*"); + + private static final Pattern COMMENT = Pattern.compile("\\s*"); + + private static final Pattern TAG_BEGIN = Pattern.compile("\\s*<(/|\\?|!)?\\s*([^\\s>/\\?]+)"); + + private static final Pattern TAG_END = Pattern.compile("\\s*(/|\\?)?>"); + + private static final Pattern TAG_ATTRIBUTE = Pattern.compile("\\s*([^\\s>=]+)=(\"[^\"]*\"|'[^']*')"); + + private static final Pattern DOCTYPE_PART = Pattern.compile("\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)"); + + public final String name; + + public final FragmentType type; + + public final Map attributes; + + public final int matchStart; + + public final int matchEnd; + + public XMLFragment(String name, FragmentType type, Map attributes, + int matchStart, int matchEnd) { + this.name = name; + this.type = type; + this.attributes = Collections.unmodifiableMap( + new TreeMap(attributes)); + this.matchStart = matchStart; + this.matchEnd = matchEnd; + } + + public static XMLFragment parse(String xmlString, int matchStart) { + Map attrs = new IdentityHashMap(); + + Matcher cdataMatch = CDATA.matcher(xmlString); + cdataMatch.region(matchStart, xmlString.length()); + if (cdataMatch.lookingAt()) { + attrs.put("value", cdataMatch.group(1)); + return new XMLFragment("", FragmentType.CDATA, attrs, matchStart, cdataMatch.end()); + } + + Matcher commentMatch = COMMENT.matcher(xmlString); + commentMatch.region(matchStart, xmlString.length()); + if (commentMatch.lookingAt()) { + attrs.put("value", commentMatch.group(1).trim()); + return new XMLFragment("", FragmentType.COMMENT, attrs, matchStart, commentMatch.end()); + } + + Matcher beginMatch = TAG_BEGIN.matcher(xmlString); + beginMatch.region(matchStart, xmlString.length()); + if (!beginMatch.lookingAt()) { + return null; + } + int matchEndPrev = beginMatch.end(); + + String modifiers = beginMatch.group(1); + String name = beginMatch.group(2); + boolean endTag = "/".equals(modifiers); + boolean declarationStart = "?".equals(modifiers); + boolean doctype = "!".equals(modifiers) && "DOCTYPE".equals(name); + + if (doctype) { + int partNo = 0; + while (true) { + Matcher attrMatch = DOCTYPE_PART.matcher(xmlString); + attrMatch.region(matchEndPrev, xmlString.length()); + if (!attrMatch.lookingAt()) { + break; + } + matchEndPrev = attrMatch.end(); + + String partValue = attrMatch.group(1); + if (partValue.startsWith("\"") || partValue.startsWith("'")) { + partValue = partValue.substring(1, partValue.length() - 1); + } + + String partId = String.format("doctype %02d", partNo++); + attrs.put(partId, partValue); + } + } else { + while (true) { + Matcher attrMatch = TAG_ATTRIBUTE.matcher(xmlString); + attrMatch.region(matchEndPrev, xmlString.length()); + if (!attrMatch.lookingAt()) { + break; + } + matchEndPrev = attrMatch.end(); + + String attrName = attrMatch.group(1); + String attrValue = attrMatch.group(2); + attrValue = attrValue.substring(1, attrValue.length() - 1); + attrs.put(attrName, attrValue); + } + } + + Matcher endMatch = TAG_END.matcher(xmlString); + endMatch.region(matchEndPrev, xmlString.length()); + if (!endMatch.lookingAt()) { + throw new AssertionError(String.format("No tag end found: %s", xmlString.substring(0, matchEndPrev))); + } + matchEndPrev = endMatch.end(); + + modifiers = endMatch.group(1); + boolean emptyElement = "/".equals(modifiers); + boolean declarationEnd = "?".equals(modifiers); + + FragmentType type = FragmentType.START_TAG; + if (endTag) { + type = FragmentType.END_TAG; + } else if (emptyElement) { + type = FragmentType.EMPTY_ELEMENT; + } else if (declarationStart && declarationEnd) { + type = FragmentType.DECLARATION; + } else if (doctype) { + type = FragmentType.DOCTYPE; + } + + return new XMLFragment(name, type, attrs, matchStart, matchEndPrev); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof XMLFragment)) { + return false; + } + XMLFragment frag = (XMLFragment) o; + if (!type.equals(frag.type) || !name.equals(frag.name)) { + return false; + } + Iterator> itThis = attributes.entrySet().iterator(); + Iterator> itFrag = frag.attributes.entrySet().iterator(); + while (itThis.hasNext() && itFrag.hasNext()) { + Map.Entry attrThis = itThis.next(); + Map.Entry attrFrag = itFrag.next(); + if (!attrThis.getKey().equals(attrFrag.getKey()) || + !attrThis.getValue().equals(attrFrag.getValue())) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return type.hashCode() ^ attributes.hashCode(); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder("<"); + if (FragmentType.END_TAG.equals(type)) { + s.append("/"); + } else if (FragmentType.DECLARATION.equals(type)) { + s.append("?"); + } + + if (FragmentType.DOCTYPE.equals(type)) { + s.append("!").append(name); + for (String partValue : attributes.values()) { + s.append(" ").append(partValue); + } + } else { + s.append(name); + for (Map.Entry attr : attributes.entrySet()) { + s.append(" ").append(attr.getKey()).append("=\"").append(attr.getValue()).append("\""); + } + } + if (FragmentType.DECLARATION.equals(type)) { + s.append("?"); + } + s.append(">"); + return s.toString(); + } + + public enum FragmentType { + START_TAG, END_TAG, EMPTY_ELEMENT, CDATA, + DECLARATION, DOCTYPE, COMMENT + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/TestUtilsTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/TestUtilsTest.java new file mode 100644 index 0000000..c22f4f6 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/TestUtilsTest.java @@ -0,0 +1,189 @@ +package org.xbib.graphics.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.TestUtils.XMLFragment; + + +public class TestUtilsTest { + + @Test + public void testParseXmlStartTag() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + xmlString = "<" + xmlTagName + ">"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.START_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + xmlString = "< " + xmlTagName + " >"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.START_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlEndTag() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.END_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.END_TAG, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlEmptyElement() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + + xmlString = "<" + xmlTagName + "/>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.EMPTY_ELEMENT, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "< " + xmlTagName + " />"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals(XMLFragment.FragmentType.EMPTY_ELEMENT, frag.type); + assertTrue(frag.attributes.isEmpty()); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlCDATA() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.CDATA, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "foo bar]]>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.CDATA, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlDeclaration() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("xml", frag.name); + assertEquals(XMLFragment.FragmentType.DECLARATION, frag.type); + assertEquals("1.0", frag.attributes.get("version")); + assertEquals("UTF-8", frag.attributes.get("encoding")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlDoctype() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(XMLFragment.FragmentType.DOCTYPE, frag.type); + assertEquals("html", frag.attributes.get("doctype 00")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(XMLFragment.FragmentType.DOCTYPE, frag.type); + assertEquals("svg", frag.attributes.get("doctype 00")); + assertEquals("PUBLIC", frag.attributes.get("doctype 01")); + assertEquals("-//W3C//DTD SVG 1.1 Tiny//EN", frag.attributes.get("doctype 02")); + assertEquals("http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd", frag.attributes.get("doctype 03")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXmlComment() throws Exception { + String xmlString; + XMLFragment frag; + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.COMMENT, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = ""; + frag = XMLFragment.parse(xmlString, 0); + assertEquals("", frag.name); + assertEquals(XMLFragment.FragmentType.COMMENT, frag.type); + assertEquals("foo bar", frag.attributes.get("value")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } + + @Test + public void testParseXMLAttributesTag() throws Exception { + String xmlTagName = "foo:bar.baz_tag"; + String xmlString; + XMLFragment frag; + + xmlString = "<" + xmlTagName + " foo='bar'>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals("bar", frag.attributes.get("foo")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "<" + xmlTagName + " foo=\"bar\">"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals("bar", frag.attributes.get("foo")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + + xmlString = "<" + xmlTagName + " foo=\"bar\" baz='qux'>"; + frag = XMLFragment.parse(xmlString, 0); + assertEquals(xmlTagName, frag.name); + assertEquals("bar", frag.attributes.get("foo")); + assertEquals("qux", frag.attributes.get("baz")); + assertEquals(0, frag.matchStart); + assertEquals(xmlString.length(), frag.matchEnd); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/VectorGraphics2DTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/VectorGraphics2DTest.java new file mode 100644 index 0000000..cd6c93d --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/VectorGraphics2DTest.java @@ -0,0 +1,56 @@ +package org.xbib.graphics.io.vector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.commands.CreateCommand; +import org.xbib.graphics.io.vector.commands.DisposeCommand; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Iterator; + +public class VectorGraphics2DTest { + + @Test + public void testEmptyVectorGraphics2DStartsWithCreateCommand() { + VectorGraphics2D g = new VectorGraphics2D(); + Iterable> commands = g.getCommands(); + Iterator> commandIterator = commands.iterator(); + assertTrue(commandIterator.hasNext()); + Command firstCommand = commandIterator.next(); + assertTrue(firstCommand instanceof CreateCommand); + assertEquals(g, ((CreateCommand) firstCommand).getValue()); + } + + @Test + public void testCreateEmitsCreateCommand() { + VectorGraphics2D g = new VectorGraphics2D(); + VectorGraphics2D g2 = (VectorGraphics2D) g.create(); + assertNotNull(g2); + CreateCommand g2CreateCommand = null; + for (Command g2Command : g2.getCommands()) { + if (g2Command instanceof CreateCommand) { + g2CreateCommand = (CreateCommand) g2Command; + } + } + assertNotNull(g2CreateCommand); + assertEquals(g2, g2CreateCommand.getValue()); + } + + @Test + public void testDisposeCommandEmitted() { + VectorGraphics2D g = new VectorGraphics2D(); + g.setColor(Color.RED); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setColor(Color.BLUE); + g2.dispose(); + Iterable> commands = g.getCommands(); + Command lastCommand = null; + for (Command command : commands) { + lastCommand = command; + } + assertTrue(lastCommand instanceof DisposeCommand); + assertEquals(Color.BLUE, ((DisposeCommand) lastCommand).getValue().getColor()); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/eps/EPSProcessorTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/eps/EPSProcessorTest.java new file mode 100644 index 0000000..29bb7da --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/eps/EPSProcessorTest.java @@ -0,0 +1,71 @@ +package org.xbib.graphics.io.vector.eps; + +import static org.xbib.graphics.io.vector.TestUtils.Template; +import static org.xbib.graphics.io.vector.TestUtils.assertTemplateEquals; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.PageSize; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class EPSProcessorTest { + + private static final String EOL = "\n"; + + private static final Object[] HEADER = { + "%!PS-Adobe-3.0 EPSF-3.0", + "%%BoundingBox: 0 28 57 114", + "%%HiResBoundingBox: 0.0 28.34645669291339 56.69291338582678 113.38582677165356", + "%%LanguageLevel: 3", + "%%Pages: 1", + "%%EndComments", + "%%Page: 1 1", + "/M /moveto load def", + "/L /lineto load def", + "/C /curveto load def", + "/Z /closepath load def", + "/RL /rlineto load def", + "/rgb /setrgbcolor load def", + "/rect { /height exch def /width exch def /y exch def /x exch def x y M width 0 RL 0 height RL width neg 0 RL } bind def", + "/ellipse { /endangle exch def /startangle exch def /ry exch def /rx exch def /y exch def /x exch def /savematrix matrix currentmatrix def x y translate rx ry scale 0 0 1 startangle endangle arcn savematrix setmatrix } bind def", + "/imgdict { /datastream exch def /hasdata exch def /decodeScale exch def /bits exch def /bands exch def /imgheight exch def /imgwidth exch def << /ImageType 1 /Width imgwidth /Height imgheight /BitsPerComponent bits /Decode [bands {0 decodeScale} repeat] ", + "/ImageMatrix [imgwidth 0 0 imgheight 0 0] hasdata { /DataSource datastream } if >> } bind def", + "/latinize { /fontName exch def /fontNameNew exch def fontName findfont 0 dict copy begin /Encoding ISOLatin1Encoding def fontNameNew /FontName def currentdict end dup /FID undef fontNameNew exch definefont pop } bind def", + Pattern.compile("/\\S+?Lat /\\S+ latinize /\\S+?Lat 12.0 selectfont"), + "gsave", + "clipsave", + "/DeviceRGB setcolorspace", + "0 85.03937007874016 translate", + "2.834645669291339 -2.834645669291339 scale", + "/basematrix matrix currentmatrix def", + "%%EOF" + }; + private static final PageSize PAGE_SIZE = new PageSize(0.0, 10.0, 20.0, 30.0); + + private final EPSProcessor processor = new EPSProcessor(); + + private final List> commands = new LinkedList<>(); + + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + private String process(Command... commands) throws IOException { + Collections.addAll(this.commands, commands); + ProcessorResult processed = processor.process(this.commands, PAGE_SIZE); + processed.write(bytes); + return bytes.toString(StandardCharsets.ISO_8859_1); + } + + @Test + public void envelopeForEmptyDocument() throws IOException { + String result = process(); + Template actual = new Template(result.split(EOL)); + Template expected = new Template(HEADER); + assertTemplateEquals(expected, actual); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/pdf/PDFProcessorTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/pdf/PDFProcessorTest.java new file mode 100644 index 0000000..f8d1e3e --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/pdf/PDFProcessorTest.java @@ -0,0 +1,109 @@ +package org.xbib.graphics.io.vector.pdf; + +import static org.xbib.graphics.io.vector.TestUtils.Template; +import static org.xbib.graphics.io.vector.TestUtils.assertTemplateEquals; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.PageSize; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class PDFProcessorTest { + private static final String EOL = "\n"; + private static final String HEADER = "%PDF-1.4"; + private static final String FOOTER = "%%EOF"; + private static final PageSize PAGE_SIZE = new PageSize(0.0, 10.0, 20.0, 30.0); + + private final PDFProcessor processor = new PDFProcessor(false); + private final List> commands = new LinkedList<>(); + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + private String process(Command... commands) throws IOException { + Collections.addAll(this.commands, commands); + processor.process(this.commands, PAGE_SIZE).write(bytes); + return bytes.toString(StandardCharsets.ISO_8859_1); + } + + @Test + public void envelopeForEmptyDocument() throws IOException { + String result = process(); + Template actual = new Template(result.split(EOL)); + Template expected = new Template(new Object[]{ + HEADER, + "1 0 obj", + "<<", + "/Type /Catalog", + "/Pages 2 0 R", + ">>", + "endobj", + "2 0 obj", + "<<", + "/Type /Pages", + "/Kids [3 0 R]", + "/Count 1", + ">>", + "endobj", + "3 0 obj", + "<<", + "/Type /Page", + "/Parent 2 0 R", + "/MediaBox [0 28.34645669291339 56.69291338582678 85.03937007874016]", + "/Contents 4 0 R", + "/Resources 6 0 R", + ">>", + "endobj", + "4 0 obj", + "<<", + "/Length 5 0 R", + ">>", + "stream", + "q", + "1 1 1 rg 1 1 1 RG", + "2.834645669291339 0 0 -2.834645669291339 0 85.03937007874016 cm", + "/Fnt0 12.0 Tf", + "Q", + "endstream", + "endobj", + "5 0 obj", + "100", + "endobj", + "6 0 obj", + "<<", + "/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]", + "/Font <<", + "/Fnt0 <<", + "/Type /Font", + "/Subtype /TrueType", + "/Encoding /WinAnsiEncoding", + Pattern.compile("/BaseFont /\\S+"), + ">>", + ">>", + ">>", + "endobj", + "xref", + "0 7", + "0000000000 65535 f ", + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + Pattern.compile("\\d{10} 00000 n "), + "trailer", + "<<", + "/Size 7", + "/Root 1 0 R", + ">>", + "startxref", + Pattern.compile("[1-9]\\d*"), + FOOTER + }); + assertTemplateEquals(expected, actual); + } +} + diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/svg/SVGProcessorTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/svg/SVGProcessorTest.java new file mode 100644 index 0000000..83f7be8 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/svg/SVGProcessorTest.java @@ -0,0 +1,68 @@ +package org.xbib.graphics.io.vector.svg; + +import static org.xbib.graphics.io.vector.TestUtils.assertXMLEquals; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.io.vector.ProcessorResult; +import org.xbib.graphics.io.vector.Command; +import org.xbib.graphics.io.vector.commands.DrawShapeCommand; +import org.xbib.graphics.io.vector.commands.FillShapeCommand; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.geom.Rectangle2D; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class SVGProcessorTest { + private static final String EOL = "\n"; + private static final String HEADER = + "" + EOL + + "" + EOL + + "" + EOL; + private static final String FOOTER = ""; + private static final PageSize PAGE_SIZE = new PageSize(0.0, 10.0, 20.0, 30.0); + + private final SVGProcessor processor = new SVGProcessor(); + private final List> commands = new LinkedList<>(); + private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + private String process(Command... commands) throws IOException { + Collections.addAll(this.commands, commands); + ProcessorResult processed = processor.process(this.commands, PAGE_SIZE); + processed.write(bytes); + return bytes.toString(StandardCharsets.UTF_8); + } + + @Test + public void envelopeForEmptyDocument() throws Exception { + String result = process(); + String expected = HEADER.replaceAll(">$", "/>"); + assertXMLEquals(expected, result); + } + + @Test + public void drawShapeBlack() throws Exception { + String result = process( + new DrawShapeCommand(new Rectangle2D.Double(1, 2, 3, 4)) + ); + String expected = + HEADER + EOL + + " " + EOL + + FOOTER; + assertXMLEquals(expected, result); + } + + @Test + public void fillShapeBlack() throws Exception { + String result = process( + new FillShapeCommand(new Rectangle2D.Double(1, 2, 3, 4)) + ); + String expected = + HEADER + EOL + + " " + EOL + + FOOTER; + assertXMLEquals(expected, result); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/util/ASCII85EncodeStreamTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/ASCII85EncodeStreamTest.java new file mode 100644 index 0000000..958de3b --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/ASCII85EncodeStreamTest.java @@ -0,0 +1,53 @@ +package org.xbib.graphics.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class ASCII85EncodeStreamTest { + + private static void assertEncodedString(String expected, String input) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(input.getBytes()); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + OutputStream encodeStream = new ASCII85EncodeStream(outStream); + byte[] buffer = new byte[1024]; + for (int count = inputStream.read(buffer); count >= 0; count = inputStream.read(buffer)) { + encodeStream.write(buffer, 0, count); + } + encodeStream.close(); + String encoded = outStream.toString(StandardCharsets.ISO_8859_1); + assertEquals(expected, encoded); + } + + @Test + public void testEncoding() throws IOException { + String input = + "Man is distinguished, not only by his reason, but by this singular passion " + + "from other animals, which is a lust of the mind, that by a perseverance of " + + "delight in the continued and indefatigable generation of knowledge, exceeds " + + "the short vehemence of any carnal pleasure."; + + String expected = + "9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKFCj@.4Gp$d7F!,L7@<6@)/0JDEF@3BB/F*&OCAfu2/AKYi(DIb:@FD,*)" + + "+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIal(DIduD.RTpAKYo'+CT/5+Cei#" + + "DII?(E,9)oF*2M7/c~>"; + + assertEncodedString(expected, input); + } + + @Test + public void testPadding() throws IOException { + assertEncodedString("/c~>", "."); + } + + @Test + public void testEmpty() throws IOException { + assertEncodedString("~>", ""); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/util/Base64EncodeStreamTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/Base64EncodeStreamTest.java new file mode 100644 index 0000000..8edea6a --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/Base64EncodeStreamTest.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class Base64EncodeStreamTest { + + private static void assertEncodedString(String expected, String input) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(input.getBytes()); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + OutputStream encodeStream = new Base64EncodeStream(outStream); + byte[] buffer = new byte[1024]; + for (int count = inputStream.read(buffer); count >= 0; count = inputStream.read(buffer)) { + encodeStream.write(buffer, 0, count); + } + encodeStream.close(); + String encoded = outStream.toString(StandardCharsets.ISO_8859_1); + assertEquals(expected, encoded); + } + + @Test + public void testEncoding() throws IOException { + String input = + "Man is distinguished, not only by his reason, but by this singular passion " + + "from other animals, which is a lust of the mind, that by a perseverance of " + + "delight in the continued and indefatigable generation of knowledge, exceeds " + + "the short vehemence of any carnal pleasure."; + + String expected = + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz" + + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg" + + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo" + + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="; + + assertEncodedString(expected, input); + } + + @Test + public void testPadding() throws IOException { + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3VyZS4=", "any carnal pleasure."); + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3VyZQ==", "any carnal pleasure"); + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3Vy", "any carnal pleasur"); + assertEncodedString("YW55IGNhcm5hbCBwbGVhc3U=", "any carnal pleasu"); + assertEncodedString("YW55IGNhcm5hbCBwbGVhcw==", "any carnal pleas"); + + assertEncodedString("cGxlYXN1cmUu", "pleasure."); + assertEncodedString("bGVhc3VyZS4=", "leasure."); + assertEncodedString("ZWFzdXJlLg==", "easure."); + assertEncodedString("YXN1cmUu", "asure."); + assertEncodedString("c3VyZS4=", "sure."); + } + + @Test + public void testEmpty() throws IOException { + assertEncodedString("", ""); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/util/DataUtilsTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/DataUtilsTest.java new file mode 100644 index 0000000..cb8ce55 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/DataUtilsTest.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +public class DataUtilsTest { + + @Test + public void stripTrailingSpaces() { + String result = DataUtils.stripTrailing(" foo bar! ", " "); + String expected = " foo bar!"; + assertEquals(expected, result); + } + + @Test + public void stripTrailingSpacesInMultilineString() { + String result = DataUtils.stripTrailing(" foo bar! \n ", " "); + String expected = " foo bar! \n"; + assertEquals(expected, result); + } + + @Test + public void stripComplexSubstring() { + String result = DataUtils.stripTrailing("+bar foo+bar+bar+bar", "+bar"); + String expected = "+bar foo"; + assertEquals(expected, result); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/vector/util/GraphicsUtilsTest.java b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/GraphicsUtilsTest.java new file mode 100644 index 0000000..3672382 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/vector/util/GraphicsUtilsTest.java @@ -0,0 +1,142 @@ +package org.xbib.graphics.io.vector.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import java.awt.Font; +import java.awt.Image; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Toolkit; +import java.awt.geom.Arc2D; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.QuadCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.FilteredImageSource; +import java.awt.image.RGBImageFilter; +import java.lang.reflect.InvocationTargetException; + +/** + * On Linux, the package msttcorefonts need to be installed. + */ +public class GraphicsUtilsTest { + private static final double DELTA = 1e-15; + + private static void assertShapeEquals(Shape expected, Shape actual) { + if ((expected instanceof Line2D) && (actual instanceof Line2D)) { + assertEquals(((Line2D) expected).getP1(), ((Line2D) actual).getP1()); + assertEquals(((Line2D) expected).getP2(), ((Line2D) actual).getP2()); + } else if ((expected instanceof Polygon) && (actual instanceof Polygon)) { + int n = ((Polygon) actual).npoints; + assertEquals(((Polygon) expected).npoints, n); + if (n > 0) { + assertArrayEquals(((Polygon) expected).xpoints, ((Polygon) actual).xpoints); + assertArrayEquals(((Polygon) expected).ypoints, ((Polygon) actual).ypoints); + } + } else if ((expected instanceof QuadCurve2D) && (actual instanceof QuadCurve2D)) { + assertEquals(((QuadCurve2D) expected).getP1(), ((QuadCurve2D) actual).getP1()); + assertEquals(((QuadCurve2D) expected).getCtrlPt(), ((QuadCurve2D) actual).getCtrlPt()); + assertEquals(((QuadCurve2D) expected).getP2(), ((QuadCurve2D) actual).getP2()); + } else if ((expected instanceof CubicCurve2D) && (actual instanceof CubicCurve2D)) { + assertEquals(((CubicCurve2D) expected).getP1(), ((CubicCurve2D) actual).getP1()); + assertEquals(((CubicCurve2D) expected).getCtrlP1(), ((CubicCurve2D) actual).getCtrlP1()); + assertEquals(((CubicCurve2D) expected).getCtrlP2(), ((CubicCurve2D) actual).getCtrlP2()); + assertEquals(((CubicCurve2D) expected).getP2(), ((CubicCurve2D) actual).getP2()); + } else if ((expected instanceof Path2D) && (actual instanceof Path2D)) { + PathIterator itExpected = expected.getPathIterator(null); + PathIterator itActual = actual.getPathIterator(null); + double[] segmentExpected = new double[6]; + double[] segmentActual = new double[6]; + for (; !itExpected.isDone() || !itActual.isDone(); itExpected.next(), itActual.next()) { + assertEquals(itExpected.getWindingRule(), itActual.getWindingRule()); + itExpected.currentSegment(segmentExpected); + itActual.currentSegment(segmentActual); + assertArrayEquals(segmentExpected, segmentActual, DELTA); + } + } else { + assertEquals(expected, actual); + } + } + + @Test + public void testToBufferedImage() { + Image[] images = { + new BufferedImage(320, 240, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB), + Toolkit.getDefaultToolkit().createImage(new FilteredImageSource( + new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB).getSource(), + new RGBImageFilter() { + @Override + public int filterRGB(int x, int y, int rgb) { + return rgb & 0xff; + } + } + )) + }; + + for (Image image : images) { + BufferedImage bimage = GraphicsUtils.toBufferedImage(image); + assertNotNull(bimage); + assertEquals(BufferedImage.class, bimage.getClass()); + assertEquals(image.getWidth(null), bimage.getWidth()); + assertEquals(image.getHeight(null), bimage.getHeight()); + } + } + + @Test + public void testHasAlpha() { + Image image; + image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_ARGB); + assertTrue(GraphicsUtils.hasAlpha(image)); + image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB); + assertFalse(GraphicsUtils.hasAlpha(image)); + } + + @Test + public void testPhysicalFont() { + Font font = new Font("Monospaced", Font.PLAIN, 12); + assertNotSame(font, GraphicsUtils.getPhysicalFont(font)); + } + + @Test + public void testCloneShape() + throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + Class[] shapeClasses = { + Line2D.Float.class, + Line2D.Double.class, + Rectangle.class, + Rectangle2D.Float.class, + Rectangle2D.Double.class, + RoundRectangle2D.Float.class, + RoundRectangle2D.Double.class, + Ellipse2D.Float.class, + Ellipse2D.Double.class, + Arc2D.Float.class, + Arc2D.Double.class, + Polygon.class, + CubicCurve2D.Float.class, + CubicCurve2D.Double.class, + QuadCurve2D.Float.class, + QuadCurve2D.Double.class, + Path2D.Float.class, + Path2D.Double.class + }; + for (Class shapeClass : shapeClasses) { + Shape shape = (Shape) shapeClass.getDeclaredConstructor().newInstance(); + Shape clone = GraphicsUtils.clone(shape); + assertNotNull(clone); + assertShapeEquals(shape, clone); + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/AbstractTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/AbstractTest.java new file mode 100644 index 0000000..2dc575a --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/AbstractTest.java @@ -0,0 +1,86 @@ +package org.xbib.graphics.io.visual; + +import org.xbib.graphics.io.vector.eps.EPSGraphics2D; +import org.xbib.graphics.io.vector.pdf.PDFGraphics2D; +import org.xbib.graphics.io.vector.svg.SVGGraphics2D; +import org.xbib.graphics.io.vector.PageSize; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import javax.imageio.ImageIO; + +public abstract class AbstractTest { + + private final PageSize pageSize; + + private final BufferedImage reference; + + private final EPSGraphics2D epsGraphics; + + private final PDFGraphics2D pdfGraphics; + + private final SVGGraphics2D svgGraphics; + + public AbstractTest() throws IOException { + int width = 150; + int height = 150; + pageSize = new PageSize(0.0, 0.0, width, height); + epsGraphics = new EPSGraphics2D(0, 0, width, height); + draw(epsGraphics); + pdfGraphics = new PDFGraphics2D(0, 0, width, height); + draw(pdfGraphics); + svgGraphics = new SVGGraphics2D(0, 0, width, height); + draw(svgGraphics); + reference = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D referenceGraphics = reference.createGraphics(); + referenceGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + referenceGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + referenceGraphics.setBackground(new Color(1f, 1f, 1f, 0f)); + referenceGraphics.clearRect(0, 0, reference.getWidth(), reference.getHeight()); + referenceGraphics.setColor(Color.BLACK); + draw(referenceGraphics); + File referenceImage = File.createTempFile(getClass().getName() + ".reference", ".png"); + referenceImage.deleteOnExit(); + ImageIO.write(reference, "png", referenceImage); + } + + public abstract void draw(Graphics2D g); + + public PageSize getPageSize() { + return pageSize; + } + + public BufferedImage getReference() { + return reference; + } + + public InputStream getEPS() { + try { + return new ByteArrayInputStream(epsGraphics.getBytes()); + } catch (IOException e) { + return null; + } + } + + public InputStream getPDF() { + try { + return new ByteArrayInputStream(pdfGraphics.getBytes()); + } catch (IOException e) { + return null; + } + } + + public InputStream getSVG() { + try { + return new ByteArrayInputStream(svgGraphics.getBytes()); + } catch (IOException e) { + return null; + } + } + +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/CharacterTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/CharacterTest.java new file mode 100644 index 0000000..8f8849f --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/CharacterTest.java @@ -0,0 +1,44 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Graphics2D; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class CharacterTest extends AbstractTest { + + public CharacterTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + double w = getPageSize().getWidth(); + double h = getPageSize().getHeight(); + Charset latin1 = StandardCharsets.ISO_8859_1; + CharsetEncoder latin1Encoder = latin1.newEncoder(); + List charactersInCharset = new ArrayList<>(); + for (char char_ = Character.MIN_VALUE; char_ < Character.MAX_VALUE; char_++) { + String javaString = String.valueOf(char_); + if (latin1Encoder.canEncode(char_)) { + charactersInCharset.add(javaString); + } + } + final int colCount = (int) Math.ceil(Math.sqrt(charactersInCharset.size())); + final int rowCount = colCount; + double tileWidth = w / colCount; + double tileHeight = h / rowCount; + int charIndex = 0; + for (double y = 0.0; y < h; y += tileHeight) { + for (double x = 0.0; x < w; x += tileWidth) { + String c = charactersInCharset.get(charIndex); + double tileCenterX = x + tileWidth / 2.0; + double tileCenterY = y + tileHeight / 2.0; + g.drawString(c, (float) tileCenterX, (float) tileCenterY); + charIndex++; + } + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/ClippingTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/ClippingTest.java new file mode 100644 index 0000000..8589b81 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/ClippingTest.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.io.IOException; + +public class ClippingTest extends AbstractTest { + + public ClippingTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + double w = getPageSize().getWidth(); + double h = getPageSize().getHeight(); + + AffineTransform txOrig = g.getTransform(); + g.translate(w / 2.0, h / 2.0); + + g.setClip(new Ellipse2D.Double(-0.6 * w / 2.0, -h / 2.0, 0.6 * w, h)); + for (double x = -w / 2.0; x < w / 2.0; x += 4.0) { + g.draw(new Line2D.Double(x, -h / 2.0, x, h / 2.0)); + } + + g.rotate(Math.toRadians(-90.0)); + g.clip(new Ellipse2D.Double(-0.6 * w / 2.0, -h / 2.0, 0.6 * w, h)); + for (double x = -h / 2.0; x < h / 2.0; x += 4.0) { + g.draw(new Line2D.Double(x, -w / 2.0, x, w / 2.0)); + } + + g.setTransform(txOrig); + g.setClip(null); + g.draw(new Line2D.Double(0.0, 0.0, w, h)); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/ColorTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/ColorTest.java new file mode 100644 index 0000000..f8e1630 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/ColorTest.java @@ -0,0 +1,32 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +public class ColorTest extends AbstractTest { + + public ColorTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + final float wPage = (float) getPageSize().getWidth(); + final float hPage = (float) getPageSize().getHeight(); + final float wTile = Math.min(wPage / 15f, hPage / 15f); + final float hTile = wTile; + float w = wPage - wTile; + float h = hPage - hTile; + for (float y = (hPage - h) / 2f; y < h; y += hTile) { + float yRel = y / h; + for (float x = (wPage - w) / 2f; x < w; x += wTile) { + float xRel = x / w; + Color c = Color.getHSBColor(yRel, 1f, 1f); + int alpha = 255 - (int) (xRel * 255f); + g.setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha)); + g.fill(new Rectangle2D.Float(x, y, wTile, hTile)); + } + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/EmptyFileTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/EmptyFileTest.java new file mode 100644 index 0000000..c984b07 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/EmptyFileTest.java @@ -0,0 +1,14 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Graphics2D; +import java.io.IOException; + +public class EmptyFileTest extends AbstractTest { + + public EmptyFileTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/FontTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/FontTest.java new file mode 100644 index 0000000..3af641c --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/FontTest.java @@ -0,0 +1,50 @@ +package org.xbib.graphics.io.visual; + +import org.xbib.graphics.io.vector.GraphicsState; +import java.awt.Font; +import java.awt.Graphics2D; +import java.io.IOException; + +public class FontTest extends AbstractTest { + + public FontTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + final int tileCountH = 4; + final int tileCountV = 8; + final double wTile = getPageSize().getWidth() / tileCountH; + final double hTile = getPageSize().getHeight() / tileCountV; + final double xOrigin = (getPageSize().getWidth() - tileCountH * wTile) / 2.0; + final double yOrigin = (getPageSize().getHeight() - tileCountV * hTile) / 2.0; + double x = xOrigin; + double y = yOrigin; + + final float[] sizes = { + GraphicsState.DEFAULT_FONT.getSize2D(), GraphicsState.DEFAULT_FONT.getSize2D() / 2f + }; + final String[] names = { + GraphicsState.DEFAULT_FONT.getName(), Font.SERIF, Font.MONOSPACED, "Monospaced" + }; + final int[] styles = { + Font.PLAIN, Font.ITALIC, Font.BOLD, Font.BOLD | Font.ITALIC + }; + + for (float size : sizes) { + for (String name : names) { + for (int style : styles) { + Font font = new Font(name, style, 10).deriveFont(size); + g.setFont(font); + g.drawString("vg2d", (float) x, (float) y); + + x += wTile; + if (x >= tileCountH * wTile) { + x = xOrigin; + y += hTile; + } + } + } + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/ImageTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/ImageTest.java new file mode 100644 index 0000000..21d9ae6 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/ImageTest.java @@ -0,0 +1,30 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; + +public class ImageTest extends AbstractTest { + + public ImageTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + BufferedImage image = new BufferedImage(4, 3, BufferedImage.TYPE_INT_ARGB); + Graphics2D gImage = (Graphics2D) image.getGraphics(); + gImage.setPaint(new GradientPaint( + new Point2D.Double(0.0, 0.0), Color.RED, + new Point2D.Double(3.0, 2.0), Color.BLUE) + ); + gImage.fill(new Rectangle2D.Double(0.0, 0.0, 4.0, 3.0)); + g.drawImage(image, 0, 0, (int) getPageSize().getWidth(), (int) (0.5 * getPageSize().getHeight()), null); + g.rotate(-10.0 / 180.0 * Math.PI, 2.0, 1.5); + g.drawImage(image, (int) (0.1 * getPageSize().getWidth()), (int) (0.6 * getPageSize().getHeight()), + (int) (0.33 * getPageSize().getWidth()), (int) (0.33 * getPageSize().getHeight()), null); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/PaintTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/PaintTest.java new file mode 100644 index 0000000..2271cba --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/PaintTest.java @@ -0,0 +1,40 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +public class PaintTest extends AbstractTest { + + public PaintTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + // Draw multiple rotated rectangles + final int steps = 25; + final int cols = 5; + final int rows = steps / cols; + final double tileWidth = getPageSize().getWidth() / cols; + final double tileHeight = getPageSize().getHeight() / rows; + g.translate(tileWidth / 2, tileHeight / 2); + final double rectWidth = tileWidth * 0.8; + final double rectHeight = tileHeight * 0.8; + Rectangle2D rect = new Rectangle2D.Double(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight); + g.setPaint(new GradientPaint(0f, (float) (-rectHeight / 2), Color.RED, 0f, (float) (rectHeight / 2), Color.BLUE)); + for (int i = 0; i < steps; i++) { + AffineTransform txOld = g.getTransform(); + AffineTransform tx = new AffineTransform(txOld); + int col = i % 5; + int row = i / 5; + tx.translate(col * tileWidth, row * tileHeight); + tx.rotate(i * Math.toRadians(360.0 / steps)); + g.setTransform(tx); + g.fill(rect); + g.setTransform(txOld); + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/ShapesTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/ShapesTest.java new file mode 100644 index 0000000..446790b --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/ShapesTest.java @@ -0,0 +1,131 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.QuadCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.io.IOException; + +public class ShapesTest extends AbstractTest { + + public ShapesTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + final int tileCountH = 4; + final int tileCountV = 6; + final double wTile = getPageSize().getWidth() / tileCountH; + final double hTile = getPageSize().getHeight() / tileCountV; + final double xOrigin = (getPageSize().getWidth() - tileCountH * wTile) / 2.0; + final double yOrigin = (getPageSize().getHeight() - tileCountV * hTile) / 2.0; + double x = xOrigin; + double y = yOrigin; + + g.draw(new Line2D.Double(x, y, x + 0.8 * wTile, y + 0.6 * hTile)); + x += wTile; + g.draw(new QuadCurve2D.Double(x, y, x + 0.8 * wTile, y, x + 0.8 * wTile, y + 0.6 * hTile)); + x += wTile; + g.draw(new CubicCurve2D.Double(x, y, x + 0.8 * wTile, y, x, y + 0.6 * hTile, x + 0.8 * wTile, y + 0.6 * hTile)); + + x = xOrigin; + y += hTile; + g.fill(new Rectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + g.draw(new Rectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + + g.fill(new RoundRectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 0.2 * wTile, 0.2 * hTile)); + x += wTile; + g.draw(new RoundRectangle2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 0.2 * wTile, 0.2 * hTile)); + x += wTile; + + + x = xOrigin; + y += hTile; + g.fill(new Ellipse2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + g.draw(new Ellipse2D.Double(x, y, 0.8 * wTile, 0.6 * hTile)); + x += wTile; + + g.fill(new Polygon( + new int[]{(int) (x), (int) (x + 0.8 * wTile / 2.0), (int) (x + 0.8 * wTile)}, + new int[]{(int) (y + 0.6 * hTile), (int) (y), (int) (y + 0.6 * hTile)}, + 3 + )); + x += wTile; + g.draw(new Polygon( + new int[]{(int) (x), (int) (x + 0.8 * wTile / 2.0), (int) (x + 0.8 * wTile)}, + new int[]{(int) (y + 0.6 * hTile), (int) (y), (int) (y + 0.6 * hTile)}, + 3 + )); + + + x = xOrigin; + y += hTile; + g.fill(new Arc2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 110, 320, Arc2D.PIE)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.8 * wTile, 0.6 * hTile, 110, 320, Arc2D.PIE)); + x += wTile; + g.fill(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.CHORD)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.CHORD)); + + + x = xOrigin; + y += hTile; + g.fill(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.OPEN)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.OPEN)); + x += wTile; + g.fill(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.PIE)); + x += wTile; + g.draw(new Arc2D.Double(x, y, 0.6 * hTile, 0.8 * wTile, 10, 320, Arc2D.PIE)); + + + x = xOrigin; + y += hTile; + + final Path2D path1 = new Path2D.Double(); + path1.moveTo(0.00, 0.00); + path1.lineTo(0.33, 1.00); + path1.lineTo(0.67, 0.00); + path1.quadTo(0.33, 0.00, 0.33, 0.50); + path1.quadTo(0.33, 1.00, 0.67, 1.00); + path1.quadTo(1.00, 1.00, 1.00, 0.50); + path1.lineTo(0.67, 0.50); + path1.transform(AffineTransform.getScaleInstance(0.8 * wTile, 0.6 * hTile)); + + path1.transform(AffineTransform.getTranslateInstance(x, y)); + g.fill(path1); + x += wTile; + path1.transform(AffineTransform.getTranslateInstance(wTile, 0.0)); + g.draw(path1); + x += wTile; + + final Path2D path2 = new Path2D.Double(); + path2.moveTo(0.0, 0.4); + path2.curveTo(0.0, 0.3, 0.0, 0.0, 0.2, 0.0); + path2.curveTo(0.3, 0.0, 0.4, 0.1, 0.4, 0.3); + path2.curveTo(0.4, 0.5, 0.2, 0.8, 0.0, 1.0); + path2.lineTo(0.6, 1.0); + path2.lineTo(0.6, 0.0); + path2.curveTo(0.8, 0.0, 1.0, 0.2, 1.0, 0.5); + path2.curveTo(1.0, 0.6, 1.0, 0.8, 0.9, 0.9); + path2.closePath(); + path2.transform(AffineTransform.getScaleInstance(0.8 * wTile, 0.6 * hTile)); + + path2.transform(AffineTransform.getTranslateInstance(x, y)); + g.fill(path2); + x += wTile; + path2.transform(AffineTransform.getTranslateInstance(wTile, 0.0)); + g.draw(path2); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/StrokeTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/StrokeTest.java new file mode 100644 index 0000000..df0be49 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/StrokeTest.java @@ -0,0 +1,94 @@ +package org.xbib.graphics.io.visual; + +import java.awt.BasicStroke; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; +import java.io.IOException; + +public class StrokeTest extends AbstractTest { + + private static final Stroke[] strokes = { + // Width + new BasicStroke(0.0f), + new BasicStroke(0.5f), + new BasicStroke(1.0f), + new BasicStroke(2.0f), + // Cap + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER), + new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER), + new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER), + null, + // Join + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND), + null, + // Miter limit + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 3f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f), + // Dash pattern + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{1f}, 0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{1f, 1f}, 0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f, 1f}, 0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f, 4f, 1f}, 0f), + // Dash phase + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 0.5f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 1.0f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 1.5f), + new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[]{3f, 1f}, 2.5f), + }; + + public StrokeTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + final int tileCountH = 4; + final int tileCountV = 6; + final double wTile = getPageSize().getWidth() / tileCountH; + final double hTile = getPageSize().getHeight() / tileCountV; + final double xOrigin = (getPageSize().getWidth() - tileCountH * wTile) / 2.0; + final double yOrigin = (getPageSize().getHeight() - tileCountV * hTile) / 2.0; + + final Path2D path = new Path2D.Double(); + path.moveTo(0.00, 0.00); + path.lineTo(0.33, 1.00); + path.lineTo(0.67, 0.00); + path.quadTo(0.33, 0.00, 0.33, 0.50); + path.quadTo(0.33, 1.00, 0.67, 1.00); + path.quadTo(1.00, 1.00, 1.00, 0.50); + path.lineTo(0.67, 0.50); + path.moveTo(1.0, 0.4); + path.curveTo(1.0, 0.3, 1.0, 0.0, 1.2, 0.0); + path.curveTo(1.3, 0.0, 1.4, 0.1, 1.4, 0.3); + path.curveTo(1.4, 0.5, 1.2, 0.8, 1.0, 1.0); + path.lineTo(1.6, 1.0); + path.lineTo(1.6, 0.0); + path.curveTo(1.8, 0.0, 2.0, 0.2, 2.0, 0.5); + path.curveTo(2.0, 0.6, 2.0, 0.8, 1.9, 0.9); + + path.transform(AffineTransform.getScaleInstance(0.8 * wTile / 2.0, 0.6 * hTile)); + + double x = xOrigin; + double y = yOrigin; + for (Stroke stroke : strokes) { + if (stroke != null) { + Path2D p = new Path2D.Double(path); + p.transform(AffineTransform.getTranslateInstance(x, y)); + + g.setStroke(stroke); + g.draw(p); + } + + x += wTile; + if (x >= tileCountH * wTile) { + x = xOrigin; + y += hTile; + } + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/SwingExportTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/SwingExportTest.java new file mode 100644 index 0000000..f23f5a3 --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/SwingExportTest.java @@ -0,0 +1,27 @@ +package org.xbib.graphics.io.visual; + +import java.awt.BorderLayout; +import java.awt.Graphics2D; +import java.io.IOException; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JSlider; + +public class SwingExportTest extends AbstractTest { + + public SwingExportTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + JFrame frame = new JFrame(); + frame.getContentPane().add(new JButton("Hello Swing!"), BorderLayout.CENTER); + frame.getContentPane().add(new JSlider(), BorderLayout.NORTH); + frame.setSize(200, 250); + //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + frame.setVisible(true); + frame.printAll(g); + frame.setVisible(false); + frame.dispose(); + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/TestBrowser.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/TestBrowser.java new file mode 100644 index 0000000..0a8ab8b --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/TestBrowser.java @@ -0,0 +1,253 @@ +package org.xbib.graphics.io.visual; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.ListSelectionModel; +import javax.swing.WindowConstants; + +@SuppressWarnings("serial") +public class TestBrowser extends JFrame { + + private final List testCases; + + private final ImageComparisonPanel imageComparisonPanel; + + private AbstractTest testCase; + + public TestBrowser() throws IOException { + super("Test browser"); + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setSize(1024, 768); + testCases = new ArrayList<>(); + testCases.add(new ColorTest()); + testCases.add(new StrokeTest()); + testCases.add(new ShapesTest()); + testCases.add(new FontTest()); + testCases.add(new CharacterTest()); + testCases.add(new EmptyFileTest()); + testCases.add(new ImageTest()); + testCases.add(new ClippingTest()); + testCases.add(new PaintTest()); + testCases.add(new SwingExportTest()); + testCases.add(new TransformTest()); + final JList testList = new JList<>(testCases.toArray()); + testList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + testList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + String testName = value.getClass().getSimpleName(); + return super.getListCellRendererComponent(list, testName, index, isSelected, cellHasFocus); + } + }); + testList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + int index = testList.getSelectedIndex(); + if (index < 0) { + return; + } + AbstractTest test = testCases.get(index); + testCase = test; + setTestCase(test); + } + }); + getContentPane().add(testList, BorderLayout.WEST); + + JPanel configurableImageComparisonPanel = new JPanel(new BorderLayout()); + getContentPane().add(configurableImageComparisonPanel, BorderLayout.CENTER); + + ImageFormat startingImageFormat = ImageFormat.EPS; + JComboBox imageFormatSelector = new JComboBox<>(ImageFormat.values()); + configurableImageComparisonPanel.add(imageFormatSelector, BorderLayout.NORTH); + imageFormatSelector.setSelectedItem(startingImageFormat); + imageFormatSelector.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent itemEvent) { + ImageFormat format = (ImageFormat) itemEvent.getItem(); + imageComparisonPanel.setImageFormat(format); + AbstractTest test = getTestCase(); + if (test != null) { + setTestCase(test); + } + } + }); + + imageComparisonPanel = new ImageComparisonPanel(startingImageFormat); + configurableImageComparisonPanel.add(imageComparisonPanel, BorderLayout.CENTER); + } + + public static void main(String[] args) throws Exception { + new TestBrowser().setVisible(true); + } + + public AbstractTest getTestCase() { + return testCase; + } + + public void setTestCase(AbstractTest test) { + BufferedImage reference = test.getReference(); + imageComparisonPanel.setLeftComponent(new ImageDisplayPanel(reference, null)); + ImageDisplayPanel imageDisplayPanel; + switch (imageComparisonPanel.getImageFormat()) { + case EPS: + imageDisplayPanel = new ImageDisplayPanel(null, test.getEPS()); + imageComparisonPanel.setRightComponent(imageDisplayPanel); + break; + case PDF: + imageDisplayPanel = new ImageDisplayPanel(null, test.getPDF()); + imageComparisonPanel.setRightComponent(imageDisplayPanel); + break; + case SVG: + imageDisplayPanel = new ImageDisplayPanel(null, test.getSVG()); + imageComparisonPanel.setRightComponent(imageDisplayPanel); + break; + default: + throw new IllegalArgumentException("Unknown image format: " + imageComparisonPanel.getImageFormat()); + } + } + + private enum ImageFormat { + EPS("EPS"), + PDF("PDF"), + SVG("SVG"); + + private final String name; + + ImageFormat(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + private static class ImageComparisonPanel extends Box { + + private final Box leftPanel; + + private final Box rightPanel; + + private ImageFormat imageFormat; + + private JComponent leftComponent; + + private JComponent rightComponent; + + public ImageComparisonPanel(ImageFormat imageFormat) { + super(BoxLayout.PAGE_AXIS); + + this.imageFormat = imageFormat; + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + splitPane.setResizeWeight(0.5); + add(splitPane); + + leftPanel = new Box(BoxLayout.PAGE_AXIS); + leftPanel.add(new JLabel("Graphics2D")); + splitPane.setTopComponent(leftPanel); + + rightPanel = new Box(BoxLayout.PAGE_AXIS); + rightPanel.add(new JLabel(imageFormat.getName())); + splitPane.setBottomComponent(rightPanel); + } + + public void setLeftComponent(JComponent leftComponent) { + if (this.leftComponent != null) { + leftPanel.remove(this.leftComponent); + } + this.leftComponent = leftComponent; + leftPanel.add(leftComponent); + leftPanel.revalidate(); + leftPanel.repaint(); + } + + public void setRightComponent(JComponent rightComponent) { + if (this.rightComponent != null) { + rightPanel.remove(this.rightComponent); + } + this.rightComponent = rightComponent; + rightPanel.add(rightComponent); + rightPanel.revalidate(); + rightPanel.repaint(); + } + + public ImageFormat getImageFormat() { + return imageFormat; + } + + public void setImageFormat(ImageFormat imageFormat) { + this.imageFormat = imageFormat; + JLabel imageFormatLabel = (JLabel) rightPanel.getComponent(0); + imageFormatLabel.setText(imageFormat.getName()); + imageFormatLabel.repaint(); + } + } + + private static class ImageDisplayPanel extends JPanel { + private final InputStream imageData; + + public ImageDisplayPanel(BufferedImage renderedImage, InputStream imageData) { + super(new BorderLayout()); + this.imageData = imageData; + if (renderedImage != null) { + JLabel imageLabel = new JLabel(new ImageIcon(renderedImage)); + add(imageLabel, BorderLayout.CENTER); + } + JButton saveToFileButton = new JButton("Save as..."); + if (imageData == null) { + saveToFileButton.setEnabled(false); + } + saveToFileButton.addActionListener(e -> { + JFileChooser saveFileDialog = new JFileChooser(); + saveFileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY); + saveFileDialog.setMultiSelectionEnabled(false); + int userChoice = saveFileDialog.showSaveDialog(ImageDisplayPanel.this); + if (userChoice != JFileChooser.APPROVE_OPTION) { + return; + } + File dest = saveFileDialog.getSelectedFile(); + FileOutputStream destStream = null; + try { + destStream = new FileOutputStream(dest); + int imageDataChunk; + while ((imageDataChunk = ImageDisplayPanel.this.imageData.read()) != -1) { + destStream.write(imageDataChunk); + } + } catch (IOException e1) { + e1.printStackTrace(); + } finally { + if (destStream != null) { + try { + destStream.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + }); + add(saveToFileButton, BorderLayout.SOUTH); + } + } +} diff --git a/io-vector/src/test/java/org/xbib/graphics/io/visual/TransformTest.java b/io-vector/src/test/java/org/xbib/graphics/io/visual/TransformTest.java new file mode 100644 index 0000000..ea4b19a --- /dev/null +++ b/io-vector/src/test/java/org/xbib/graphics/io/visual/TransformTest.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.io.visual; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +public class TransformTest extends AbstractTest { + + public TransformTest() throws IOException { + } + + @Override + public void draw(Graphics2D g) { + final int rowCount = 2; + final int colCount = 4; + double wTile = getPageSize().getWidth() / colCount; + double hTile = wTile; + + g.translate(0.5 * wTile, 0.5 * hTile); + AffineTransform txOrig = g.getTransform(); + + Shape s = new Rectangle2D.Double(0.0, 0.0, 0.5 * wTile, 0.75 * hTile); + + // Row 1 + + g.draw(s); + + g.translate(wTile, 0.0); + g.draw(s); + + g.translate(wTile, 0.0); + { + Graphics2D g2 = (Graphics2D) g.create(); + g2.scale(0.5, 0.5); + g2.draw(s); + g2.dispose(); + } + + g.translate(wTile, 0.0); + { + Graphics2D g2 = (Graphics2D) g.create(); + g2.rotate(Math.toRadians(30.0)); + g2.draw(s); + g2.dispose(); + } + + // Row 2 + + g.setTransform(txOrig); + g.translate(0.0, hTile); + + g.shear(0.5, 0.0); + g.draw(s); + g.shear(-0.5, 0.0); + g.translate(wTile, 0.0); + + g.shear(0.0, 0.5); + g.draw(s); + g.shear(0.0, -0.5); + } +} diff --git a/layout-pdfbox/NOTICE.txt b/layout-pdfbox/NOTICE.txt new file mode 100644 index 0000000..076cb25 --- /dev/null +++ b/layout-pdfbox/NOTICE.txt @@ -0,0 +1,7 @@ +This work is based on + +https://github.com/ralfstuckert/pdfbox-layout + +(MIT License) + +as of October, 2020 \ No newline at end of file diff --git a/layout-pdfbox/build.gradle b/layout-pdfbox/build.gradle new file mode 100644 index 0000000..7a11b87 --- /dev/null +++ b/layout-pdfbox/build.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation "org.apache.pdfbox:pdfbox:${project.property('pdfbox.version')}" +} \ No newline at end of file diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/ControlElement.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/ControlElement.java new file mode 100644 index 0000000..fc52e3b --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/ControlElement.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +/** + * ControlElements do not have a drawable representation, but control page flow, + * rendering etc. + */ +public class ControlElement implements Element { + + /** + * Triggers a new page in a document. + */ + public final static ControlElement NEWPAGE = new ControlElement("NEWPAGE"); + + private final String name; + + public ControlElement(final String name) { + this.name = name; + } + + @Override + public String toString() { + return "ControlElement [NEWPAGE=" + NEWPAGE + ", name=" + name + "]"; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Cutter.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Cutter.java new file mode 100644 index 0000000..6a2d2b3 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Cutter.java @@ -0,0 +1,63 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.IOException; + +/** + * A cutter transforms any Drawable element into a {@link Dividable}. It simply + * cuts the drawable vertically into pieces matching the target height. + */ +public class Cutter implements Dividable, Drawable { + + private final Drawable undividable; + private final float viewPortY; + private final float viewPortHeight; + + public Cutter(Drawable undividableElement) throws IOException { + this(undividableElement, 0, undividableElement.getHeight()); + } + + protected Cutter(Drawable undividable, float viewPortY, float viewPortHeight) { + this.undividable = undividable; + this.viewPortY = viewPortY; + this.viewPortHeight = viewPortHeight; + } + + @Override + public Divided divide(float remainingHeight, final float pageHeight) { + return new Divided(new Cutter(undividable, viewPortY, remainingHeight), + new Cutter(undividable, viewPortY - remainingHeight, + viewPortHeight - remainingHeight)); + } + + @Override + public float getWidth() throws IOException { + return undividable.getWidth(); + } + + @Override + public float getHeight() throws IOException { + return viewPortHeight; + } + + @Override + public Position getAbsolutePosition() { + return null; + } + + @Override + public void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, DrawListener drawListener) throws IOException { + Position viewPortOrigin = upperLeft.add(0, -viewPortY); + undividable.draw(pdDocument, contentStream, viewPortOrigin, drawListener); + } + + @Override + public Drawable removeLeadingEmptyVerticalSpace() throws IOException { + return new Cutter(undividable.removeLeadingEmptyVerticalSpace()); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Dimension.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Dimension.java new file mode 100644 index 0000000..27a2b49 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Dimension.java @@ -0,0 +1,57 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +/** + * In order to avoid dependencies to AWT, we use our own Dimension class here. + */ +public class Dimension { + + private final float width; + private final float height; + + public Dimension(float width, float height) { + super(); + this.width = width; + this.height = height; + } + + public float getWidth() { + return width; + } + + public float getHeight() { + return height; + } + + @Override + public String toString() { + return "Dimension [width=" + width + ", height=" + height + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(height); + result = prime * result + Float.floatToIntBits(width); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Dimension other = (Dimension) obj; + if (Float.floatToIntBits(height) != Float.floatToIntBits(other.height)) { + return false; + } + return Float.floatToIntBits(width) == Float.floatToIntBits(other.width); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Dividable.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Dividable.java new file mode 100644 index 0000000..5f6c9b6 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Dividable.java @@ -0,0 +1,51 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import java.io.IOException; + +/** + * If a drawable is marked as {@link Dividable}, it can be (vertically) divided + * in case it does not fit on the (remaining) page. + */ +public interface Dividable { + + /** + * Divides the drawable vetically into pieces where the first part is to + * respect the given remaining height. The page height allows to make better + * decisions on how to divide best. + * + * @param remainingHeight the remaining height on the page dictating the height of the + * first part. + * @param nextPageHeight the height of the next page allows to make better decisions on + * how to divide best, e.g. maybe the element fits completely on + * the next page. + * @return the Divided containing the first part and the tail. + * @throws IOException by pdfbox. + */ + Divided divide(final float remainingHeight, final float nextPageHeight) + throws IOException; + + /** + * A container for the result of a {@link Dividable#divide(float, float)} + * operation. + */ + class Divided { + + private final Drawable first; + private final Drawable tail; + + public Divided(Drawable first, Drawable tail) { + this.first = first; + this.tail = tail; + } + + public Drawable getFirst() { + return first; + } + + public Drawable getTail() { + return tail; + } + + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Document.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Document.java new file mode 100644 index 0000000..83fb1a1 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Document.java @@ -0,0 +1,364 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.xbib.graphics.layout.pdfbox.elements.render.Layout; +import org.xbib.graphics.layout.pdfbox.elements.render.LayoutHint; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderContext; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderListener; +import org.xbib.graphics.layout.pdfbox.elements.render.Renderer; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayout; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * The central class for creating a document. + */ +public class Document implements RenderListener { + + /** + * A4 portrait without margins. + */ + public final static PageFormat DEFAULT_PAGE_FORMAT = new PageFormat(); + + private final List> elements = new ArrayList<>(); + private final List customRenderer = new CopyOnWriteArrayList(); + private final List renderListener = new CopyOnWriteArrayList(); + + private PDDocument pdDocument; + private final PageFormat pageFormat; + + /** + * Creates a Document using the {@link #DEFAULT_PAGE_FORMAT}. + */ + public Document() { + this(DEFAULT_PAGE_FORMAT); + } + + /** + * Creates a Document in A4 with orientation portrait and the given margins. + * By default, a {@link VerticalLayout} is used. + * + * @param marginLeft the left margin + * @param marginRight the right margin + * @param marginTop the top margin + * @param marginBottom the bottom margin + */ + public Document(float marginLeft, float marginRight, float marginTop, + float marginBottom) { + this(PageFormat.with() + .margins(marginLeft, marginRight, marginTop, marginBottom) + .build()); + } + + /** + * Creates a Document based on the given media box. By default, a + * {@link VerticalLayout} is used. + * + * @param mediaBox the media box to use. + * @deprecated use {@link #Document(PageFormat)} instead. + */ + @Deprecated + public Document(PDRectangle mediaBox) { + this(mediaBox, 0, 0, 0, 0); + } + + /** + * Creates a Document based on the given media box and margins. By default, + * a {@link VerticalLayout} is used. + * + * @param mediaBox the media box to use. + * @param marginLeft the left margin + * @param marginRight the right margin + * @param marginTop the top margin + * @param marginBottom the bottom margin + * @deprecated use {@link #Document(PageFormat)} instead. + */ + @Deprecated + public Document(PDRectangle mediaBox, float marginLeft, float marginRight, + float marginTop, float marginBottom) { + this(new PageFormat(mediaBox, Orientation.Portrait, marginLeft, + marginRight, marginTop, marginBottom)); + } + + /** + * Creates a Document based on the given page format. By default, a + * {@link VerticalLayout} is used. + * + * @param pageFormat the page format box to use. + */ + public Document(final PageFormat pageFormat) { + this.pageFormat = pageFormat; + } + + /** + * Adds an element to the document using a {@link VerticalLayoutHint}. + * + * @param element the element to add + */ + public void add(final Element element) { + add(element, new VerticalLayoutHint()); + } + + /** + * Adds an element with the given layout hint. + * + * @param element the element to add + * @param layoutHint the hint for the {@link Layout}. + */ + public void add(final Element element, final LayoutHint layoutHint) { + elements.add(createEntry(element, layoutHint)); + } + + private Entry createEntry(final Element element, + final LayoutHint layoutHint) { + return new SimpleEntry(element, layoutHint); + } + + /** + * Removes the given element. + * + * @param element the element to remove. + */ + public void remove(final Element element) { + elements.remove(element); + } + + /** + * @return the page format to use as default. + */ + public PageFormat getPageFormat() { + return pageFormat; + } + + /** + * @return the left document margin. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public float getMarginLeft() { + return getPageFormat().getMarginLeft(); + } + + /** + * @return the right document margin. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public float getMarginRight() { + return getPageFormat().getMarginRight(); + } + + /** + * @return the top document margin. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public float getMarginTop() { + return getPageFormat().getMarginTop(); + } + + /** + * @return the bottom document margin. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public float getMarginBottom() { + return getPageFormat().getMarginBottom(); + } + + /** + * @return the media box to use. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public PDRectangle getMediaBox() { + return getPageFormat().getMediaBox(); + } + + /** + * @return the orientation to use. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public Orientation getOrientation() { + return getPageFormat().getOrientation(); + } + + /** + * @return the media box width minus margins. + */ + public float getPageWidth() { + return getMediaBox().getWidth() - getMarginLeft() - getMarginRight(); + } + + /** + * @return the media box height minus margins. + */ + public float getPageHeight() { + return getMediaBox().getHeight() - getMarginTop() - getMarginBottom(); + } + + /** + * Returns the {@link PDDocument} to be created by method {@link #render()}. + * Beware that this PDDocument is released after rendering. This means each + * rendering process creates a new PDDocument. + * + * @return the PDDocument to be used on the next call to {@link #render()}. + */ + public PDDocument getPDDocument() { + if (pdDocument == null) { + pdDocument = new PDDocument(); + } + return pdDocument; + } + + /** + * Called after {@link #render()} in order to release the current document. + */ + protected void resetPDDocument() { + this.pdDocument = null; + } + + /** + * Adds a (custom) {@link Renderer} that may handle the rendering of an + * element. All renderers will be asked to render the current element in the + * order they have been added. If no renderer is capable, the default + * renderer will be asked. + * + * @param renderer the renderer to add. + */ + public void addRenderer(final Renderer renderer) { + if (renderer != null) { + customRenderer.add(renderer); + } + } + + /** + * Removes a {@link Renderer} . + * + * @param renderer the renderer to remove. + */ + public void removeRenderer(final Renderer renderer) { + customRenderer.remove(renderer); + } + + /** + * Renders all elements and returns the resulting {@link PDDocument}. + * + * @return the resulting {@link PDDocument} + * @throws IOException by pdfbox + */ + public PDDocument render() throws IOException { + PDDocument document = getPDDocument(); + RenderContext renderContext = new RenderContext(this, document); + for (Entry entry : elements) { + Element element = entry.getKey(); + LayoutHint layoutHint = entry.getValue(); + boolean success = false; + + // first ask custom renderer to render the element + Iterator customRendererIterator = customRenderer + .iterator(); + while (!success && customRendererIterator.hasNext()) { + success = customRendererIterator.next().render(renderContext, + element, layoutHint); + } + + // if none of them felt responsible, let the default renderer do the job. + if (!success) { + success = renderContext.render(renderContext, element, + layoutHint); + } + + if (!success) { + throw new IllegalArgumentException( + String.format( + "neither layout %s nor the render context knows what to do with %s", + renderContext.getLayout(), element)); + + } + } + renderContext.close(); + + resetPDDocument(); + return document; + } + + /** + * {@link #render() Renders} the document and saves it to the given file. + * + * @param file the file to save to. + * @throws IOException by pdfbox + */ + public void save(final File file) throws IOException { + try (OutputStream out = new FileOutputStream(file)) { + save(out); + } + } + + /** + * {@link #render() Renders} the document and saves it to the given output + * stream. + * + * @param output the stream to save to. + * @throws IOException by pdfbox + */ + public void save(final OutputStream output) throws IOException { + try (PDDocument document = render()) { + try { + document.save(output); + } catch (IOException ioe) { + throw ioe; + } catch (Exception e) { + throw new IOException(e); + } + } + } + + /** + * Adds a {@link RenderListener} that will be notified during + * {@link #render() rendering}. + * + * @param listener the listener to add. + */ + public void addRenderListener(final RenderListener listener) { + if (listener != null) { + renderListener.add(listener); + } + } + + /** + * Removes a {@link RenderListener} . + * + * @param listener the listener to remove. + */ + public void removeRenderListener(final RenderListener listener) { + renderListener.remove(listener); + } + + @Override + public void beforePage(final RenderContext renderContext) + throws IOException { + for (RenderListener listener : renderListener) { + listener.beforePage(renderContext); + } + } + + @Override + public void afterPage(final RenderContext renderContext) throws IOException { + for (RenderListener listener : renderListener) { + listener.afterPage(renderContext); + } + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Drawable.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Drawable.java new file mode 100644 index 0000000..3119ca3 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Drawable.java @@ -0,0 +1,57 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.elements.render.Layout; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.IOException; + +/** + * Common interface for drawable objects. + */ +public interface Drawable { + + /** + * @return the width of the drawable. + * @throws IOException by pdfbox + */ + float getWidth() throws IOException; + + /** + * @return the height of the drawable. + * @throws IOException by pdfbox + */ + float getHeight() throws IOException; + + /** + * If an absolute position is given, the drawable will be drawn at this + * position ignoring any {@link Layout}. + * + * @return the absolute position. + * @throws IOException by pdfbox + */ + Position getAbsolutePosition() throws IOException; + + /** + * Draws the object at the given position. + * + * @param pdDocument the underlying pdfbox document. + * @param contentStream the stream to draw to. + * @param upperLeft the upper left position to start drawing. + * @param drawListener the listener to + * {@link DrawListener#drawn(Object, Position, float, float) notify} on + * drawn objects. + * @throws IOException by pdfbox + */ + void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, DrawListener drawListener) throws IOException; + + /** + * @return a copy of this drawable where any leading empty vertical space is + * removed, if possible. This is useful for avoiding leading empty + * space on a new page. + * @throws IOException by pdfbox + */ + Drawable removeLeadingEmptyVerticalSpace() throws IOException; +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Element.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Element.java new file mode 100644 index 0000000..dc67d17 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Element.java @@ -0,0 +1,8 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +/** + * Base (tagging) interface for elements in a {@link Document}. + */ +public interface Element { + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Frame.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Frame.java new file mode 100644 index 0000000..7ed2348 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Frame.java @@ -0,0 +1,680 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.shape.Rect; +import org.xbib.graphics.layout.pdfbox.shape.Shape; +import org.xbib.graphics.layout.pdfbox.shape.Stroke; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.WidthRespecting; +import java.awt.Color; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * The frame is a container for a {@link Drawable}, that allows to add margin, + * padding, border and background to the contained drawable. The size (width and + * height) is either given, or calculated based on the dimensions of the + * contained item. The size available for the inner element is reduced by the + * margin, padding and border width. + */ +public class Frame implements Element, Drawable, WidthRespecting, Dividable { + + private final List innerList = new CopyOnWriteArrayList(); + + private float paddingLeft; + private float paddingRight; + private float paddingTop; + private float paddingBottom; + + private float marginLeft; + private float marginRight; + private float marginTop; + private float marginBottom; + + private Shape shape = new Rect(); + private Stroke borderStroke = new Stroke(); + private Color borderColor; + private Color backgroundColor; + + private float maxWidth = -1; + + private final Float givenWidth; + private final Float givenHeight; + + private Position absolutePosition; + + /** + * Creates an empty frame. + */ + public Frame() { + this(null, null); + } + + /** + * Creates a frame containing the inner element. + * + * @param inner the item to contain. + */ + public Frame(final Drawable inner) { + this(inner, null, null); + } + + /** + * Creates a frame containing the inner element, optionally constraint by + * the given dimensions. These contraints target the border-box of the + * frame, means: the inner element plus padding plus border width, but not + * the margin. + * + * @param inner the item to contain. + * @param width the width to constrain the border-box of the frame to, or + * null. + * @param height the height to constrain the border-box of the frame to, or + * null. + */ + public Frame(final Drawable inner, final Float width, final Float height) { + this(width, height); + add(inner); + } + + /** + * Creates a frame constraint by the given dimensions. These contraints + * target the border-box of the frame, means: the inner element plus padding + * plus border width, but not the margin. + * + * @param width the width to constrain the border-box of the frame to, or + * null. + * @param height the height to constrain the border-box of the frame to, or + * null. + */ + public Frame(final Float width, final Float height) { + this.givenWidth = width; + this.givenHeight = height; + } + + /** + * Adds a drawable to the frame. + * + * @param drawable + */ + public void add(final Drawable drawable) { + innerList.add(drawable); + } + + protected void addAll(final Collection drawable) { + innerList.addAll(drawable); + } + + /** + * @return the shape to use as border and/or background. + */ + public Shape getShape() { + return shape; + } + + /** + * Sets the shape to use as border and/or background. + * + * @param shape the shape to use. + */ + public void setShape(Shape shape) { + this.shape = shape; + } + + /** + * The stroke to use to draw the border. + * + * @return the stroke to use. + */ + public Stroke getBorderStroke() { + return borderStroke; + } + + /** + * Sets the stroke to use to draw the border. + * + * @param borderStroke the stroke to use. + */ + public void setBorderStroke(Stroke borderStroke) { + this.borderStroke = borderStroke; + } + + /** + * @return the widht of the {@link #getBorderStroke()} or 0. + */ + protected float getBorderWidth() { + return hasBorder() ? getBorderStroke().getLineWidth() : 0; + } + + /** + * @return if a {@link #getShape() shape}, a {@link #getBorderStroke() + * stroke} and {@link #getBorderColor() color} is set. + */ + protected boolean hasBorder() { + return getShape() != null && getBorderStroke() != null + && getBorderColor() != null; + } + + /** + * @return the color to use to draw the border. + */ + public Color getBorderColor() { + return borderColor; + } + + /** + * Sets the color to use to draw the border. + * + * @param borderColor the border color. + */ + public void setBorderColor(Color borderColor) { + this.borderColor = borderColor; + } + + /** + * Convenience method for setting both border color and stroke. + * + * @param borderColor the border color. + * @param borderStroke the stroke to use. + */ + public void setBorder(Color borderColor, Stroke borderStroke) { + setBorderColor(borderColor); + setBorderStroke(borderStroke); + } + + /** + * @return the color to use to draw the background. + */ + public Color getBackgroundColor() { + return backgroundColor; + } + + /** + * Sets the color to use to draw the background. + * + * @param backgroundColor the background color. + */ + public void setBackgroundColor(Color backgroundColor) { + this.backgroundColor = backgroundColor; + } + + /** + * Copies all attributes but the inner drawable and size to the given frame. + * + * @param other the frame to copy the attributes to. + */ + protected void copyAllButInnerAndSizeTo(final Frame other) { + other.setShape(this.getShape()); + other.setBorderStroke(this.getBorderStroke()); + other.setBorderColor(this.getBorderColor()); + other.setBackgroundColor(this.getBackgroundColor()); + + other.setPaddingBottom(this.getPaddingBottom()); + other.setPaddingLeft(this.getPaddingLeft()); + other.setPaddingRight(this.getPaddingRight()); + other.setPaddingTop(this.getPaddingTop()); + + other.setMarginBottom(this.getMarginBottom()); + other.setMarginLeft(this.getMarginLeft()); + other.setMarginRight(this.getMarginRight()); + other.setMarginTop(this.getMarginTop()); + } + + /** + * @return the left padding + */ + public float getPaddingLeft() { + return paddingLeft; + } + + /** + * Sets the left padding. + * + * @param paddingLeft left padding. + */ + public void setPaddingLeft(float paddingLeft) { + this.paddingLeft = paddingLeft; + } + + /** + * @return the right padding + */ + public float getPaddingRight() { + return paddingRight; + } + + /** + * Sets the right padding. + * + * @param paddingRight right padding. + */ + public void setPaddingRight(float paddingRight) { + this.paddingRight = paddingRight; + } + + /** + * @return the top padding + */ + public float getPaddingTop() { + return paddingTop; + } + + /** + * Sets the top padding. + * + * @param paddingTop top padding. + */ + public void setPaddingTop(float paddingTop) { + this.paddingTop = paddingTop; + } + + /** + * @return the bottom padding + */ + public float getPaddingBottom() { + return paddingBottom; + } + + /** + * Sets the bottom padding. + * + * @param paddingBottom bottom padding. + */ + public void setPaddingBottom(float paddingBottom) { + this.paddingBottom = paddingBottom; + } + + /** + * Sets the padding. + * + * @param left left padding. + * @param right right padding. + * @param top top padding. + * @param bottom bottom padding. + */ + public void setPadding(float left, float right, float top, float bottom) { + setPaddingLeft(left); + setPaddingRight(right); + setPaddingTop(top); + setPaddingBottom(bottom); + } + + /** + * @return the left margin + */ + public float getMarginLeft() { + return marginLeft; + } + + /** + * Sets the left margin. + * + * @param marginLeft left margin. + */ + public void setMarginLeft(float marginLeft) { + this.marginLeft = marginLeft; + } + + /** + * @return the right margin + */ + public float getMarginRight() { + return marginRight; + } + + /** + * Sets the right margin. + * + * @param marginRight right margin. + */ + public void setMarginRight(float marginRight) { + this.marginRight = marginRight; + } + + /** + * @return the top margin + */ + public float getMarginTop() { + return marginTop; + } + + /** + * Sets the top margin. + * + * @param marginTop top margin. + */ + public void setMarginTop(float marginTop) { + this.marginTop = marginTop; + } + + /** + * @return the bottom margin + */ + public float getMarginBottom() { + return marginBottom; + } + + /** + * Sets the bottom margin. + * + * @param marginBottom bottom margin. + */ + public void setMarginBottom(float marginBottom) { + this.marginBottom = marginBottom; + } + + /** + * Sets the margin. + * + * @param left left margin. + * @param right right margin. + * @param top top margin. + * @param bottom bottom margin. + */ + public void setMargin(float left, float right, float top, float bottom) { + setMarginLeft(left); + setMarginRight(right); + setMarginTop(top); + setMarginBottom(bottom); + } + + /** + * @return the sum of left/right padding and border width. + */ + protected float getHorizontalShapeSpacing() { + return 2 * getBorderWidth() + getPaddingLeft() + getPaddingRight(); + } + + /** + * @return the sum of top/bottom padding and border width. + */ + protected float getVerticalShapeSpacing() { + return 2 * getBorderWidth() + getPaddingTop() + getPaddingBottom(); + } + + /** + * @return the sum of left/right margin, padding and border width. + */ + protected float getHorizontalSpacing() { + return getMarginLeft() + getMarginRight() + getHorizontalShapeSpacing(); + } + + /** + * @return the sum of top/bottom margin, padding and border width. + */ + protected float getVerticalSpacing() { + return getMarginTop() + getMarginBottom() + getVerticalShapeSpacing(); + } + + /** + * @return the height given to constrain the size of the shape. + */ + protected Float getGivenHeight() { + return givenHeight; + } + + /** + * @return the width given to constrain the size of the shape. + */ + protected Float getGivenWidth() { + return givenWidth; + } + + @Override + public float getWidth() throws IOException { + if (getGivenWidth() != null) { + return getGivenWidth() + getMarginLeft() + getMarginRight(); + } + return getMaxWidth(innerList) + getHorizontalSpacing(); + } + + protected float getMaxWidth(List drawableList) throws IOException { + float max = 0; + if (drawableList != null) { + for (Drawable inner : drawableList) { + max = Math.max(max, inner.getWidth()); + } + } + return max; + } + + @Override + public float getHeight() throws IOException { + if (getGivenHeight() != null) { + return getGivenHeight() + getMarginTop() + getMarginBottom(); + } + return getHeight(innerList) + getVerticalSpacing(); + } + + protected float getHeight(List drawableList) throws IOException { + float height = 0; + if (drawableList != null) { + for (Drawable inner : drawableList) { + height += inner.getHeight(); + } + } + return height; + } + + @Override + public Position getAbsolutePosition() throws IOException { + return absolutePosition; + } + + /** + * Sets th absolute position. + * + * @param absolutePosition the absolute position to use, or null. + */ + public void setAbsolutePosition(Position absolutePosition) { + this.absolutePosition = absolutePosition; + } + + @Override + public float getMaxWidth() { + return maxWidth; + } + + @Override + public void setMaxWidth(float maxWidth) { + this.maxWidth = maxWidth; + + for (Drawable inner : innerList) { + setMaxWidth(inner, maxWidth); + } + } + + private void setMaxWidth(final Drawable inner, float maxWidth) { + if (inner instanceof WidthRespecting) { + if (getGivenWidth() != null) { + ((WidthRespecting) inner).setMaxWidth(getGivenWidth() + - getHorizontalShapeSpacing()); + } else if (maxWidth >= 0) { + ((WidthRespecting) inner).setMaxWidth(maxWidth + - getHorizontalSpacing()); + } + } + } + + /** + * Propagates the max width to the inner item if there is a given size, but + * no absolute position. + * + * @throws IOException by pdfbox. + */ + protected void setInnerMaxWidthIfNecessary() throws IOException { + if (getAbsolutePosition() == null && getGivenWidth() != null) { + setMaxWidth(getGivenWidth() - getHorizontalShapeSpacing()); + } + } + + @Override + public void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, DrawListener drawListener) throws IOException { + + setInnerMaxWidthIfNecessary(); + + float halfBorderWidth = 0; + if (getBorderWidth() > 0) { + halfBorderWidth = getBorderWidth() / 2f; + } + upperLeft = upperLeft.add(getMarginLeft() + halfBorderWidth, + -getMarginTop() - halfBorderWidth); + + if (getShape() != null) { + float shapeWidth = getWidth() - getMarginLeft() - getMarginRight() + - getBorderWidth(); + float shapeHeight = getHeight() - getMarginTop() + - getMarginBottom() - getBorderWidth(); + + if (getBackgroundColor() != null) { + getShape().fill(pdDocument, contentStream, upperLeft, + shapeWidth, shapeHeight, getBackgroundColor(), + drawListener); + } + if (hasBorder()) { + getShape().draw(pdDocument, contentStream, upperLeft, + shapeWidth, shapeHeight, getBorderColor(), + getBorderStroke(), drawListener); + } + } + + Position innerUpperLeft = upperLeft.add(getPaddingLeft() + + halfBorderWidth, -getPaddingTop() - halfBorderWidth); + + for (Drawable inner : innerList) { + inner.draw(pdDocument, contentStream, innerUpperLeft, drawListener); + innerUpperLeft = innerUpperLeft.add(0, -inner.getHeight()); + } + } + + @Override + public Drawable removeLeadingEmptyVerticalSpace() throws IOException { + if (innerList.size() > 0) { + Drawable drawableWithoutLeadingVerticalSpace = innerList.get(0) + .removeLeadingEmptyVerticalSpace(); + innerList.set(0, drawableWithoutLeadingVerticalSpace); + } + return this; + } + + @Override + public Divided divide(float remainingHeight, float nextPageHeight) + throws IOException { + setInnerMaxWidthIfNecessary(); + + if (remainingHeight - getVerticalSpacing() <= 0) { + return new Divided(new VerticalSpacer(remainingHeight), this); + } + + // find first inner that does not fit on page + float spaceLeft = remainingHeight - getVerticalSpacing(); + + DividedList dividedList = divideList(innerList, spaceLeft); + + float spaceLeftForDivided = spaceLeft + - getHeight(dividedList.getHead()); + Divided divided = null; + + if (dividedList.getDrawableToDivide() != null) { + Dividable innerDividable = null; + if (dividedList.getDrawableToDivide() instanceof Dividable) { + innerDividable = (Dividable) dividedList.getDrawableToDivide(); + } else { + innerDividable = new Cutter(dividedList.getDrawableToDivide()); + } + // some space left on this page for the inner element + divided = innerDividable.divide(spaceLeftForDivided, nextPageHeight + - getVerticalSpacing()); + } + + Float firstHeight = getGivenHeight() == null ? null : remainingHeight; + Float tailHeight = getGivenHeight() == null ? null : getGivenHeight() + - spaceLeft; + + // create head sub frame + Frame first = new Frame(getGivenWidth(), firstHeight); + copyAllButInnerAndSizeTo(first); + if (dividedList.getHead() != null) { + first.addAll(dividedList.getHead()); + } + if (divided != null) { + first.add(divided.getFirst()); + } + + // create tail sub frame + Frame tail = new Frame(getGivenWidth(), tailHeight); + copyAllButInnerAndSizeTo(tail); + if (divided != null) { + tail.add(divided.getTail()); + } + if (dividedList.getTail() != null) { + tail.addAll(dividedList.getTail()); + } + + return new Divided(first, tail); + } + + private DividedList divideList(List items, float spaceLeft) + throws IOException { + List head = null; + List tail = null; + Drawable toDivide = null; + + float tmpHeight = 0; + int index = 0; + while (tmpHeight < spaceLeft) { + tmpHeight += items.get(index).getHeight(); + + if (tmpHeight == spaceLeft) { + // we can split between two drawables + head = items.subList(0, index + 1); + if (index + 1 < items.size()) { + tail = items.subList(index + 1, items.size()); + } + } + + if (tmpHeight > spaceLeft) { + head = items.subList(0, index); + toDivide = items.get(index); + if (index + 1 < items.size()) { + tail = items.subList(index + 1, items.size()); + } + } + + ++index; + } + + return new DividedList(head, toDivide, tail); + } + + public static class DividedList { + private final List head; + private final Drawable drawableToDivide; + private final List tail; + + public DividedList(List head, Drawable drawableToDivide, + List tail) { + this.head = head; + this.drawableToDivide = drawableToDivide; + this.tail = tail; + } + + public List getHead() { + return head; + } + + public Drawable getDrawableToDivide() { + return drawableToDivide; + } + + public List getTail() { + return tail; + } + + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/HorizontalRuler.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/HorizontalRuler.java new file mode 100644 index 0000000..4646510 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/HorizontalRuler.java @@ -0,0 +1,96 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.shape.Stroke; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.WidthRespecting; +import java.awt.Color; +import java.io.IOException; + +/** + * A horizontal ruler that adjust its width to the given + * {@link WidthRespecting#getMaxWidth() max width}. + */ +public class HorizontalRuler implements Drawable, Element, WidthRespecting { + + private final Stroke stroke; + + private final Color color; + + private float maxWidth = -1f; + + public HorizontalRuler(Stroke stroke, Color color) { + super(); + this.stroke = stroke; + this.color = color; + } + + /** + * @return the stroke to draw the ruler with. + */ + public Stroke getStroke() { + return stroke; + } + + /** + * @return the color to draw the ruler with. + */ + public Color getColor() { + return color; + } + + @Override + public float getMaxWidth() { + return maxWidth; + } + + @Override + public void setMaxWidth(float maxWidth) { + this.maxWidth = maxWidth; + } + + @Override + public float getWidth() throws IOException { + return getMaxWidth(); + } + + @Override + public float getHeight() throws IOException { + if (getStroke() == null) { + return 0f; + } + return getStroke().getLineWidth(); + } + + @Override + public Position getAbsolutePosition() { + return null; + } + + @Override + public void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, DrawListener drawListener) throws IOException { + if (getColor() != null) { + contentStream.setStrokingColor(getColor()); + } + if (getStroke() != null) { + getStroke().applyTo(contentStream); + float x = upperLeft.getX(); + float y = upperLeft.getY() - getStroke().getLineWidth() / 2; + contentStream.moveTo(x, y); + contentStream.lineTo(x + getWidth(), y); + contentStream.stroke(); + } + if (drawListener != null) { + drawListener.drawn(this, upperLeft, getWidth(), getHeight()); + } + } + + @Override + public Drawable removeLeadingEmptyVerticalSpace() { + return this; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/ImageElement.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/ImageElement.java new file mode 100644 index 0000000..fd47698 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/ImageElement.java @@ -0,0 +1,137 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.WidthRespecting; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import javax.imageio.ImageIO; + +public class ImageElement implements Element, Drawable, Dividable, + WidthRespecting { + + /** + * Set this to {@link #setWidth(float)} resp. {@link #setHeight(float)} + * (usually both) in order to respect the {@link WidthRespecting width}. + */ + public final static float SCALE_TO_RESPECT_WIDTH = -1f; + + private final BufferedImage image; + private float width; + private float height; + private float maxWidth = -1; + private Position absolutePosition; + + public ImageElement(final BufferedImage image) { + this.image = image; + this.width = image.getWidth(); + this.height = image.getHeight(); + } + + public ImageElement(final InputStream inputStream) throws IOException { + this(ImageIO.read(inputStream)); + } + + public ImageElement(final String filePath) throws IOException { + this(ImageIO.read(new File(filePath))); + } + + @Override + public float getWidth() throws IOException { + if (width == SCALE_TO_RESPECT_WIDTH) { + if (getMaxWidth() > 0 && image.getWidth() > getMaxWidth()) { + return getMaxWidth(); + } + return image.getWidth(); + } + return width; + } + + /** + * Sets the width. Default is the image width. Set to + * {@link #SCALE_TO_RESPECT_WIDTH} in order to let the image + * {@link WidthRespecting respect any given width}. + * + * @param width the width to use. + */ + public void setWidth(float width) { + this.width = width; + } + + @Override + public float getHeight() throws IOException { + if (height == SCALE_TO_RESPECT_WIDTH) { + if (getMaxWidth() > 0 && image.getWidth() > getMaxWidth()) { + return getMaxWidth() / (float) image.getWidth() + * (float) image.getHeight(); + } + return image.getHeight(); + } + return height; + } + + /** + * Sets the height. Default is the image height. Set to + * {@link #SCALE_TO_RESPECT_WIDTH} in order to let the image + * {@link WidthRespecting respect any given width}. Usually this makes only + * sense if you also set the width to {@link #SCALE_TO_RESPECT_WIDTH}. + * + * @param height the height to use. + */ + public void setHeight(float height) { + this.height = height; + } + + @Override + public Divided divide(float remainingHeight, float nextPageHeight) + throws IOException { + if (getHeight() <= nextPageHeight) { + return new Divided(new VerticalSpacer(remainingHeight), this); + } + return new Cutter(this).divide(remainingHeight, nextPageHeight); + } + + @Override + public float getMaxWidth() { + return maxWidth; + } + + @Override + public void setMaxWidth(float maxWidth) { + this.maxWidth = maxWidth; + } + + @Override + public Position getAbsolutePosition() { + return absolutePosition; + } + + /** + * Sets the absolute position to render at. + * + * @param absolutePosition the absolute position. + */ + public void setAbsolutePosition(Position absolutePosition) { + this.absolutePosition = absolutePosition; + } + + @Override + public void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, DrawListener drawListener) throws IOException { + CompatibilityHelper.drawImage(image, pdDocument, contentStream, + upperLeft, getWidth(), getHeight()); + if (drawListener != null) { + drawListener.drawn(this, upperLeft, getWidth(), getHeight()); + } + } + + @Override + public Drawable removeLeadingEmptyVerticalSpace() { + return this; + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Orientation.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Orientation.java new file mode 100644 index 0000000..91db239 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Orientation.java @@ -0,0 +1,7 @@ +package org.xbib.graphics.layout.pdfbox.elements; + + +public enum Orientation { + + Portrait, Landscape +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/PageFormat.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/PageFormat.java new file mode 100644 index 0000000..ec4a1b1 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/PageFormat.java @@ -0,0 +1,369 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayout; +import org.xbib.graphics.layout.pdfbox.text.Constants; + +/** + * Defines the size and orientation of a page. The default is A4 portrait + * without margins. + */ +public class PageFormat implements Element { + + private final float marginLeft; + private final float marginRight; + private final float marginTop; + private final float marginBottom; + private final PDRectangle mediaBox; + private final Orientation orientation; + private final int rotation; + + /** + * Creates a PageFormat with A4 portrait without margins. + */ + public PageFormat() { + this(Constants.A4); + } + + /** + * Creates a PageFormat with a given size and orientation portrait. + * + * @param mediaBox the size. + */ + public PageFormat(final PDRectangle mediaBox) { + this(mediaBox, Orientation.Portrait); + } + + /** + * Creates a PageFormat with a given size and orientation. + * + * @param mediaBox the size. + * @param orientation the orientation. + */ + public PageFormat(final PDRectangle mediaBox, final Orientation orientation) { + this(mediaBox, orientation, 0, 0, 0, 0); + } + + /** + * Creates a Document based on the given media box and margins. By default, + * a {@link VerticalLayout} is used. + * + * @param mediaBox the media box to use. + * @param orientation the orientation to use. + * @param marginLeft the left margin + * @param marginRight the right margin + * @param marginTop the top margin + * @param marginBottom the bottom margin + */ + public PageFormat(PDRectangle mediaBox, Orientation orientation, + float marginLeft, float marginRight, float marginTop, + float marginBottom) { + this(mediaBox, orientation, 0, marginLeft, marginRight, marginTop, marginBottom); + } + + /** + * Creates a Document based on the given media box and margins. By default, + * a {@link VerticalLayout} is used. + * + * @param mediaBox the media box to use. + * @param orientation the orientation to use. + * @param rotation the rotation to apply to the page after rendering. + * @param marginLeft the left margin + * @param marginRight the right margin + * @param marginTop the top margin + * @param marginBottom the bottom margin + */ + public PageFormat(PDRectangle mediaBox, Orientation orientation, + int rotation, float marginLeft, float marginRight, + float marginTop, float marginBottom) { + this.mediaBox = mediaBox; + this.orientation = orientation; + this.rotation = rotation; + this.marginLeft = marginLeft; + this.marginRight = marginRight; + this.marginTop = marginTop; + this.marginBottom = marginBottom; + } + + /** + * @return the orientation to use. + */ + public Orientation getOrientation() { + if (orientation != null) { + return orientation; + } + if (getMediaBox().getWidth() > getMediaBox().getHeight()) { + return Orientation.Landscape; + } + return Orientation.Portrait; + } + + /** + * @return the rotation to apply to the page after rendering. + */ + public int getRotation() { + return rotation; + } + + /** + * @return the left document margin. + */ + public float getMarginLeft() { + return marginLeft; + } + + /** + * @return the right document margin. + */ + public float getMarginRight() { + return marginRight; + } + + /** + * @return the top document margin. + */ + public float getMarginTop() { + return marginTop; + } + + /** + * @return the bottom document margin. + */ + public float getMarginBottom() { + return marginBottom; + } + + /** + * @return the media box to use. + */ + public PDRectangle getMediaBox() { + return mediaBox; + } + + /** + * @return a page format builder. The default of the builder is A4 portrait + * without margins. + */ + public static PageFormatBuilder with() { + return new PageFormatBuilder(); + } + + public static class PageFormatBuilder { + private float marginLeft; + private float marginRight; + private float marginTop; + private float marginBottom; + private PDRectangle mediaBox = Constants.A4; + private Orientation orientation; + private int rotation; + + protected PageFormatBuilder() { + } + + /** + * Actually builds the PageFormat. + * + * @return the resulting PageFormat. + */ + public PageFormat build() { + return new PageFormat(mediaBox, orientation, rotation, marginLeft, + marginRight, marginTop, marginBottom); + } + + /** + * Sets the left margin. + * + * @param marginLeft the left margin to use. + * @return the builder. + */ + public PageFormatBuilder marginLeft(float marginLeft) { + this.marginLeft = marginLeft; + return this; + } + + /** + * Sets the right margin. + * + * @param marginRight the right margin to use. + * @return the builder. + */ + public PageFormatBuilder marginRight(float marginRight) { + this.marginRight = marginRight; + return this; + } + + /** + * Sets the top margin. + * + * @param marginTop the top margin to use. + * @return the builder. + */ + public PageFormatBuilder marginTop(float marginTop) { + this.marginTop = marginTop; + return this; + } + + /** + * Sets the bottom margin. + * + * @param marginBottom the bottom margin to use. + * @return the builder. + */ + public PageFormatBuilder marginBottom(float marginBottom) { + this.marginBottom = marginBottom; + return this; + } + + /** + * Sets the margins. + * + * @param marginLeft the left margin to use. + * @param marginRight the right margin to use. + * @param marginTop the top margin to use. + * @param marginBottom the bottom margin to use. + * @return the builder. + */ + public PageFormatBuilder margins(float marginLeft, float marginRight, + float marginTop, float marginBottom) { + this.marginLeft = marginLeft; + this.marginRight = marginRight; + this.marginTop = marginTop; + this.marginBottom = marginBottom; + return this; + } + + /** + * Sets the media box to the given size. + * + * @param mediaBox the media box to use. + * @return the builder. + */ + public PageFormatBuilder mediaBox(PDRectangle mediaBox) { + this.mediaBox = mediaBox; + return this; + } + + /** + * Sets the media box to size {@link Constants#A0}. + * + * @return the builder. + */ + public PageFormatBuilder A0() { + this.mediaBox = Constants.A0; + return this; + } + + /** + * Sets the media box to size {@link Constants#A1}. + * + * @return the builder. + */ + public PageFormatBuilder A1() { + this.mediaBox = Constants.A1; + return this; + } + + /** + * Sets the media box to size {@link Constants#A2}. + * + * @return the builder. + */ + public PageFormatBuilder A2() { + this.mediaBox = Constants.A2; + return this; + } + + /** + * Sets the media box to size {@link Constants#A3}. + * + * @return the builder. + */ + public PageFormatBuilder A3() { + this.mediaBox = Constants.A3; + return this; + } + + /** + * Sets the media box to size {@link Constants#A4}. + * + * @return the builder. + */ + public PageFormatBuilder A4() { + this.mediaBox = Constants.A4; + return this; + } + + /** + * Sets the media box to size {@link Constants#A5}. + * + * @return the builder. + */ + public PageFormatBuilder A5() { + this.mediaBox = Constants.A5; + return this; + } + + /** + * Sets the media box to size {@link Constants#A6}. + * + * @return the builder. + */ + public PageFormatBuilder A6() { + this.mediaBox = Constants.A6; + return this; + } + + /** + * Sets the media box to size {@link Constants#Letter}. + * + * @return the builder. + */ + public PageFormatBuilder letter() { + this.mediaBox = Constants.Letter; + return this; + } + + /** + * Sets the orientation to the given one. + * + * @param orientation the orientation to use. + * @return the builder. + */ + public PageFormatBuilder orientation(Orientation orientation) { + this.orientation = orientation; + return this; + } + + /** + * Sets the orientation to {@link Orientation#Portrait}. + * + * @return the builder. + */ + public PageFormatBuilder portrait() { + this.orientation = Orientation.Portrait; + return this; + } + + /** + * Sets the orientation to {@link Orientation#Landscape}. + * + * @return the builder. + */ + public PageFormatBuilder landscape() { + this.orientation = Orientation.Landscape; + return this; + } + + /** + * Sets the rotation to apply to the page after rendering. + * + * @param angle the angle to rotate. + * @return the builder. + */ + public PageFormatBuilder rotation(int angle) { + this.rotation = angle; + return this; + } + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Paragraph.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Paragraph.java new file mode 100644 index 0000000..6e9fdc6 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Paragraph.java @@ -0,0 +1,85 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.TextFlow; +import org.xbib.graphics.layout.pdfbox.text.TextSequenceUtil; +import org.xbib.graphics.layout.pdfbox.text.WidthRespecting; +import java.io.IOException; + +/** + * A paragraph is used as a container for {@link TextFlow text} that is drawn as + * one element. A paragraph has a {@link #setAlignment(Alignment) (text-) + * alignment}, and {@link WidthRespecting respects a given width} by applying + * word-wrap. + */ +public class Paragraph extends TextFlow implements Drawable, Element, + WidthRespecting, Dividable { + + private Position absolutePosition; + private Alignment alignment = Alignment.Left; + + @Override + public Position getAbsolutePosition() { + return absolutePosition; + } + + /** + * Sets the absolute position to render at. + * + * @param absolutePosition the absolute position. + */ + public void setAbsolutePosition(Position absolutePosition) { + this.absolutePosition = absolutePosition; + } + + /** + * @return the text alignment to apply. Default is left. + */ + public Alignment getAlignment() { + return alignment; + } + + /** + * Sets the alignment to apply. + * + * @param alignment the text alignment. + */ + public void setAlignment(Alignment alignment) { + this.alignment = alignment; + } + + @Override + public void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, DrawListener drawListener) throws IOException { + drawText(contentStream, upperLeft, getAlignment(), drawListener); + } + + @Override + public Divided divide(float remainingHeight, final float pageHeight) + throws IOException { + return TextSequenceUtil.divide(this, getMaxWidth(), remainingHeight); + } + + @Override + public Paragraph removeLeadingEmptyVerticalSpace() throws IOException { + return removeLeadingEmptyLines(); + } + + @Override + public Paragraph removeLeadingEmptyLines() throws IOException { + Paragraph result = (Paragraph) super.removeLeadingEmptyLines(); + result.setAbsolutePosition(this.getAbsolutePosition()); + result.setAlignment(this.getAlignment()); + return result; + } + + @Override + protected Paragraph createInstance() { + return new Paragraph(); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/PositionControl.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/PositionControl.java new file mode 100644 index 0000000..97a9b03 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/PositionControl.java @@ -0,0 +1,103 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +/** + * Utility class to create elements that allow the manipulation of the current + * layout position. Use carefully. + */ +public class PositionControl extends ControlElement { + + /** + * Use this value in {@link #createSetPosition(Float, Float)} to reset + * either one or both coordinates to the marked position. + */ + public static final Float MARKED_POSITION = Float.NEGATIVE_INFINITY; + + /** + * Add this element to a document to mark the current position. + * + * @return the created element + */ + public static MarkPosition createMarkPosition() { + return new MarkPosition(); + } + + /** + * Add this element to a document to manipulate the current layout position. + * If null, the position won't be changed (useful if you want + * to change only X or Y). If the value is {@link #MARKED_POSITION}, it wil + * be (re-)set to the marked position. + * + * @param newX the new X position. + * @param newY new new Y position. + * @return the created element + */ + public static SetPosition createSetPosition(final Float newX, + final Float newY) { + return new SetPosition(newX, newY); + } + + /** + * Add this element to a document to manipulate the current layout position + * by a relative amount. If null, the position won't be changed + * (useful if you want to change only X or Y). + * + * @param relativeX the value to change position in X direction. + * @param relativeY the value to change position in Y direction. + * @return the created element + */ + public static MovePosition createMovePosition(final float relativeX, + final float relativeY) { + return new MovePosition(relativeX, relativeY); + } + + public static class MarkPosition extends PositionControl { + private MarkPosition() { + super("MARK_POSITION"); + } + } + + public static class SetPosition extends PositionControl { + private final Float newX; + private final Float newY; + + private SetPosition(final Float newX, final Float newY) { + super(String.format("SET_POSITION x:%f, y%f", newX, newY)); + this.newX = newX; + this.newY = newY; + } + + public Float getX() { + return newX; + } + + public Float getY() { + return newY; + } + + } + + public static class MovePosition extends PositionControl { + private final float relativeX; + private final float relativeY; + + private MovePosition(final float relativeX, final float relativeY) { + super(String.format("SET_POSITION x:%f, y%f", relativeX, relativeY)); + this.relativeX = relativeX; + this.relativeY = relativeY; + } + + public float getX() { + return relativeX; + } + + public float getY() { + return relativeY; + } + + } + + private PositionControl(String name) { + super(name); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Rectangle.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Rectangle.java new file mode 100644 index 0000000..825da6a --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/Rectangle.java @@ -0,0 +1,58 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +/** + * In order to avoid dependencies to AWT, we use our own Rectangle class here. + */ +public class Rectangle extends Dimension { + + private final float x; + private final float y; + + public Rectangle(float x, float y, float width, float height) { + super(width, height); + this.x = x; + this.y = y; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + @Override + public String toString() { + return "Rectangle [x=" + x + ", y=" + y + ", width=" + getWidth() + + ", height=" + getHeight() + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Float.floatToIntBits(x); + result = prime * result + Float.floatToIntBits(y); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Rectangle other = (Rectangle) obj; + if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) { + return false; + } + return Float.floatToIntBits(y) == Float.floatToIntBits(other.y); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/VerticalSpacer.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/VerticalSpacer.java new file mode 100644 index 0000000..fb24a92 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/VerticalSpacer.java @@ -0,0 +1,61 @@ +package org.xbib.graphics.layout.pdfbox.elements; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.IOException; + +/** + * A drawable element that occupies some vertical space without any graphical + * representation. + */ +public class VerticalSpacer implements Drawable, Element, Dividable { + + private final float height; + + /** + * Creates a vertical space with the given height. + * + * @param height the height of the space. + */ + public VerticalSpacer(float height) { + this.height = height; + } + + @Override + public float getWidth() throws IOException { + return 0; + } + + @Override + public float getHeight() throws IOException { + return height; + } + + @Override + public Position getAbsolutePosition() { + return null; + } + + @Override + public void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, DrawListener drawListener) throws IOException { + if (drawListener != null) { + drawListener.drawn(this, upperLeft, getWidth(), getHeight()); + } + } + + @Override + public Divided divide(float remainingHeight, final float pageHeight) + throws IOException { + return new Divided(new VerticalSpacer(remainingHeight), + new VerticalSpacer(getHeight() - remainingHeight)); + } + + @Override + public Drawable removeLeadingEmptyVerticalSpace() { + return this; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/ColumnLayout.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/ColumnLayout.java new file mode 100644 index 0000000..7f2a721 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/ColumnLayout.java @@ -0,0 +1,93 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.xbib.graphics.layout.pdfbox.elements.ControlElement; +import org.xbib.graphics.layout.pdfbox.elements.Drawable; +import org.xbib.graphics.layout.pdfbox.elements.Element; +import java.io.IOException; + +/** + * The column layout divides the page vertically into columns. You can specify + * the number of columns and the inter-column spacing. The layouting inside a + * column is similar to the {@link VerticalLayout}. See there for more details + * on the possiblities. + */ +public class ColumnLayout extends VerticalLayout { + + /** + * Triggers flip to the next column. + */ + public final static ControlElement NEWCOLUMN = new ControlElement("NEWCOLUMN"); + + + private final int columnCount; + private final float columnSpacing; + private int columnIndex = 0; + private Float offsetY = null; + + + public ColumnLayout(int columnCount) { + this(columnCount, 0); + } + + public ColumnLayout(int columnCount, float columnSpacing) { + this.columnCount = columnCount; + this.columnSpacing = columnSpacing; + } + + @Override + protected float getTargetWidth(final RenderContext renderContext) { + return (renderContext.getWidth() - ((columnCount - 1) * columnSpacing)) + / columnCount; + } + + /** + * Flips to the next column + */ + @Override + protected void turnPage(final RenderContext renderContext) + throws IOException { + if (++columnIndex >= columnCount) { + renderContext.newPage(); + columnIndex = 0; + offsetY = 0f; + } else { + float nextColumnX = (getTargetWidth(renderContext) + columnSpacing) + * columnIndex; + renderContext.resetPositionToUpperLeft(); + renderContext.movePositionBy(nextColumnX, -offsetY); + } + } + + @Override + public boolean render(RenderContext renderContext, Element element, + LayoutHint layoutHint) throws IOException { + if (element == ControlElement.NEWPAGE) { + renderContext.newPage(); + return true; + } + if (element == NEWCOLUMN) { + turnPage(renderContext); + return true; + } + return super.render(renderContext, element, layoutHint); + } + + @Override + public void render(RenderContext renderContext, Drawable drawable, + LayoutHint layoutHint) throws IOException { + if (offsetY == null) { + offsetY = renderContext.getUpperLeft().getY() - renderContext.getCurrentPosition().getY(); + } + super.render(renderContext, drawable, layoutHint); + } + + @Override + protected boolean isPositionTopOfPage(final RenderContext renderContext) { + float topPosition = renderContext.getUpperLeft().getY(); + if (offsetY != null) { + topPosition -= offsetY; + } + return renderContext.getCurrentPosition().getY() == topPosition; + } + +} \ No newline at end of file diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/ColumnLayoutHint.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/ColumnLayoutHint.java new file mode 100644 index 0000000..0febecd --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/ColumnLayoutHint.java @@ -0,0 +1,86 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.xbib.graphics.layout.pdfbox.text.Alignment; + +/** + * The column layout hint provides currently the same possibilities as the + * {@link VerticalLayoutHint}. See there for more details. + */ +public class ColumnLayoutHint extends VerticalLayoutHint { + + public final static ColumnLayoutHint LEFT = new ColumnLayoutHint( + Alignment.Left); + public final static ColumnLayoutHint CENTER = new ColumnLayoutHint( + Alignment.Center); + public final static ColumnLayoutHint RIGHT = new ColumnLayoutHint( + Alignment.Right); + + /** + * Creates a layout hint with {@link Alignment#Left left alignment}. + */ + public ColumnLayoutHint() { + super(); + } + + /** + * Creates a layout hint with the given alignment. + * + * @param alignment the element alignment. + */ + public ColumnLayoutHint(Alignment alignment) { + super(alignment); + } + + /** + * Creates a layout hint with the given alignment and margins. + * + * @param alignment the element alignment. + * @param marginLeft the left alignment. + * @param marginRight the right alignment. + * @param marginTop the top alignment. + * @param marginBottom the bottom alignment. + */ + public ColumnLayoutHint(Alignment alignment, float marginLeft, + float marginRight, float marginTop, float marginBottom) { + super(alignment, marginLeft, marginRight, marginTop, marginBottom); + } + + /** + * Creates a layout hint with the given alignment and margins. + * + * @param alignment the element alignment. + * @param marginLeft the left alignment. + * @param marginRight the right alignment. + * @param marginTop the top alignment. + * @param marginBottom the bottom alignment. + * @param resetY if true, the y coordinate will be reset to the + * point before layouting the element. + */ + public ColumnLayoutHint(Alignment alignment, float marginLeft, + float marginRight, float marginTop, float marginBottom, + boolean resetY) { + super(alignment, marginLeft, marginRight, marginTop, marginBottom, + resetY); + } + + + /** + * @return a {@link VerticalLayoutHintBuilder} for creating a + * {@link VerticalLayoutHint} using a fluent API. + */ + public static ColumnLayoutHintBuilder builder() { + return new ColumnLayoutHintBuilder(); + } + + /** + * A builder for creating a {@link VerticalLayoutHint} using a fluent API. + */ + public static class ColumnLayoutHintBuilder extends VerticalLayoutHintBuilder { + + public ColumnLayoutHint build() { + return new ColumnLayoutHint(alignment, marginLeft, marginRight, + marginTop, marginBottom, resetY); + } + + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/Layout.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/Layout.java new file mode 100644 index 0000000..686cbf1 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/Layout.java @@ -0,0 +1,11 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.xbib.graphics.layout.pdfbox.elements.Element; + +/** + * A layout is used to size and position the elements of a document according to + * a specific strategy. + */ +public interface Layout extends Element, Renderer { + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/LayoutHint.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/LayoutHint.java new file mode 100644 index 0000000..d15b80f --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/LayoutHint.java @@ -0,0 +1,14 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Element; + +/** + * Each element in a document is + * {@link Document#add(Element, LayoutHint) + * accompanied} by a hint, which gives some notes to the current layout on how + * to layout the element. + */ +public interface LayoutHint { + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/RenderContext.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/RenderContext.java new file mode 100644 index 0000000..a30cd6c --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/RenderContext.java @@ -0,0 +1,462 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.xbib.graphics.layout.pdfbox.elements.ControlElement; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Element; +import org.xbib.graphics.layout.pdfbox.elements.Orientation; +import org.xbib.graphics.layout.pdfbox.elements.PageFormat; +import org.xbib.graphics.layout.pdfbox.elements.PositionControl; +import org.xbib.graphics.layout.pdfbox.elements.PositionControl.MarkPosition; +import org.xbib.graphics.layout.pdfbox.elements.PositionControl.MovePosition; +import org.xbib.graphics.layout.pdfbox.elements.PositionControl.SetPosition; +import org.xbib.graphics.layout.pdfbox.text.DrawContext; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationDrawListener; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.io.Closeable; +import java.io.IOException; + +/** + * The render context is a container providing all state of the current + * rendering process. + */ +public class RenderContext implements Renderer, Closeable, DrawContext, DrawListener { + + private final Document document; + private final PDDocument pdDocument; + private PDPage page; + private int pageIndex = 0; + private PDPageContentStream contentStream; + private Position currentPosition; + private Position markedPosition; + private Position maxPositionOnPage; + private Layout layout = new VerticalLayout(); + + private PageFormat nextPageFormat; + private PageFormat pageFormat; + + private final AnnotationDrawListener annotationDrawListener; + + /** + * Creates a render context. + * + * @param document the document to render. + * @param pdDocument the underlying pdfbox document. + * @throws IOException by pdfbox. + */ + public RenderContext(Document document, PDDocument pdDocument) + throws IOException { + this.document = document; + this.pdDocument = pdDocument; + this.pageFormat = document.getPageFormat(); + this.annotationDrawListener = new AnnotationDrawListener(this); + newPage(); + } + + /** + * @return the current {@link Layout} used for rendering. + */ + public Layout getLayout() { + return layout; + } + + /** + * Sets the current {@link Layout} used for rendering. + * + * @param layout the new layout. + */ + public void setLayout(Layout layout) { + this.layout = layout; + resetPositionToLeftEndOfPage(); + } + + /** + * @return the orientation to use for the page. If no special + * {@link #setPageFormat(PageFormat) page format} is set, the + * {@link Document#getOrientation() document orientation} is used. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public Orientation getOrientation() { + return getPageFormat().getOrientation(); + } + + /** + * @return the media box to use for the page. If no special + * {@link #setPageFormat(PageFormat) page format} is set, the + * {@link Document#getMediaBox() document media box} is used. + * @deprecated use {@link #getPageFormat()} instead. + */ + @Deprecated + public PDRectangle getMediaBox() { + return getPageFormat().getMediaBox(); + } + + public void setPageFormat(final PageFormat pageFormat) { + if (pageFormat == null) { + this.pageFormat = document.getPageFormat(); + } else { + this.pageFormat = pageFormat; + } + } + + public PageFormat getPageFormat() { + return pageFormat; + } + + /** + * @return the upper left position in the document respecting the + * {@link Document document} margins. + */ + public Position getUpperLeft() { + return new Position(getPageFormat().getMarginLeft(), getPageHeight() + - getPageFormat().getMarginTop()); + } + + /** + * @return the lower right position in the document respecting the + * {@link Document document} margins. + */ + public Position getLowerRight() { + return new Position(getPageWidth() - getPageFormat().getMarginRight(), + getPageFormat().getMarginBottom()); + } + + /** + * @return the current rendering position in pdf coord space (origin in + * lower left corner). + */ + public Position getCurrentPosition() { + return currentPosition; + } + + /** + * @return the {@link PositionControl#MARKED_POSITION}. + */ + public Position getMarkedPosition() { + return markedPosition; + } + + protected void setMarkedPosition(Position markedPosition) { + this.markedPosition = markedPosition; + } + + /** + * Moves the {@link #getCurrentPosition() current position} relatively by + * the given offset. + * + * @param x to move horizontally. + * @param y to move vertically. + */ + public void movePositionBy(final float x, final float y) { + currentPosition = currentPosition.add(x, y); + } + + /** + * Resets the position to {@link #getUpperLeft()}. + */ + public void resetPositionToUpperLeft() { + currentPosition = getUpperLeft(); + } + + /** + * Resets the position to the x of {@link #getUpperLeft()} while keeping the + * current y. + */ + public void resetPositionToLeft() { + currentPosition = new Position(getUpperLeft().getX(), + currentPosition.getY()); + } + + /** + * Resets the position to the x of {@link #getUpperLeft()} and the + * y of {@link #getMaxPositionOnPage()}. + */ + protected void resetPositionToLeftEndOfPage() { + currentPosition = new Position(getUpperLeft().getX(), + getMaxPositionOnPage().getY()); + } + + /** + * @return the orientation of the current page + */ + protected Orientation getPageOrientation() { + if (getPageWidth() > getPageHeight()) { + return Orientation.Landscape; + } + return Orientation.Portrait; + } + + /** + * @return true if the page is rotated by 90/270 degrees. + */ + public boolean isPageTilted() { + return CompatibilityHelper.getPageRotation(page) == 90 + || CompatibilityHelper.getPageRotation(page) == 270; + } + + /** + * @return the page' width, or - if {@link #isPageTilted() rotated} - the + * height. + */ + public float getPageWidth() { + if (isPageTilted()) { + return page.getMediaBox().getHeight(); + } + return page.getMediaBox().getWidth(); + } + + /** + * @return the page' height, or - if {@link #isPageTilted() rotated} - the + * width. + */ + public float getPageHeight() { + if (isPageTilted()) { + return page.getMediaBox().getWidth(); + } + return page.getMediaBox().getHeight(); + } + + /** + * @return the {@link #getPageWidth() width of the page} respecting the + * margins. + */ + public float getWidth() { + return getPageWidth() - getPageFormat().getMarginLeft() + - getPageFormat().getMarginRight(); + } + + /** + * @return the {@link #getPageHeight() height of the page} respecting the + * margins. + */ + public float getHeight() { + return getPageHeight() - getPageFormat().getMarginTop() + - getPageFormat().getMarginBottom(); + } + + /** + * @return the remaining height on the page. + */ + public float getRemainingHeight() { + return getCurrentPosition().getY() - getPageFormat().getMarginBottom(); + } + + /** + * @return the document. + */ + public Document getDocument() { + return document; + } + + /** + * @return the PDDocument. + */ + @Override + public PDDocument getPdDocument() { + return pdDocument; + } + + @Override + public PDPage getCurrentPage() { + return page; + } + + @Override + public PDPageContentStream getCurrentPageContentStream() { + return getContentStream(); + } + + /** + * @return the current PDPage. + */ + @Deprecated + public PDPage getPage() { + return getCurrentPage(); + } + + /** + * @return the current PDPageContentStream. + */ + public PDPageContentStream getContentStream() { + return contentStream; + } + + /** + * @return the current page index (starting from 0). + */ + public int getPageIndex() { + return pageIndex; + } + + @Override + public boolean render(RenderContext renderContext, Element element, + LayoutHint layoutHint) throws IOException { + boolean success = getLayout() + .render(renderContext, element, layoutHint); + if (success) { + return true; + } + if (element == ControlElement.NEWPAGE) { + newPage(); + return true; + } + if (element instanceof PositionControl) { + return render((PositionControl) element); + } + if (element instanceof PageFormat) { + nextPageFormat = (PageFormat) element; + return true; + } + if (element instanceof Layout) { + setLayout((Layout) element); + return true; + } + return false; + } + + protected boolean render(final PositionControl positionControl) { + if (positionControl instanceof MarkPosition) { + setMarkedPosition(getCurrentPosition()); + return true; + } + if (positionControl instanceof SetPosition) { + SetPosition setPosition = (SetPosition) positionControl; + Float x = setPosition.getX(); + if (x == PositionControl.MARKED_POSITION) { + x = getMarkedPosition().getX(); + } + if (x == null) { + x = getCurrentPosition().getX(); + } + Float y = setPosition.getY(); + if (y == PositionControl.MARKED_POSITION) { + y = getMarkedPosition().getY(); + } + if (y == null) { + y = getCurrentPosition().getY(); + } + Position newPosition = new Position(x, y); + currentPosition = newPosition; + return true; + } + if (positionControl instanceof MovePosition) { + MovePosition movePosition = (MovePosition) positionControl; + movePositionBy(movePosition.getX(), movePosition.getY()); + return true; + } + return false; + } + + /** + * Triggers a new page. + * + * @throws IOException by pdfbox + */ + public void newPage() throws IOException { + if (closePage()) { + ++pageIndex; + } + if (nextPageFormat != null) { + setPageFormat(nextPageFormat); + } + + this.page = new PDPage(getPageFormat().getMediaBox()); + this.pdDocument.addPage(page); + this.contentStream = CompatibilityHelper + .createAppendablePDPageContentStream(pdDocument, page); + + // fix orientation + if (getPageOrientation() != getPageFormat().getOrientation()) { + if (isPageTilted()) { + page.setRotation(0); + } else { + page.setRotation(90); + } + } + + if (isPageTilted()) { + CompatibilityHelper.transform(contentStream, 0, 1, -1, 0, + getPageHeight(), 0); + } + + resetPositionToUpperLeft(); + resetMaxPositionOnPage(); + document.beforePage(this); + annotationDrawListener.beforePage(this); + } + + /** + * Closes the current page. + * + * @return true if the current page has not been closed before. + * @throws IOException by pdfbox + */ + public boolean closePage() throws IOException { + if (contentStream != null) { + + annotationDrawListener.afterPage(this); + document.afterPage(this); + + if (getPageFormat().getRotation() != 0) { + int currentRotation = CompatibilityHelper + .getPageRotation(getCurrentPage()); + getCurrentPage().setRotation( + currentRotation + getPageFormat().getRotation()); + } + + contentStream.close(); + contentStream = null; + return true; + } + return false; + } + + @Override + public void close() throws IOException { + closePage(); + annotationDrawListener.afterRender(); + } + + @Override + public void drawn(Object drawnObject, Position upperLeft, float width, + float height) { + updateMaxPositionOnPage(upperLeft, width, height); + annotationDrawListener.drawn(drawnObject, upperLeft, width, height); + } + + /** + * Updates the maximum right resp. bottom position on the page. + * + * @param upperLeft + * @param width + * @param height + */ + protected void updateMaxPositionOnPage(Position upperLeft, float width, + float height) { + maxPositionOnPage = new Position(Math.max(maxPositionOnPage.getX(), + upperLeft.getX() + width), Math.min(maxPositionOnPage.getY(), + upperLeft.getY() - height)); + } + + /** + * Resets the maximumn position to upper left. + */ + protected void resetMaxPositionOnPage() { + maxPositionOnPage = getUpperLeft(); + } + + /** + * @return the maximum right and bottom position of all + * objects rendered on this page so far. + */ + protected Position getMaxPositionOnPage() { + return maxPositionOnPage; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/RenderListener.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/RenderListener.java new file mode 100644 index 0000000..80c8248 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/RenderListener.java @@ -0,0 +1,26 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import java.io.IOException; + +/** + * A render listener is called before and after a page has been rendered. It may + * be used, to perform some custom operations (drawings) to the page. + */ +public interface RenderListener { + + /** + * Called before any rendering is performed to the page. + * + * @param renderContext the context providing all rendering state. + * @throws IOException by pdfbox. + */ + void beforePage(final RenderContext renderContext) throws IOException; + + /** + * Called after any rendering is performed to the page. + * + * @param renderContext the context providing all rendering state. + * @throws IOException by pdfbox. + */ + void afterPage(final RenderContext renderContext) throws IOException; +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/Renderer.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/Renderer.java new file mode 100644 index 0000000..57b4ab8 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/Renderer.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.xbib.graphics.layout.pdfbox.elements.Element; +import java.io.IOException; + +/** + * A renderer is responsible for rendering certain, but not necessarily all + * elements. The boolean return value indicates whether the element could be + * processed by this renderer. + */ +public interface Renderer { + + /** + * Renders an element. + * + * @param renderContext the render context. + * @param element the element to draw. + * @param layoutHint the associated layout hint + * @return true if the layout is able to render the element. + * @throws IOException by pdfbox + */ + boolean render(final RenderContext renderContext, final Element element, + final LayoutHint layoutHint) throws IOException; + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/VerticalLayout.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/VerticalLayout.java new file mode 100644 index 0000000..2e27f50 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/VerticalLayout.java @@ -0,0 +1,294 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.elements.ControlElement; +import org.xbib.graphics.layout.pdfbox.elements.Cutter; +import org.xbib.graphics.layout.pdfbox.elements.Dividable; +import org.xbib.graphics.layout.pdfbox.elements.Drawable; +import org.xbib.graphics.layout.pdfbox.elements.Element; +import org.xbib.graphics.layout.pdfbox.elements.PageFormat; +import org.xbib.graphics.layout.pdfbox.elements.VerticalSpacer; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.WidthRespecting; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.io.IOException; + +/** + * Layout implementation that stacks drawables vertically onto the page. If the + * remaining height on the page is not sufficient for the drawable, it will be + * {@link Dividable divided}. Any given {@link VerticalLayoutHint} will be taken + * into account to calculate the position, width, alignment etc. + */ +public class VerticalLayout implements Layout { + + protected boolean removeLeadingEmptyVerticalSpace = true; + + /** + * See {@link Drawable#removeLeadingEmptyVerticalSpace()} + * + * @return true if empty space (e.g. empty lines) should be + * removed at the begin of a page. + */ + public boolean isRemoveLeadingEmptyVerticalSpace() { + return removeLeadingEmptyVerticalSpace; + } + + /** + * Indicates if empty space (e.g. empty lines) should be removed at the + * begin of a page. See {@link Drawable#removeLeadingEmptyVerticalSpace()} + * + * @param removeLeadingEmptyLines true if space should be removed. + */ + public void setRemoveLeadingEmptyVerticalSpace( + boolean removeLeadingEmptyLines) { + this.removeLeadingEmptyVerticalSpace = removeLeadingEmptyLines; + } + + /** + * Turns to the next area, usually a page. + * + * @param renderContext the render context. + * @throws IOException by pdfbox. + */ + protected void turnPage(final RenderContext renderContext) + throws IOException { + renderContext.newPage(); + } + + /** + * @param renderContext the render context. + * @return the target width to draw to. + */ + protected float getTargetWidth(final RenderContext renderContext) { + float targetWidth = renderContext.getWidth(); + return targetWidth; + } + + @Override + public boolean render(RenderContext renderContext, Element element, + LayoutHint layoutHint) throws IOException { + if (element instanceof Drawable) { + render(renderContext, (Drawable) element, layoutHint); + return true; + } + if (element == ControlElement.NEWPAGE) { + turnPage(renderContext); + return true; + } + + return false; + } + + public void render(final RenderContext renderContext, Drawable drawable, + final LayoutHint layoutHint) throws IOException { + if (drawable.getAbsolutePosition() != null) { + renderAbsolute(renderContext, drawable, layoutHint, + drawable.getAbsolutePosition()); + } else { + renderReleative(renderContext, drawable, layoutHint); + } + } + + /** + * Draws at the given position, ignoring all layouting rules. + * + * @param renderContext the context providing all rendering state. + * @param drawable the drawable to draw. + * @param layoutHint the layout hint used to layout. + * @param position the left upper position to start drawing at. + * @throws IOException by pdfbox + */ + protected void renderAbsolute(final RenderContext renderContext, + Drawable drawable, final LayoutHint layoutHint, + final Position position) throws IOException { + drawable.draw(renderContext.getPdDocument(), + renderContext.getContentStream(), position, renderContext); + } + + /** + * Renders the drawable at the {@link RenderContext#getCurrentPosition() + * current position}. This method is responsible taking any top or bottom + * margin described by the (Vertical-)LayoutHint into account. The actual + * rendering of the drawable is performed by + * {@link #layoutAndDrawReleative(RenderContext, Drawable, LayoutHint)}. + * + * @param renderContext the context providing all rendering state. + * @param drawable the drawable to draw. + * @param layoutHint the layout hint used to layout. + * @throws IOException by pdfbox + */ + protected void renderReleative(final RenderContext renderContext, + Drawable drawable, final LayoutHint layoutHint) throws IOException { + VerticalLayoutHint verticalLayoutHint = null; + if (layoutHint instanceof VerticalLayoutHint) { + verticalLayoutHint = (VerticalLayoutHint) layoutHint; + if (verticalLayoutHint.getMarginTop() > 0) { + layoutAndDrawReleative(renderContext, new VerticalSpacer( + verticalLayoutHint.getMarginTop()), verticalLayoutHint); + } + } + + layoutAndDrawReleative(renderContext, drawable, verticalLayoutHint); + + if (verticalLayoutHint != null) { + if (verticalLayoutHint.getMarginBottom() > 0) { + layoutAndDrawReleative(renderContext, new VerticalSpacer( + verticalLayoutHint.getMarginBottom()), + verticalLayoutHint); + } + } + } + + /** + * Adjusts the width of the drawable (if it is {@link WidthRespecting}), and + * divides it onto multiple pages if necessary. Actual drawing is delegated + * to + * {@link #drawReletivePartAndMovePosition(RenderContext, Drawable, LayoutHint, boolean)} + * . + * + * @param renderContext the context providing all rendering state. + * @param drawable the drawable to draw. + * @param layoutHint the layout hint used to layout. + * @throws IOException by pdfbox + */ + protected void layoutAndDrawReleative(final RenderContext renderContext, + Drawable drawable, final LayoutHint layoutHint) throws IOException { + + float targetWidth = getTargetWidth(renderContext); + boolean movePosition = true; + VerticalLayoutHint verticalLayoutHint = null; + if (layoutHint instanceof VerticalLayoutHint) { + verticalLayoutHint = (VerticalLayoutHint) layoutHint; + targetWidth -= verticalLayoutHint.getMarginLeft(); + targetWidth -= verticalLayoutHint.getMarginRight(); + movePosition = !verticalLayoutHint.isResetY(); + } + + float oldMaxWidth = -1; + if (drawable instanceof WidthRespecting) { + WidthRespecting flowing = (WidthRespecting) drawable; + oldMaxWidth = flowing.getMaxWidth(); + if (oldMaxWidth < 0) { + flowing.setMaxWidth(targetWidth); + } + } + + Drawable drawablePart = removeLeadingEmptyVerticalSpace(drawable, + renderContext); + while (renderContext.getRemainingHeight() < drawablePart.getHeight()) { + Dividable dividable = null; + if (drawablePart instanceof Dividable) { + dividable = (Dividable) drawablePart; + } else { + dividable = new Cutter(drawablePart); + } + Dividable.Divided divided = dividable.divide( + renderContext.getRemainingHeight(), + renderContext.getHeight()); + drawReletivePartAndMovePosition(renderContext, divided.getFirst(), + layoutHint, true); + + // new page + turnPage(renderContext); + + drawablePart = divided.getTail(); + drawablePart = removeLeadingEmptyVerticalSpace(drawablePart, + renderContext); + } + + drawReletivePartAndMovePosition(renderContext, drawablePart, + layoutHint, movePosition); + + if (drawable instanceof WidthRespecting) { + if (oldMaxWidth < 0) { + ((WidthRespecting) drawable).setMaxWidth(oldMaxWidth); + } + } + } + + /** + * Actually draws the (drawble) part at the + * {@link RenderContext#getCurrentPosition()} and - depending on flag + * movePosition - moves to the new Y position. Any left or + * right margin is taken into account to calculate the position and + * alignment. + * + * @param renderContext the context providing all rendering state. + * @param drawable the drawable to draw. + * @param layoutHint the layout hint used to layout. + * @param movePosition indicates if the position should be moved (vertically) after + * drawing. + * @throws IOException by pdfbox + */ + protected void drawReletivePartAndMovePosition( + final RenderContext renderContext, Drawable drawable, + final LayoutHint layoutHint, final boolean movePosition) + throws IOException { + PDPageContentStream contentStream = renderContext.getContentStream(); + PageFormat pageFormat = renderContext.getPageFormat(); + float offsetX = 0; + if (layoutHint instanceof VerticalLayoutHint) { + VerticalLayoutHint verticalLayoutHint = (VerticalLayoutHint) layoutHint; + Alignment alignment = verticalLayoutHint.getAlignment(); + float horizontalExtraSpace = getTargetWidth(renderContext) + - drawable.getWidth(); + switch (alignment) { + case Right: + offsetX = horizontalExtraSpace + - verticalLayoutHint.getMarginRight(); + break; + case Center: + offsetX = horizontalExtraSpace / 2f; + break; + default: + offsetX = verticalLayoutHint.getMarginLeft(); + break; + } + } + + contentStream.saveGraphicsState(); + contentStream.addRect(0, pageFormat.getMarginBottom(), renderContext.getPageWidth(), + renderContext.getHeight()); + CompatibilityHelper.clip(contentStream); + + drawable.draw(renderContext.getPdDocument(), contentStream, + renderContext.getCurrentPosition().add(offsetX, 0), renderContext); + + contentStream.restoreGraphicsState(); + + if (movePosition) { + renderContext.movePositionBy(0, -drawable.getHeight()); + } + } + + /** + * Indicates if the current position is the top of page. + * + * @param renderContext the render context. + * @return true if the current position is top of page. + */ + protected boolean isPositionTopOfPage(final RenderContext renderContext) { + return renderContext.getCurrentPosition().getY() == renderContext + .getUpperLeft().getY(); + } + + /** + * Removes empty space (e.g. empty lines) at the begin of a page. See + * {@link Drawable#removeLeadingEmptyVerticalSpace()} + * + * @param drawable the drawable to process. + * @param renderContext the render context. + * @return the processed drawable + * @throws IOException by pdfbox + */ + protected Drawable removeLeadingEmptyVerticalSpace(final Drawable drawable, + final RenderContext renderContext) throws IOException { + if (isRemoveLeadingEmptyVerticalSpace() + && isPositionTopOfPage(renderContext)) { + return drawable.removeLeadingEmptyVerticalSpace(); + } + return drawable; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/VerticalLayoutHint.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/VerticalLayoutHint.java new file mode 100644 index 0000000..64af7fd --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/elements/render/VerticalLayoutHint.java @@ -0,0 +1,180 @@ +package org.xbib.graphics.layout.pdfbox.elements.render; + +import org.xbib.graphics.layout.pdfbox.text.Alignment; + +/** + * Layout hint for the {@link VerticalLayout}. You may specify margins to define + * some extra space around the drawable. If there is still some extra space + * available vertically, the alignment decides where to position the drawable. + * The {@link #isResetY() reset Y} indicates if the Y postion should be reset to + * the value before drawing. Be aware that this only applies to the current page + * where the remainder of the element has been drawn to. Means, if the elemenent + * spawns multiple pages, the position is reset to the begin of the last page. + */ +public class VerticalLayoutHint implements LayoutHint { + + public final static VerticalLayoutHint LEFT = new VerticalLayoutHint( + Alignment.Left); + public final static VerticalLayoutHint CENTER = new VerticalLayoutHint( + Alignment.Center); + public final static VerticalLayoutHint RIGHT = new VerticalLayoutHint( + Alignment.Right); + + private final Alignment alignment; + private final float marginLeft; + private final float marginRight; + private final float marginTop; + private final float marginBottom; + private final boolean resetY; + + /** + * Creates a layout hint with {@link Alignment#Left left alignment}. + */ + public VerticalLayoutHint() { + this(Alignment.Left); + } + + /** + * Creates a layout hint with the given alignment. + * + * @param alignment the element alignment. + */ + public VerticalLayoutHint(Alignment alignment) { + this(alignment, 0, 0, 0, 0); + } + + /** + * Creates a layout hint with the given alignment and margins. + * + * @param alignment the element alignment. + * @param marginLeft the left alignment. + * @param marginRight the right alignment. + * @param marginTop the top alignment. + * @param marginBottom the bottom alignment. + */ + public VerticalLayoutHint(Alignment alignment, float marginLeft, + float marginRight, float marginTop, float marginBottom) { + this(alignment, marginLeft, marginRight, marginTop, marginBottom, false); + } + + /** + * Creates a layout hint with the given alignment and margins. + * + * @param alignment the element alignment. + * @param marginLeft the left alignment. + * @param marginRight the right alignment. + * @param marginTop the top alignment. + * @param marginBottom the bottom alignment. + * @param resetY if true, the y coordinate will be reset to the + * point before layouting the element. + */ + public VerticalLayoutHint(Alignment alignment, float marginLeft, + float marginRight, float marginTop, float marginBottom, + boolean resetY) { + this.alignment = alignment; + this.marginLeft = marginLeft; + this.marginRight = marginRight; + this.marginTop = marginTop; + this.marginBottom = marginBottom; + this.resetY = resetY; + } + + public Alignment getAlignment() { + return alignment; + } + + public float getMarginLeft() { + return marginLeft; + } + + public float getMarginRight() { + return marginRight; + } + + public float getMarginTop() { + return marginTop; + } + + public float getMarginBottom() { + return marginBottom; + } + + public boolean isResetY() { + return resetY; + } + + @Override + public String toString() { + return "VerticalLayoutHint [alignment=" + alignment + ", marginLeft=" + + marginLeft + ", marginRight=" + marginRight + ", marginTop=" + + marginTop + ", marginBottom=" + marginBottom + ", resetY=" + + resetY + "]"; + } + + /** + * @return a {@link VerticalLayoutHintBuilder} for creating a + * {@link VerticalLayoutHint} using a fluent API. + */ + public static VerticalLayoutHintBuilder builder() { + return new VerticalLayoutHintBuilder(); + } + + /** + * A builder for creating a + * {@link VerticalLayoutHint} using a fluent API. + */ + public static class VerticalLayoutHintBuilder { + protected Alignment alignment = Alignment.Left; + protected float marginLeft = 0; + protected float marginRight = 0; + protected float marginTop = 0; + protected float marginBottom = 0; + protected boolean resetY = false; + + public VerticalLayoutHintBuilder alignment(final Alignment alignment) { + this.alignment = alignment; + return this; + } + + public VerticalLayoutHintBuilder marginLeft(final float marginLeft) { + this.marginLeft = marginLeft; + return this; + } + + public VerticalLayoutHintBuilder marginRight(final float marginRight) { + this.marginRight = marginRight; + return this; + } + + public VerticalLayoutHintBuilder marginTop(final float marginTop) { + this.marginTop = marginTop; + return this; + } + + public VerticalLayoutHintBuilder marginBottom(final float marginBottom) { + this.marginBottom = marginBottom; + return this; + } + + public VerticalLayoutHintBuilder margins(float marginLeft, + float marginRight, float marginTop, float marginBottom) { + this.marginLeft = marginLeft; + this.marginRight = marginRight; + this.marginTop = marginTop; + this.marginBottom = marginBottom; + return this; + } + + public VerticalLayoutHintBuilder resetY(final boolean resetY) { + this.resetY = resetY; + return this; + } + + public VerticalLayoutHint build() { + return new VerticalLayoutHint(alignment, marginLeft, marginRight, + marginTop, marginBottom, resetY); + } + + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/AbstractShape.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/AbstractShape.java new file mode 100644 index 0000000..684ca97 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/AbstractShape.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.layout.pdfbox.shape; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.awt.Color; +import java.io.IOException; + +/** + * Abstract base class for shapes which performs the + * {@link #fill(PDDocument, PDPageContentStream, Position, float, float, Color, DrawListener)} + * and (@link + * {@link #draw(PDDocument, PDPageContentStream, Position, float, float, Color, Stroke, DrawListener)} + * . + */ +public abstract class AbstractShape implements Shape { + + @Override + public void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, float width, float height, Color color, + Stroke stroke, DrawListener drawListener) throws IOException { + + add(pdDocument, contentStream, upperLeft, width, height); + + if (stroke != null) { + stroke.applyTo(contentStream); + } + if (color != null) { + contentStream.setStrokingColor(color); + } + contentStream.stroke(); + + if (drawListener != null) { + drawListener.drawn(this, upperLeft, width, height); + } + + } + + @Override + public void fill(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, float width, float height, Color color, + DrawListener drawListener) throws IOException { + + add(pdDocument, contentStream, upperLeft, width, height); + + if (color != null) { + contentStream.setNonStrokingColor(color); + } + CompatibilityHelper.fillNonZero(contentStream); + + if (drawListener != null) { + drawListener.drawn(this, upperLeft, width, height); + } + + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Ellipse.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Ellipse.java new file mode 100644 index 0000000..92ed3be --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Ellipse.java @@ -0,0 +1,26 @@ +package org.xbib.graphics.layout.pdfbox.shape; + +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.IOException; + +/** + * Shapes an ellipse, or a circle if width==height. + */ +public class Ellipse extends RoundRect { + + /** + * Default constructor. + */ + public Ellipse() { + super(0); + } + + @Override + protected void addRoundRect(PDPageContentStream contentStream, + Position upperLeft, float width, float height, float cornerRadiusX, + float cornerRadiusY) throws IOException { + super.addRoundRect(contentStream, upperLeft, width, height, width / 2f, + height / 2); + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Rect.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Rect.java new file mode 100644 index 0000000..8d69db1 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Rect.java @@ -0,0 +1,20 @@ +package org.xbib.graphics.layout.pdfbox.shape; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.IOException; + +/** + * A simple rectangular shape. + */ +public class Rect extends AbstractShape { + + @Override + public void add(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, float width, float height) throws IOException { + contentStream.addRect(upperLeft.getX(), upperLeft.getY() - height, + width, height); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/RoundRect.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/RoundRect.java new file mode 100644 index 0000000..b961ba8 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/RoundRect.java @@ -0,0 +1,126 @@ +package org.xbib.graphics.layout.pdfbox.shape; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.io.IOException; + +/** + * A rectangular shape with rounded corners. + */ +public class RoundRect extends AbstractShape { + + private final static float BEZ = 0.551915024494f; + + private final float cornerRadiusX; + private final float cornerRadiusY; + + /** + * Creates a rounded rect with equal radiuss for both x-axis and y-axis (quarter of a circle). + * + * @param cornerRadius the radius of the corner circle. + */ + public RoundRect(float cornerRadius) { + this(cornerRadius, cornerRadius); + } + + /** + * Creates a rounded rect with potentially different radiuss for both x-axis and y-axis (quarter of an ellipse). + * + * @param cornerRadiusX the radius in x-direction of the corner ellipse. + * @param cornerRadiusY the radius in y-direction of the corner ellipse. + */ + public RoundRect(float cornerRadiusX, float cornerRadiusY) { + this.cornerRadiusX = cornerRadiusX; + this.cornerRadiusY = cornerRadiusY; + } + + @Override + public void add(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, float width, float height) throws IOException { + + addRoundRect(contentStream, upperLeft, width, height, cornerRadiusX, cornerRadiusY); + } + + /** + * create points clockwise starting in upper left corner + * + *
      +     *     a          b
      +     *      ----------
      +     *     /          \
      +     *  h |            | c
      +     *    |            |
      +     *    |            |
      +     *   g \          / d
      +     *      ----------
      +     *     f          e
      +     * 
      + * + * @param contentStream the content stream. + * @param upperLeft the upper left point + * @param width the width + * @param height the height + * @param cornerRadiusX the corner radius in x direction + * @param cornerRadiusY the corner radius in y direction + * @throws IOException by pdfbox + */ + protected void addRoundRect(PDPageContentStream contentStream, + Position upperLeft, float width, float height, float cornerRadiusX, + float cornerRadiusY) throws IOException { + float nettoWidth = width - 2 * cornerRadiusX; + float nettoHeight = height - 2 * cornerRadiusY; + + // top line + Position a = new Position(upperLeft.getX() + cornerRadiusX, + upperLeft.getY()); + Position b = new Position(a.getX() + nettoWidth, a.getY()); + // right line + Position c = new Position(upperLeft.getX() + width, upperLeft.getY() + - cornerRadiusY); + Position d = new Position(c.getX(), c.getY() - nettoHeight); + // bottom line + Position e = new Position( + upperLeft.getX() + width - cornerRadiusX, upperLeft.getY() + - height); + Position f = new Position(e.getX() - nettoWidth, e.getY()); + // left line + Position g = new Position(upperLeft.getX(), upperLeft.getY() - height + + cornerRadiusY); + Position h = new Position(g.getX(), upperLeft.getY() + - cornerRadiusY); + + float bezX = cornerRadiusX * BEZ; + float bezY = cornerRadiusY * BEZ; + + contentStream.moveTo(a.getX(), a.getY()); + addLine(contentStream, a.getX(), a.getY(), b.getX(), b.getY()); + CompatibilityHelper.curveTo(contentStream, b.getX() + bezX, b.getY(), c.getX(), + c.getY() + bezY, c.getX(), c.getY()); + // contentStream.addLine(c.getX(), c.getY(), d.getX(), d.getY()); + addLine(contentStream, c.getX(), c.getY(), d.getX(), d.getY()); + CompatibilityHelper.curveTo(contentStream, d.getX(), d.getY() - bezY, e.getX() + bezX, + e.getY(), e.getX(), e.getY()); + // contentStream.addLine(e.getX(), e.getY(), f.getX(), f.getY()); + addLine(contentStream, e.getX(), e.getY(), f.getX(), f.getY()); + CompatibilityHelper.curveTo(contentStream, f.getX() - bezX, f.getY(), g.getX(), + g.getY() - bezY, g.getX(), g.getY()); + addLine(contentStream, g.getX(), g.getY(), h.getX(), h.getY()); + CompatibilityHelper.curveTo(contentStream, h.getX(), h.getY() + bezY, a.getX() - bezX, + a.getY(), a.getX(), a.getY()); + } + + /** + * Using lines won't give us a continuing path, which looks silly on fill. + * So we are approximating lines with bezier curves... is there no better + * way? + */ + private void addLine(final PDPageContentStream contentStream, float x1, + float y1, float x2, float y2) throws IOException { + float xMid = (x1 + x2) / 2f; + float yMid = (y1 + y2) / 2f; + CompatibilityHelper.curveTo1(contentStream, xMid, yMid, x2, y2); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Shape.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Shape.java new file mode 100644 index 0000000..08b41bd --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Shape.java @@ -0,0 +1,71 @@ +package org.xbib.graphics.layout.pdfbox.shape; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.awt.Color; +import java.io.IOException; + +/** + * Shapes can be used to either + * {@link #draw(PDDocument, PDPageContentStream, Position, float, float, Color, Stroke, DrawListener) + * stroke} or + * {@link #fill(PDDocument, PDPageContentStream, Position, float, float, Color, DrawListener) + * fill} the path of the shape, or simply + * {@link #add(PDDocument, PDPageContentStream, Position, float, float) add the + * path} of the shape to the drawing context. + */ +public interface Shape { + + /** + * Draws (strokes) the shape. + * + * @param pdDocument the underlying pdfbox document. + * @param contentStream the stream to draw to. + * @param upperLeft the upper left position to start drawing. + * @param width the width of the bounding box. + * @param height the height of the bounding box. + * @param color the color to use. + * @param stroke the stroke to use. + * @param drawListener the listener to + * {@link DrawListener#drawn(Object, Position, float, float) + * notify} on drawn objects. + * @throws IOException by pdfbox + */ + void draw(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, float width, float height, Color color, + Stroke stroke, DrawListener drawListener) throws IOException; + + /** + * Fills the shape. + * + * @param pdDocument the underlying pdfbox document. + * @param contentStream the stream to draw to. + * @param upperLeft the upper left position to start drawing. + * @param width the width of the bounding box. + * @param height the height of the bounding box. + * @param color the color to use. + * @param drawListener the listener to + * {@link DrawListener#drawn(Object, Position, float, float) + * notify} on drawn objects. + * @throws IOException by pdfbox + */ + void fill(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, float width, float height, Color color, + DrawListener drawListener) throws IOException; + + /** + * Adds (the path of) the shape without drawing anything. + * + * @param pdDocument the underlying pdfbox document. + * @param contentStream the stream to draw to. + * @param upperLeft the upper left position to start drawing. + * @param width the width of the bounding box. + * @param height the height of the bounding box. + * @throws IOException by pdfbox + */ + void add(PDDocument pdDocument, PDPageContentStream contentStream, + Position upperLeft, float width, float height) throws IOException; + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Stroke.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Stroke.java new file mode 100644 index 0000000..6970475 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/shape/Stroke.java @@ -0,0 +1,228 @@ +package org.xbib.graphics.layout.pdfbox.shape; + +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import java.io.IOException; + +/** + * This is a container for all information needed to perform a stroke. + */ +public class Stroke { + + /** + * Enum for the PDF cap styles. + */ + public enum CapStyle { + + Cap(0), RoundCap(1), Square(2); + + private final int value; + + CapStyle(final int value) { + this.value = value; + } + + public int value() { + return value; + } + } + + /** + * Enum for the PDF join styles. + */ + public enum JoinStyle { + + Miter(0), Round(1), Bevel(2); + + private final int value; + + JoinStyle(final int value) { + this.value = value; + } + + public int value() { + return value; + } + } + + /** + * Describes a PDF dash pattern. See the PDF documentation for more + * information on that. + */ + public static class DashPattern { + + private final float[] pattern; + private final float phase; + + /** + * Creates a pattern with equal on and off length, starting with phase + * 0. + * + * @param onOff the length of the on/off part. + */ + public DashPattern(float onOff) { + this(onOff, onOff, 0f); + } + + /** + * Creates a pattern with different on and off length, starting with + * phase 0. + * + * @param on the length of the off part. + * @param off the length of the off part. + */ + public DashPattern(float on, float off) { + this(on, off, 0f); + } + + /** + * Creates a pattern with different on and off length, starting with the + * given phase . + * + * @param on the length of the off part. + * @param off the length of the off part. + * @param phase the phase to start the pattern with. + */ + public DashPattern(float on, float off, float phase) { + this.pattern = new float[]{on, off}; + this.phase = phase; + } + + public float getOn() { + return pattern[0]; + } + + public float getOff() { + return pattern[1]; + } + + public float[] getPattern() { + return pattern; + } + + public float getPhase() { + return phase; + } + + } + + private final CapStyle capStyle; + private final JoinStyle joinStyle; + private final DashPattern dashPattern; + private final float lineWidth; + + /** + * Creates a Stroke with line width 1, cap style + * {@link CapStyle#Cap}, join style {@link JoinStyle#Miter}, and no dash + * pattern. + */ + public Stroke() { + this(1f); + } + + /** + * Creates a Stroke with the given line width, cap style + * {@link CapStyle#Cap}, join style {@link JoinStyle#Miter}, and no dash + * pattern. + * + * @param lineWidth the line width. + */ + public Stroke(float lineWidth) { + this(CapStyle.Cap, JoinStyle.Miter, null, lineWidth); + } + + /** + * Creates a stroke with the given attributes. + * + * @param capStyle the cap style. + * @param joinStyle the join style. + * @param dashPattern the dash pattern. + * @param lineWidth the line width. + */ + public Stroke(CapStyle capStyle, JoinStyle joinStyle, + DashPattern dashPattern, float lineWidth) { + this.capStyle = capStyle; + this.joinStyle = joinStyle; + this.dashPattern = dashPattern; + this.lineWidth = lineWidth; + } + + public CapStyle getCapStyle() { + return capStyle; + } + + public JoinStyle getJoinStyle() { + return joinStyle; + } + + public DashPattern getDashPattern() { + return dashPattern; + } + + public float getLineWidth() { + return lineWidth; + } + + /** + * Applies this stroke to the given content stream. + * + * @param contentStream the content stream to apply this stroke to. + * @throws IOException by PDFBox. + */ + public void applyTo(final PDPageContentStream contentStream) + throws IOException { + if (getCapStyle() != null) { + contentStream.setLineCapStyle(getCapStyle().value()); + } + if (getJoinStyle() != null) { + contentStream.setLineJoinStyle(getJoinStyle().value()); + } + if (getDashPattern() != null) { + contentStream.setLineDashPattern(getDashPattern().getPattern(), + getDashPattern().getPhase()); + } + contentStream.setLineWidth(getLineWidth()); + } + + /** + * Creates a stroke builder providing a fluent interface for creating a stroke. + * + * @return a stroke builder. + */ + public static StrokeBuilder builder() { + return new StrokeBuilder(); + } + + /** + * A builder providing a fluent interface for creating a stroke. + */ + public static class StrokeBuilder { + private CapStyle capStyle = CapStyle.Cap; + private JoinStyle joinStyle = JoinStyle.Miter; + private DashPattern dashPattern; + float lineWidth = 1f; + + public StrokeBuilder capStyle(CapStyle capStyle) { + this.capStyle = capStyle; + return this; + } + + public StrokeBuilder joinStyle(JoinStyle joinStyle) { + this.joinStyle = joinStyle; + return this; + } + + public StrokeBuilder dashPattern(DashPattern dashPattern) { + this.dashPattern = dashPattern; + return this; + } + + public StrokeBuilder lineWidth(float lineWidth) { + this.lineWidth = lineWidth; + return this; + } + + public Stroke build() { + return new Stroke(capStyle, joinStyle, dashPattern, lineWidth); + } + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Alignment.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Alignment.java new file mode 100644 index 0000000..f8cbdcf --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Alignment.java @@ -0,0 +1,9 @@ +package org.xbib.graphics.layout.pdfbox.text; + +/** + * Enumeration for (vertical) alignment. + */ +public enum Alignment { + + Left, Center, Right, Justify +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Area.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Area.java new file mode 100644 index 0000000..9544f38 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Area.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import java.io.IOException; + +/** + * Defines an area with a width and height. + */ +public interface Area { + + /** + * @return the width of the area. + * @throws IOException by pdfbox + */ + float getWidth() throws IOException; + + /** + * @return the height of the area. + * @throws IOException by pdfbox + */ + float getHeight() throws IOException; +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/BaseFont.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/BaseFont.java new file mode 100644 index 0000000..23a944b --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/BaseFont.java @@ -0,0 +1,52 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType1Font; + +/** + * In order to easy handling with fonts, this enum bundles the + * plain/italic/bold/bold-italic variants of the three standard font types + * {@link PDType1Font#TIMES_ROMAN Times},{@link PDType1Font#COURIER Courier} and + * {@link PDType1Font#HELVETICA Helveticy}. + * + * @author Ralf + */ +public enum BaseFont { + + Times(PDType1Font.TIMES_ROMAN, PDType1Font.TIMES_BOLD, + PDType1Font.TIMES_ITALIC, PDType1Font.TIMES_BOLD_ITALIC), // + Courier(PDType1Font.COURIER, PDType1Font.COURIER_BOLD, + PDType1Font.COURIER_OBLIQUE, PDType1Font.COURIER_BOLD_OBLIQUE), // + Helvetica(PDType1Font.HELVETICA, PDType1Font.HELVETICA_BOLD, + PDType1Font.HELVETICA_OBLIQUE, PDType1Font.HELVETICA_BOLD_OBLIQUE); + + private final PDFont plainFont; + private final PDFont boldFont; + private final PDFont italicFont; + private final PDFont boldItalicFont; + + BaseFont(PDFont plainFont, PDFont boldFont, PDFont italicFont, + PDFont boldItalicFont) { + this.plainFont = plainFont; + this.boldFont = boldFont; + this.italicFont = italicFont; + this.boldItalicFont = boldItalicFont; + } + + public PDFont getPlainFont() { + return plainFont; + } + + public PDFont getBoldFont() { + return boldFont; + } + + public PDFont getItalicFont() { + return italicFont; + } + + public PDFont getBoldItalicFont() { + return boldItalicFont; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Constants.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Constants.java new file mode 100644 index 0000000..fc481cb --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Constants.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.common.PDRectangle; + +public class Constants { + + private static final int DEFAULT_USER_SPACE_UNIT_DPI = 72; + private static final float MM_TO_UNITS = 1 / (10 * 2.54f) + * DEFAULT_USER_SPACE_UNIT_DPI; + + public static final PDRectangle A0 = new PDRectangle(841 * MM_TO_UNITS, + 1189 * MM_TO_UNITS); + public static final PDRectangle A1 = new PDRectangle(594 * MM_TO_UNITS, + 841 * MM_TO_UNITS); + public static final PDRectangle A2 = new PDRectangle(420 * MM_TO_UNITS, + 594 * MM_TO_UNITS); + public static final PDRectangle A3 = new PDRectangle(297 * MM_TO_UNITS, + 420 * MM_TO_UNITS); + public static final PDRectangle A4 = new PDRectangle(210 * MM_TO_UNITS, + 297 * MM_TO_UNITS); + public static final PDRectangle A5 = new PDRectangle(148 * MM_TO_UNITS, + 210 * MM_TO_UNITS); + public static final PDRectangle A6 = new PDRectangle(105 * MM_TO_UNITS, + 148 * MM_TO_UNITS); + + public static final PDRectangle Letter = new PDRectangle(215.9f * MM_TO_UNITS, + 279.4f * MM_TO_UNITS); +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlCharacter.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlCharacter.java new file mode 100644 index 0000000..4179be7 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlCharacter.java @@ -0,0 +1,83 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import java.util.regex.Pattern; + +/** + * A control character represents the pattern to match and escape sequence for + * character sequences with a special meaning. Currently there is newline for + * all kinds of text, and bold and italic for markup. + */ +public class ControlCharacter implements CharSequence { + + private final String description; + private final String charaterToEscape; + + protected ControlCharacter(final String description, + final String charaterToEscape) { + this.description = description; + this.charaterToEscape = charaterToEscape; + } + + /** + * @return the character to escape, e.g. '*' for bold. + */ + public String getCharacterToEscape() { + return charaterToEscape; + } + + /** + * @return true if this control character must be escaped in + * text. + */ + public boolean mustEscape() { + return getCharacterToEscape() != null; + } + + /** + * Escapes the control character in the given text if necessary. + * + * @param text the text to escape. + * @return the escaped text. + */ + public String escape(final String text) { + if (!mustEscape()) { + return text; + } + return text.replaceAll(Pattern.quote(getCharacterToEscape()), "\\" + + getCharacterToEscape()); + } + + /** + * Un-escapes the control character in the given text if necessary. + * + * @param text the text to un-escape. + * @return the un-escaped text. + */ + public String unescape(final String text) { + if (!mustEscape()) { + return text; + } + return text.replaceAll("\\\\" + Pattern.quote(getCharacterToEscape()), + getCharacterToEscape()); + } + + @Override + public int length() { + return 0; + } + + @Override + public char charAt(int index) { + throw new ArrayIndexOutOfBoundsException(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return null; + } + + @Override + public String toString() { + return description; + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlCharacters.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlCharacters.java new file mode 100644 index 0000000..bc68dbe --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlCharacters.java @@ -0,0 +1,284 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import java.awt.Color; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Container class for all control character factories. + */ +public class ControlCharacters { + + /** + * Unescapes the escape character backslash. + * + * @param text the text to escape. + * @return the unescaped text. + */ + public static String unescapeBackslash(final String text) { + return text.replaceAll(Pattern.quote("\\\\"), "\\\\"); + } + + /** + * A control character factory is used to create control characters on the + * fly from the control pattern. This allows to parameterize the characters + * as needed for e.g. colors. + */ + public interface ControlCharacterFactory { + + /** + * Creates the control character from the given matched pattern. + * + * @param text the parsed text. + * @param matcher the matcher. + * @param charactersSoFar the characters created so far. + * @return the created character. + */ + ControlCharacter createControlCharacter(final String text, + final Matcher matcher, final List charactersSoFar); + + /** + * @return the pattern used to match the control character. + */ + Pattern getPattern(); + + /** + * Indicates if the pattern should be applied to the begin of line only. + * + * @return true if the pattern is to be applied at the + * begin of a line. + */ + boolean patternMatchesBeginOfLine(); + + /** + * Unescapes the pattern. + * + * @param text the text to unescape. + * @return the unescaped text. + */ + String unescape(final String text); + + } + + /** + * The factory for bold control characters. + */ + public static ControlCharacterFactory BOLD_FACTORY = new StaticControlCharacterFactory( + new BoldControlCharacter(), BoldControlCharacter.PATTERN); + /** + * The factory for italic control characters. + */ + public static ControlCharacterFactory ITALIC_FACTORY = new StaticControlCharacterFactory( + new ItalicControlCharacter(), ItalicControlCharacter.PATTERN); + /** + * The factory for new line control characters. + */ + public static ControlCharacterFactory NEWLINE_FACTORY = new StaticControlCharacterFactory( + new NewLineControlCharacter(), NewLineControlCharacter.PATTERN); + /** + * The factory for color control characters. + */ + public static ControlCharacterFactory COLOR_FACTORY = new ColorControlCharacterFactory(); + + /** + * The factory for metrics control characters. + */ + public static MetricsControlCharacterFactory METRICS_FACTORY = new MetricsControlCharacterFactory(); + + /** + * An asterisk ('*') indicates switching of bold font mode in markup. It can + * be escaped with a backslash ('\'). + */ + public static class BoldControlCharacter extends ControlCharacter { + public static Pattern PATTERN = Pattern + .compile("(?{color:#ee22aa} indicates switching the color in markup, + * where the color is given as hex RGB code (ee22aa in this case). It can be + * escaped with a backslash ('\'). + */ + public static class ColorControlCharacter extends ControlCharacter { + private final Color color; + + protected ColorControlCharacter(final String hex) { + super("COLOR", ColorControlCharacterFactory.TO_ESCAPE); + int r = Integer.parseUnsignedInt(hex.substring(0, 2), 16); + int g = Integer.parseUnsignedInt(hex.substring(2, 4), 16); + int b = Integer.parseUnsignedInt(hex.substring(4, 6), 16); + this.color = new Color(r, g, b); + } + + public Color getColor() { + return color; + } + } + + private static class StaticControlCharacterFactory implements + ControlCharacterFactory { + + private final ControlCharacter controlCharacter; + private final Pattern pattern; + + public StaticControlCharacterFactory( + final ControlCharacter controlCharacter, final Pattern pattern) { + this.controlCharacter = controlCharacter; + this.pattern = pattern; + } + + @Override + public ControlCharacter createControlCharacter(String text, + Matcher matcher, final List charactersSoFar) { + return controlCharacter; + } + + @Override + public Pattern getPattern() { + return pattern; + } + + @Override + public String unescape(String text) { + return controlCharacter.unescape(text); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return false; + } + + } + + private static class ColorControlCharacterFactory implements + ControlCharacterFactory { + + private final static Pattern PATTERN = Pattern + .compile("(? charactersSoFar) { + return new ColorControlCharacter(matcher.group(2)); + } + + @Override + public Pattern getPattern() { + return PATTERN; + } + + @Override + public String unescape(String text) { + return text + .replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return false; + } + + } + + public static class MetricsControlCharacter extends ControlCharacter { + private final float fontScale; + private final float baselineOffsetScale; + + protected MetricsControlCharacter(String name, final String fontScale, + final String baselineOffset) { + super(name, MetricsControlCharacterFactory.TO_ESCAPE); + this.fontScale = parse(fontScale, 1); + this.baselineOffsetScale = parse(baselineOffset, 0); + } + + private static float parse(final String text, final float defaultValue) { + if (text == null || text.trim().isEmpty()) { + return defaultValue; + } + return Float.parseFloat(text); + } + + public float getFontScale() { + return fontScale; + } + + public float getBaselineOffsetScale() { + return baselineOffsetScale; + } + + } + + private static class MetricsControlCharacterFactory implements + ControlCharacterFactory { + + private final static Pattern PATTERN = Pattern + .compile("(? charactersSoFar) { + boolean isSuperscript = "^".equals(matcher.group(2)); + String name = isSuperscript ? "SUPERSCRIPT" : "SUBSCRIPT"; + String baselineOffsetScale = isSuperscript ? "-0.4" : "0.15"; + if (matcher.groupCount() > 6 && matcher.group(6) != null) { + baselineOffsetScale = matcher.group(6); + } + String fontScale = "0.61"; + if (matcher.groupCount() > 4 && matcher.group(4) != null) { + fontScale = matcher.group(4); + } + return new MetricsControlCharacter(name, fontScale, baselineOffsetScale); + } + + @Override + public Pattern getPattern() { + return PATTERN; + } + + @Override + public String unescape(String text) { + return text + .replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return false; + } + + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlFragment.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlFragment.java new file mode 100644 index 0000000..7d35651 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ControlFragment.java @@ -0,0 +1,71 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import java.awt.Color; +import java.io.IOException; + +/** + * A control fragment has no drawable representation but is meant to control the + * text rendering. + */ +public class ControlFragment implements TextFragment { + + protected final static FontDescriptor DEFAULT_FONT_DESCRIPTOR = new FontDescriptor( + PDType1Font.HELVETICA, 11); + + private String name; + private final String text; + private final FontDescriptor fontDescriptor; + private final Color color; + + protected ControlFragment(final String text, + final FontDescriptor fontDescriptor) { + this(null, text, fontDescriptor, Color.black); + } + + protected ControlFragment(final String name, final String text, + final FontDescriptor fontDescriptor, final Color color) { + this.name = name; + if (this.name == null) { + this.name = getClass().getSimpleName(); + } + this.text = text; + this.fontDescriptor = fontDescriptor; + this.color = color; + } + + @Override + public float getWidth() throws IOException { + return 0; + } + + @Override + public float getHeight() throws IOException { + return getFontDescriptor() == null ? 0 : getFontDescriptor().getSize(); + } + + @Override + public FontDescriptor getFontDescriptor() { + return fontDescriptor; + } + + protected String getName() { + return name; + } + + @Override + public String getText() { + return text; + } + + @Override + public Color getColor() { + return color; + } + + @Override + public String toString() { + return "ControlFragment [" + name + "]"; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawContext.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawContext.java new file mode 100644 index 0000000..632f2a7 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawContext.java @@ -0,0 +1,26 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; + +/** + * Provides the current page and document to draw to. + */ +public interface DrawContext { + + /** + * @return the document to draw to. + */ + PDDocument getPdDocument(); + + /** + * @return the current page to draw to. + */ + PDPage getCurrentPage(); + + /** + * @return the current page content stream. + */ + PDPageContentStream getCurrentPageContentStream(); +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawListener.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawListener.java new file mode 100644 index 0000000..8781c96 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawListener.java @@ -0,0 +1,18 @@ +package org.xbib.graphics.layout.pdfbox.text; + + +/** + * Called if an object has been drawn. + */ +public interface DrawListener { + + /** + * Indicates that an object has been drawn. + * + * @param drawnObject the drawn object. + * @param upperLeft the upper left position. + * @param width the width of the drawn object. + * @param height the height of the drawn object. + */ + void drawn(Object drawnObject, Position upperLeft, float width, float height); +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawableText.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawableText.java new file mode 100644 index 0000000..bee09b1 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/DrawableText.java @@ -0,0 +1,24 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import java.io.IOException; + +/** + * Represents a drawable text. + */ +public interface DrawableText extends Area { + + /** + * Draws the text of the (PdfBox-) cursor position. + * + * @param contentStream the content stream used to render. + * @param upperLeft the upper left position to draw to. + * @param alignment the text alignment. + * @param drawListener the listener to + * {@link DrawListener#drawn(Object, Position, float, float) + * notify} on drawn objects. + * @throws IOException by pdfbox. + */ + void drawText(PDPageContentStream contentStream, Position upperLeft, + Alignment alignment, DrawListener drawListener) throws IOException; +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/FontDescriptor.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/FontDescriptor.java new file mode 100644 index 0000000..83802ff --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/FontDescriptor.java @@ -0,0 +1,81 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.font.PDFont; + +/** + * Container for a Font and size. + */ +public class FontDescriptor { + + /** + * the associated font. + */ + private final PDFont font; + + /** + * the font size. + */ + private final float size; + + /** + * Creates the descriptor the the given font and size. + * + * @param font the font. + * @param size the size. + */ + public FontDescriptor(final PDFont font, final float size) { + this.font = font; + this.size = size; + } + + /** + * @return the font. + */ + public PDFont getFont() { + return font; + } + + /** + * @return the size. + */ + public float getSize() { + return size; + } + + @Override + public String toString() { + return "FontDescriptor [font=" + font + ", size=" + size + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((font == null) ? 0 : font.hashCode()); + result = prime * result + Float.floatToIntBits(size); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FontDescriptor other = (FontDescriptor) obj; + if (font == null) { + if (other.font != null) { + return false; + } + } else if (!font.equals(other.font)) { + return false; + } + return Float.floatToIntBits(size) == Float.floatToIntBits(other.size); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Indent.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Indent.java new file mode 100644 index 0000000..0cd97b8 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Indent.java @@ -0,0 +1,176 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.font.PDFont; +import java.awt.Color; +import java.io.IOException; + +/** + * Control fragment that represents a indent in text. + */ +public class Indent extends ControlFragment { + + /** + * Constant for the indentation of 0. + */ + public final static Indent UNINDENT = new Indent(0); + + protected float indentWidth = 4; + protected SpaceUnit indentUnit = SpaceUnit.em; + protected Alignment alignment = Alignment.Left; + protected StyledText styledText; + + /** + * Creates a new line with the given font descriptor. + * + * @param indentWidth the indentation. + * @param indentUnit the indentation unit. + * @throws IOException by pdfbox + */ + public Indent(final float indentWidth, final SpaceUnit indentUnit) + throws IOException { + this("", indentWidth, indentUnit, DEFAULT_FONT_DESCRIPTOR, + Alignment.Left, Color.black); + } + + /** + * Creates a new line with the + * {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}'s font and the given + * height. + * + * @param label the label of the indentation. + * @param indentWidth the indentation. + * @param indentUnit the indentation unit. + * @param fontSize the font size, resp. the height of the new line. + * @param font the font to use. + * @throws IOException by pdfbox + */ + public Indent(final String label, final float indentWidth, + final SpaceUnit indentUnit, final float fontSize, final PDFont font) + throws IOException { + + this(label, indentWidth, indentUnit, fontSize, font, Alignment.Left, + Color.black); + } + + /** + * Creates a new line with the + * {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}'s font and the given + * height. + * + * @param label the label of the indentation. + * @param indentWidth the indentation. + * @param indentUnit the indentation unit. + * @param fontSize the font size, resp. the height of the new line. + * @param font the font to use. + * @param alignment the alignment of the label. + * @throws IOException by pdfbox + */ + public Indent(final String label, final float indentWidth, + final SpaceUnit indentUnit, final float fontSize, + final PDFont font, final Alignment alignment) throws IOException { + this(label, indentWidth, indentUnit, fontSize, font, alignment, + Color.black); + } + + /** + * Creates a new line with the + * {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}'s font and the given + * height. + * + * @param label the label of the indentation. + * @param indentWidth the indentation. + * @param indentUnit the indentation unit. + * @param fontSize the font size, resp. the height of the new line. + * @param font the font to use. + * @param alignment the alignment of the label. + * @param color the color to use. + * @throws IOException by pdfbox + */ + public Indent(final String label, final float indentWidth, + final SpaceUnit indentUnit, final float fontSize, + final PDFont font, final Alignment alignment, final Color color) + throws IOException { + this(label, indentWidth, indentUnit, + new FontDescriptor(font, fontSize), alignment, color); + } + + /** + * Creates a new line with the given font descriptor. + * + * @param label the label of the indentation. + * @param indentWidth the indentation width. + * @param indentUnit the indentation unit. + * @param fontDescriptor the font and size associated with this new line. + * @param alignment the alignment of the label. + * @param color the color to use. + * @throws IOException by pdfbox + */ + public Indent(final String label, final float indentWidth, + final SpaceUnit indentUnit, final FontDescriptor fontDescriptor, + final Alignment alignment, final Color color) throws IOException { + super("INDENT", label, fontDescriptor, color); + + float indent = calculateIndent(indentWidth, indentUnit, fontDescriptor); + float textWidth = 0; + if (label != null && !label.isEmpty()) { + textWidth = fontDescriptor.getSize() + * fontDescriptor.getFont().getStringWidth(label) / 1000f; + } + float marginLeft = 0; + float marginRight = 0; + if (textWidth < indent) { + switch (alignment) { + case Left: + marginRight = indent - textWidth; + break; + case Right: + marginLeft = indent - textWidth; + break; + default: + marginLeft = (indent - textWidth) / 2f; + marginRight = marginLeft; + break; + } + } + styledText = new StyledText(label, getFontDescriptor(), getColor(), 0, + marginLeft, marginRight); + } + + /** + * Directly creates an indent of the given width in pt. + * + * @param indentPt the indentation in pt. + */ + public Indent(final float indentPt) { + super("", DEFAULT_FONT_DESCRIPTOR); + styledText = new StyledText("", getFontDescriptor(), getColor(), 0, + indentPt, 0); + } + + private float calculateIndent(final float indentWidth, + final SpaceUnit indentUnit, final FontDescriptor fontDescriptor) + throws IOException { + if (indentWidth < 0) { + return 0; + } + return indentUnit.toPt(indentWidth, fontDescriptor); + } + + @Override + public float getWidth() throws IOException { + return styledText.getWidth(); + } + + /** + * @return a styled text representation of the indent. + */ + public StyledText toStyledText() { + return styledText; + } + + @Override + public String toString() { + return "ControlFragment [" + getName() + ", " + styledText + "]"; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/IndentCharacters.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/IndentCharacters.java new file mode 100644 index 0000000..c9f1a5a --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/IndentCharacters.java @@ -0,0 +1,341 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import org.xbib.graphics.layout.pdfbox.util.Enumerator; +import org.xbib.graphics.layout.pdfbox.util.EnumeratorFactory; +import java.awt.Color; +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Container class for current supported indentation control characters. + */ +public class IndentCharacters { + + /** + * The factory for indent control characters. + */ + public static ControlCharacters.ControlCharacterFactory INDENT_FACTORY = new IndentCharacterFactory(); + + /** + * Represent un-indentation, means effectively indent of 0. + */ + public static IndentCharacter UNINDENT_CHARACTER = new IndentCharacter("0", + "0", "pt"); + + /** + * An --{7em} indicates an indentation of 7 characters in markup, + * where the number, the unit, and the brackets are optional. Default + * indentation is 4 characters, default unit is 7em It can be + * escaped with a backslash ('\'). + */ + public static class IndentCharacter extends ControlCharacter { + + protected int level = 1; + protected float indentWidth = 4; + protected SpaceUnit indentUnit = SpaceUnit.em; + + public IndentCharacter(final String level, final String indentWidth, + final String indentUnit) { + super("INDENT", IndentCharacterFactory.TO_ESCAPE); + try { + this.level = level == null ? 0 : level.length() + 1; + } catch (NumberFormatException e) { + } + try { + this.indentUnit = indentUnit == null ? SpaceUnit.em : SpaceUnit + .valueOf(indentUnit); + } catch (NumberFormatException e) { + } + float defaultIndent = this.indentUnit == SpaceUnit.em ? 4 : 10; + try { + this.indentWidth = indentWidth == null ? defaultIndent + : Integer.parseInt(indentWidth); + } catch (NumberFormatException e) { + } + + } + + /** + * @return the level of indentation, where 0 means no indent. + */ + public int getLevel() { + return level; + } + + /** + * @return the next label to use on a subsequent indent. Makes only + * sense for enumerating indents. + */ + protected String nextLabel() { + return ""; + } + + /** + * Creates the actual {@link Indent} fragment from this control + * character. + * + * @param fontSize the current font size. + * @param font the current font. + * @param color the color to use. + * @return the new Indent. + * @throws IOException by pdfbox + */ + public Indent createNewIndent(final float fontSize, final PDFont font, + final Color color) throws IOException { + return new Indent(nextLabel(), level * indentWidth, indentUnit, + fontSize, font, Alignment.Right, color); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((indentUnit == null) ? 0 : indentUnit.hashCode()); + result = prime * result + Float.floatToIntBits(indentWidth); + result = prime * result + level; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + IndentCharacter other = (IndentCharacter) obj; + if (indentUnit != other.indentUnit) { + return false; + } + if (Float.floatToIntBits(indentWidth) != Float + .floatToIntBits(other.indentWidth)) { + return false; + } + return level == other.level; + } + + } + + /** + * An -+{--:7em} indicates a list indentation of 7 characters in + * markup, using -- as the bullet. The number, the unit, bullet + * character and the brackets are optional. Default indentation is 4 + * characters, default unit is em and the default bullet + * depends on {@link CompatibilityHelper#getBulletCharacter(int)}. It can be + * escaped with a backslash ('\'). + */ + public static class ListCharacter extends IndentCharacter { + + protected String label; + + protected ListCharacter(String level, String indentWidth, + String indentUnit, String bulletCharacter) { + super(level, indentWidth, indentUnit); + if (bulletCharacter != null) { + label = bulletCharacter; + if (!label.endsWith(" ")) { + label += " "; + } + } else { + label = CompatibilityHelper.getBulletCharacter(getLevel()) + " "; + } + } + + @Override + protected String nextLabel() { + return label; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((label == null) ? 0 : label.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ListCharacter other = (ListCharacter) obj; + if (label == null) { + return other.label == null; + } else return label.equals(other.label); + } + + } + + /** + * An -#{a):7em} indicates an enumeration indentation of 7 + * characters in markup, using a)...b)...etc as the + * enumeration. The number, the unit, enumeration type/separator, and the + * brackets are optional. Default indentation is 4 characters, default unit is + * em. Default enumeration are arabic numbers, the separator + * depends on the enumerator by default ('.' for arabic). For available + * enumerators see {@link EnumeratorFactory}.It can be escaped with a + * backslash ('\'). + */ + public static class EnumerationCharacter extends IndentCharacter { + + protected Enumerator enumerator; + protected String separator; + + protected EnumerationCharacter(String level, String indentWidth, + String indentUnit, String enumerationType, String separator) { + super(level, indentWidth, indentUnit); + + if (enumerationType == null) { + enumerationType = "1"; + } + enumerator = EnumeratorFactory.createEnumerator(enumerationType); + this.separator = separator != null ? separator : enumerator + .getDefaultSeperator(); + } + + @Override + protected String nextLabel() { + String next = enumerator.next(); + StringBuilder bob = new StringBuilder(next.length() + + separator.length() + 1); + bob.append(next); + bob.append(separator); + if (!separator.endsWith(" ")) { + bob.append(" "); + } + return bob.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((enumerator == null) ? 0 : enumerator.hashCode()); + result = prime * result + + ((separator == null) ? 0 : separator.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + EnumerationCharacter other = (EnumerationCharacter) obj; + if (enumerator == null) { + if (other.enumerator != null) { + return false; + } + } else if (other.enumerator == null) { + return false; + } else if (!enumerator.getClass().equals( + other.enumerator.getClass())) { + return false; + } + if (separator == null) { + return other.separator == null; + } else return separator.equals(other.separator); + } + + } + + private static class IndentCharacterFactory implements + ControlCharacters.ControlCharacterFactory { + + private final static Pattern PATTERN = Pattern + .compile("^-(!)|^([ ]*)-(-)(\\{(\\d*)(em|pt)?\\})?|^([ ]*)-(\\+)(\\{(.+)?:(\\d*)(em|pt)?\\})?|^([ ]*)-(#)(\\{((?!:).)?(.+)?:((\\d*))((em|pt))?\\})?"); + private final static Pattern UNESCAPE_PATTERN = Pattern + .compile("^\\\\([ ]*-[-|+|#])"); + + private final static String TO_ESCAPE = "--"; + + @Override + public ControlCharacter createControlCharacter(String text, + Matcher matcher, final List charactersSoFar) { + if ("!".equals(matcher.group(1))) { + return UNINDENT_CHARACTER; + } + + if ("-".equals(matcher.group(3))) { + return new IndentCharacter(matcher.group(2), matcher.group(5), + matcher.group(6)); + } + + if ("+".equals(matcher.group(8))) { + return new ListCharacter(matcher.group(7), matcher.group(11), + matcher.group(12), matcher.group(10)); + } + + if ("#".equals(matcher.group(14))) { + return new EnumerationCharacter(matcher.group(13), + matcher.group(18), matcher.group(20), + matcher.group(16), matcher.group(17)); + } + + throw new IllegalArgumentException("unkown indentation " + text); + } + + @Override + public Pattern getPattern() { + return PATTERN; + } + + @Override + public String unescape(String text) { + Matcher matcher = UNESCAPE_PATTERN.matcher(text); + if (!matcher.find()) { + return text; + } + return matcher.group(1) + text.substring(matcher.end()); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return true; + } + + } + + public static void main(String[] args) { + Pattern PATTERN = Pattern// + .compile("^-(!)|^([ ]*)-(-)(\\{(\\d*)(em|pt)?\\})?|^([ ]*)-(\\+)(\\{(.)?:(\\d*)(em|pt)?\\})?|^([ ]*)-(#)(\\{((?!:).)?(.+)?:((\\d*))((em|pt))?\\})?"); + Matcher matcher = PATTERN.matcher(" -#{d:3em}"); + System.out.println("matches: " + matcher.find()); + if (!matcher.matches()) { + System.err.println("exit"); + return; + } + System.out.println("start: " + matcher.start()); + System.out.println("end: " + matcher.end()); + System.out.println("groups: " + matcher.groupCount()); + for (int i = 0; i < matcher.groupCount(); i++) { + System.out.println("group " + i + ": '" + matcher.group(i) + "'"); + } + // 2 - -> 1: blanks, 4: size, 5: unit + // 7 + -> 6: blanks, 9: sign, 10: size, 11: unit + // 11 # -> 12: blanks, 15: number-sign, 16: size, 18: unit + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/NewLine.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/NewLine.java new file mode 100644 index 0000000..e13c553 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/NewLine.java @@ -0,0 +1,37 @@ +package org.xbib.graphics.layout.pdfbox.text; + +/** + * Control fragment that represents a new line in text. It has a (font and) + * height in order to specify the height of an empty line. + */ +public class NewLine extends ControlFragment { + + /** + * Creates a new line with the + * {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}. + */ + public NewLine() { + this(DEFAULT_FONT_DESCRIPTOR); + } + + /** + * Creates a new line with the + * {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}'s font and the given + * height. + * + * @param fontSize the font size, resp. the height of the new line. + */ + public NewLine(final float fontSize) { + this(new FontDescriptor(DEFAULT_FONT_DESCRIPTOR.getFont(), fontSize)); + } + + /** + * Creates a new line with the given font descriptor. + * + * @param fontDescriptor the font and size associated with this new line. + */ + public NewLine(final FontDescriptor fontDescriptor) { + super("\n", fontDescriptor); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Position.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Position.java new file mode 100644 index 0000000..e7a3efe --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/Position.java @@ -0,0 +1,80 @@ +package org.xbib.graphics.layout.pdfbox.text; + +/** + * In order to avoid dependencies to AWT classes (e.g. Point), we have our own + * silly implemenation of a position. + */ +public class Position { + + private final float x; + private final float y; + + /** + * Creates a position at the given coordinates. + * + * @param x the x coordinate. + * @param y the y coordinate. + */ + public Position(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * @return the x coordinate of the position. + */ + public float getX() { + return x; + } + + /** + * @return the y coordinate of the position. + */ + public float getY() { + return y; + } + + /** + * Adds an offset to the current position and returns it as a new position. + * + * @param x the x offset to add. + * @param y the y offset to add. + * @return the new position. + */ + public Position add(final float x, final float y) { + return new Position(this.x + x, this.y + y); + } + + @Override + public String toString() { + return "Position [x=" + x + ", y=" + y + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(x); + result = prime * result + Float.floatToIntBits(y); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Position other = (Position) obj; + if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) { + return false; + } + return Float.floatToIntBits(y) == Float.floatToIntBits(other.y); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ReplacedWhitespace.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ReplacedWhitespace.java new file mode 100644 index 0000000..28f1333 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/ReplacedWhitespace.java @@ -0,0 +1,29 @@ +package org.xbib.graphics.layout.pdfbox.text; + +/** + * Acts as a replacement for whitespace that has been removed by word wrapping. + */ +public class ReplacedWhitespace extends ControlFragment { + + private final String replacedSpace; + + public ReplacedWhitespace(String replacedSpace, FontDescriptor fontDescriptor) { + super("", fontDescriptor); + + this.replacedSpace = replacedSpace; + } + + /** + * @return the replaced space. + */ + public String getReplacedSpace() { + return replacedSpace; + } + + /** + * @return the replaced fragment. + */ + public TextFragment toReplacedFragment() { + return new StyledText(getReplacedSpace(), getFontDescriptor()); + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/SpaceUnit.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/SpaceUnit.java new file mode 100644 index 0000000..d01fce4 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/SpaceUnit.java @@ -0,0 +1,34 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import java.io.IOException; + +/** + * Unit to specify space, currently only em and pt. + */ +public enum SpaceUnit { + + /** + * The average character width of the associated font. + */ + em, + /** + * Measuring in points. + */ + pt; + + /** + * Converts the given unit to pt. + * + * @param size the size with respect to the unit. + * @param fontDescriptor the font/size to use. + * @return the size in pt. + * @throws IOException by pdfbox + */ + public float toPt(final float size, final FontDescriptor fontDescriptor) throws IOException { + if (this == em) { + return fontDescriptor.getSize() + * fontDescriptor.getFont().getAverageFontWidth() / 1000 * size; + } + return size; + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/StyledText.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/StyledText.java new file mode 100644 index 0000000..5e61e7a --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/StyledText.java @@ -0,0 +1,217 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.font.PDFont; +import java.awt.Color; +import java.io.IOException; + +/** + * Base class representing drawable text styled with font, size, color etc. + */ +public class StyledText implements TextFragment { + + private final String text; + private final FontDescriptor fontDescriptor; + private final Color color; + private final float leftMargin; + private final float rightMargin; + private final float baselineOffset; + + /** + * The cached (calculated) width of the text. + */ + private Float width = null; + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param size the size of the font. + * @param font the font to use. + */ + public StyledText(final String text, final float size, final PDFont font) { + this(text, size, font, Color.black); + } + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param size the size of the font. + * @param font the font to use. + * @param color the color to use. + */ + public StyledText(final String text, final float size, final PDFont font, + final Color color) { + this(text, new FontDescriptor(font, size), color); + } + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param size the size of the font. + * @param font the font to use. + * @param color the color to use. + * @param baselineOffset the offset of the baseline. + */ + public StyledText(final String text, final float size, final PDFont font, + final Color color, final float baselineOffset) { + this(text, new FontDescriptor(font, size), color, baselineOffset, 0, 0); + } + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param fontDescriptor the font to use. + */ + public StyledText(final String text, final FontDescriptor fontDescriptor) { + this(text, fontDescriptor, Color.black); + } + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param fontDescriptor the font to use. + * @param color the color to use. + */ + public StyledText(final String text, final FontDescriptor fontDescriptor, + final Color color) { + this(text, fontDescriptor, color, 0, 0, 0); + } + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param fontDescriptor the font to use. + * @param color the color to use. + * @param baselineOffset the offset of the baseline. + * @param leftMargin the margin left to the text. + * @param rightMargin the margin right to the text. + */ + public StyledText(final String text, final FontDescriptor fontDescriptor, + final Color color, final float baselineOffset, + final float leftMargin, final float rightMargin) { + if (text.contains("\n")) { + throw new IllegalArgumentException( + "StyledText must not contain line breaks, use TextFragment.LINEBREAK for that"); + } + if (leftMargin < 0) { + throw new IllegalArgumentException("leftMargin must be >= 0"); + } + if (rightMargin < 0) { + throw new IllegalArgumentException("rightMargin must be >= 0"); + } + this.text = text; + this.fontDescriptor = fontDescriptor; + this.color = color; + this.leftMargin = leftMargin; + this.rightMargin = rightMargin; + this.baselineOffset = baselineOffset; + } + + /** + * @return the text to draw. + */ + public String getText() { + return text; + } + + /** + * @return the font to use to draw the text. + */ + public FontDescriptor getFontDescriptor() { + return fontDescriptor; + } + + @Override + public float getWidth() throws IOException { + if (width == null) { + width = getFontDescriptor().getSize() + * getFontDescriptor().getFont().getStringWidth(getText()) + / 1000; + width += leftMargin; + width += rightMargin; + } + return width; + } + + public float getWidthWithoutMargin() throws IOException { + return getWidth() - leftMargin - rightMargin; + } + + @Override + public float getHeight() throws IOException { + return getFontDescriptor().getSize(); + } + + /** + * @return the ascent of the associated font. + * @throws IOException by pdfbox. + */ + public float getAsent() throws IOException { + return getFontDescriptor().getSize() + * getFontDescriptor().getFont().getFontDescriptor().getAscent() + / 1000; + } + + public float getBaselineOffset() { + return baselineOffset; + } + + @Override + public Color getColor() { + return color; + } + + /** + * @return the margin left to the text represented by this object. + */ + public float getLeftMargin() { + return leftMargin; + } + + /** + * @return the margin right to the text represented by this object. + */ + public float getRightMargin() { + return rightMargin; + } + + /** + * @return indicates if this text has margin. + */ + public boolean hasMargin() { + return getLeftMargin() != 0 || getRightMargin() != 0; + } + + /** + * @return converts this text to a sequence. + */ + public TextSequence asSequence() { + TextLine line = new TextLine(); + line.add(this); + return line; + } + + public StyledText inheritAttributes(String text) { + return inheritAttributes(text, getLeftMargin(), getRightMargin()); + } + + public StyledText inheritAttributes(String text, float leftMargin, + float rightMargin) { + return new StyledText(text, getFontDescriptor(), getColor(), + getBaselineOffset(), leftMargin, rightMargin); + } + + @Override + public String toString() { + return "StyledText [text=" + text + ", fontDescriptor=" + + fontDescriptor + ", width=" + width + ", color=" + color + + ", leftMargin=" + leftMargin + ", rightMargin=" + rightMargin + + ", baselineOffset=" + baselineOffset + "]"; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFlow.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFlow.java new file mode 100644 index 0000000..7a2b84d --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFlow.java @@ -0,0 +1,287 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDFont; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * A text flow is a text sequence that {@link WidthRespecting respects a given + * width} by word wrapping the text. The text may contain line breaks ('\n').
      + * In order to ease creation of styled text, this class supports a kind of + * {@link #addMarkup(String, float, BaseFont) markup}. The following raw text + * + *
      + * Markup supports *bold*, _italic_, and *even _mixed* markup_.
      + * 
      + *

      + * is rendered like this: + * + *

      + * Markup supports bold, italic, and even mixed markup.
      + * 
      + *

      + * Use backslash to escape special characters '*', '_' and '\' itself: + * + *

      + * Escape \* with \\\* and \_ with \\\_ in markup.
      + * 
      + *

      + * is rendered like this: + * + *

      + * Escape * with \* and _ with \_ in markup.
      + * 
      + */ +public class TextFlow implements TextSequence, WidthRespecting { + + public static final float DEFAULT_LINE_SPACING = 1.2f; + private static final String HEIGHT = "height"; + private static final String WIDTH = "width"; + + private final Map cache = new HashMap(); + + private final List text = new ArrayList(); + private float lineSpacing = DEFAULT_LINE_SPACING; + private float maxWidth = -1; + private boolean applyLineSpacingToFirstLine = true; + + private void clearCache() { + cache.clear(); + } + + private void setCachedValue(final String key, Object value) { + cache.put(key, value); + } + + @SuppressWarnings("unchecked") + private T getCachedValue(final String key, Class type) { + return (T) cache.get(key); + } + + /** + * Adds some text associated with the font to draw. The text may contain + * line breaks ('\n'). + * + * @param text the text to add. + * @param fontSize the size of the font. + * @param font the font to use to draw the text. + * @throws IOException by PDFBox + */ + public void addText(final String text, final float fontSize, + final PDFont font) throws IOException { + add(TextFlowUtil.createTextFlow(text, fontSize, font)); + } + + /** + * Adds some markup to the text flow. + * + * @param markup the markup to add. + * @param fontSize the font size to use. + * @param baseFont the base font describing the bundle of + * plain/blold/italic/bold-italic fonts. + * @throws IOException by PDFBox + */ + public void addMarkup(final String markup, final float fontSize, + final BaseFont baseFont) throws IOException { + add(TextFlowUtil.createTextFlowFromMarkup(markup, fontSize, baseFont)); + } + + /** + * Adds some markup to the text flow. + * + * @param markup the markup to add. + * @param fontSize the font size to use. + * @param plainFont the plain font to use. + * @param boldFont the bold font to use. + * @param italicFont the italic font to use. + * @param boldItalicFont the bold-italic font to use. + * @throws IOException by PDFBox + */ + public void addMarkup(final String markup, final float fontSize, + final PDFont plainFont, final PDFont boldFont, + final PDFont italicFont, final PDFont boldItalicFont) throws IOException { + add(TextFlowUtil.createTextFlowFromMarkup(markup, fontSize, plainFont, + boldFont, italicFont, boldItalicFont)); + } + + /** + * Adds a text sequence to this flow. + * + * @param sequence the sequence to add. + */ + public void add(final TextSequence sequence) { + for (TextFragment fragment : sequence) { + add(fragment); + } + } + + /** + * Adds a text fragment to this flow. + * + * @param fragment the fragment to add. + */ + public void add(final TextFragment fragment) { + text.add(fragment); + clearCache(); + } + + /** + * Removes the last added fragment. + * + * @return the removed fragment (if any). + */ + public TextFragment removeLast() { + if (text.size() > 0) { + clearCache(); + return text.remove(text.size() - 1); + } + return null; + } + + /** + * @return the last added fragment (if any). + */ + public TextFragment getLast() { + if (text.size() > 0) { + clearCache(); + return text.get(text.size() - 1); + } + return null; + } + + /** + * @return true if this flow does not contain any fragments. + */ + public boolean isEmpty() { + return text.isEmpty(); + } + + @Override + public Iterator iterator() { + return text.iterator(); + } + + @Override + public float getMaxWidth() { + return maxWidth; + } + + @Override + public void setMaxWidth(float maxWidth) { + this.maxWidth = maxWidth; + clearCache(); + } + + /** + * @return the factor multiplied with the height to calculate the line + * spacing. + */ + public float getLineSpacing() { + return lineSpacing; + } + + /** + * Sets the factor multiplied with the height to calculate the line spacing. + * + * @param lineSpacing the line spacing factor. + */ + public void setLineSpacing(float lineSpacing) { + this.lineSpacing = lineSpacing; + clearCache(); + } + + /** + * Indicates if the line spacing should be applied to the first line. Makes + * sense if there is text above to achieve an equal spacing. In case you + * want to position the text precisely on top, you may set this value to + * false. Default is true. + * + * @return true if the line spacing should be applied to the + * first line. + */ + public boolean isApplyLineSpacingToFirstLine() { + return applyLineSpacingToFirstLine; + } + + /** + * Sets the indicator whether to apply line spacing to the first line. + * + * @param applyLineSpacingToFirstLine true if the line spacing should be applied to the + * first line. + * @see TextFlow#isApplyLineSpacingToFirstLine() + */ + public void setApplyLineSpacingToFirstLine( + boolean applyLineSpacingToFirstLine) { + this.applyLineSpacingToFirstLine = applyLineSpacingToFirstLine; + } + + @Override + public float getWidth() throws IOException { + Float width = getCachedValue(WIDTH, Float.class); + if (width == null) { + width = TextSequenceUtil.getWidth(this, getMaxWidth()); + setCachedValue(WIDTH, width); + } + return width; + } + + @Override + public float getHeight() throws IOException { + Float height = getCachedValue(HEIGHT, Float.class); + if (height == null) { + height = TextSequenceUtil.getHeight(this, getMaxWidth(), + getLineSpacing(), isApplyLineSpacingToFirstLine()); + setCachedValue(HEIGHT, height); + } + return height; + } + + @Override + public void drawText(PDPageContentStream contentStream, Position upperLeft, + Alignment alignment, DrawListener drawListener) throws IOException { + TextSequenceUtil.drawText(this, contentStream, upperLeft, drawListener, alignment, + getMaxWidth(), getLineSpacing(), + isApplyLineSpacingToFirstLine()); + } + + public void drawTextRightAligned(PDPageContentStream contentStream, + Position endOfFirstLine, DrawListener drawListener) throws IOException { + drawText(contentStream, endOfFirstLine.add(-getWidth(), 0), + Alignment.Right, drawListener); + } + + /** + * @return a copy of this text flow where all leading {@link NewLine}s are removed. + * @throws IOException by pdfbox. + */ + public TextFlow removeLeadingEmptyLines() throws IOException { + if (text.size() == 0 || !(text.get(0) instanceof NewLine)) { + return this; + } + TextFlow result = createInstance(); + result.setApplyLineSpacingToFirstLine(this.isApplyLineSpacingToFirstLine()); + result.setLineSpacing(this.getLineSpacing()); + result.setMaxWidth(this.getMaxWidth()); + for (TextFragment fragment : this) { + if (!result.isEmpty() || !(fragment instanceof NewLine)) { + result.add(fragment); + } + } + return result; + } + + protected TextFlow createInstance() { + return new TextFlow(); + } + + @Override + public String toString() { + return "TextFlow [text=" + text + "]"; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFlowUtil.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFlowUtil.java new file mode 100644 index 0000000..bd7e12b --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFlowUtil.java @@ -0,0 +1,354 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotatedStyledText; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotation; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationCharacters; +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.regex.Matcher; + +public class TextFlowUtil { + + /** + * Creates a text flow from the given text. The text may contain line + * breaks. + * + * @param text the text + * @param fontSize the font size to use. + * @param font the font to use. + * @return the created text flow. + * @throws IOException by pdfbox + */ + public static TextFlow createTextFlow(final String text, + final float fontSize, final PDFont font) throws IOException { + final Iterable parts = fromPlainText(text); + return createTextFlow(parts, fontSize, font, font, font, font); + } + + /** + * Convenience alternative to + * {@link #createTextFlowFromMarkup(String, float, PDFont, PDFont, PDFont, PDFont)} + * which allows to specifies the fonts to use by using the {@link BaseFont} + * enum. + * + * @param markup the markup text. + * @param fontSize the font size to use. + * @param baseFont the base font describing the bundle of + * plain/blold/italic/bold-italic fonts. + * @return the created text flow. + * @throws IOException by pdfbox + */ + public static TextFlow createTextFlowFromMarkup(final String markup, + final float fontSize, final BaseFont baseFont) throws IOException { + return createTextFlowFromMarkup(markup, fontSize, + baseFont.getPlainFont(), baseFont.getBoldFont(), + baseFont.getItalicFont(), baseFont.getBoldItalicFont()); + } + + /** + * Creates a text flow from the given text. The text may contain line + * breaks, and also supports some markup for creating bold and italic fonts. + * The following raw text + * + *
      +     * Markup supports *bold*, _italic_, and *even _mixed* markup_.
      +     * 
      + *

      + * is rendered like this: + * + *

      +     * Markup supports bold, italic, and even mixed markup.
      +     * 
      + *

      + * Use backslash to escape special characters '*', '_' and '\' itself: + * + *

      +     * Escape \* with \\\* and \_ with \\\_ in markup.
      +     * 
      + *

      + * is rendered like this: + * + *

      +     * Escape * with \* and _ with \_ in markup.
      +     * 
      + * + * @param markup the markup text. + * @param fontSize the font size to use. + * @param plainFont the plain font. + * @param boldFont the bold font. + * @param italicFont the italic font. + * @param boldItalicFont the bold-italic font. + * @return the created text flow. + * @throws IOException by pdfbox + */ + public static TextFlow createTextFlowFromMarkup(final String markup, + final float fontSize, final PDFont plainFont, + final PDFont boldFont, final PDFont italicFont, + final PDFont boldItalicFont) throws IOException { + final Iterable parts = fromMarkup(markup); + return createTextFlow(parts, fontSize, plainFont, boldFont, italicFont, + boldItalicFont); + } + + /** + * Actually creates the text flow from the given (markup) text. + * + * @param parts the parts to create the text flow from. + * @param fontSize the font size to use. + * @param plainFont the plain font. + * @param boldFont the bold font. + * @param italicFont the italic font. + * @param boldItalicFont the bold-italic font. + * @return the created text flow. + * @throws IOException by pdfbox + */ + protected static TextFlow createTextFlow( + final Iterable parts, final float fontSize, + final PDFont plainFont, final PDFont boldFont, + final PDFont italicFont, final PDFont boldItalicFont) + throws IOException { + final TextFlow result = new TextFlow(); + boolean bold = false; + boolean italic = false; + Color color = Color.black; + ControlCharacters.MetricsControlCharacter metricsControl = null; + Map, Annotation> annotationMap = new HashMap, Annotation>(); + Stack indentStack = new Stack(); + for (final CharSequence fragment : parts) { + + if (fragment instanceof ControlCharacter) { + if (fragment instanceof ControlCharacters.NewLineControlCharacter) { + result.add(new NewLine(fontSize)); + } + if (fragment instanceof ControlCharacters.BoldControlCharacter) { + bold = !bold; + } + if (fragment instanceof ControlCharacters.ItalicControlCharacter) { + italic = !italic; + } + if (fragment instanceof ControlCharacters.ColorControlCharacter) { + color = ((ControlCharacters.ColorControlCharacter) fragment).getColor(); + } + if (fragment instanceof AnnotationCharacters.AnnotationControlCharacter) { + AnnotationCharacters.AnnotationControlCharacter annotationControlCharacter = (AnnotationCharacters.AnnotationControlCharacter) fragment; + if (annotationMap.containsKey(annotationControlCharacter.getAnnotationType())) { + annotationMap.remove(annotationControlCharacter + .getAnnotationType()); + } else { + annotationMap.put( + annotationControlCharacter.getAnnotationType(), + annotationControlCharacter.getAnnotation()); + } + } + if (fragment instanceof ControlCharacters.MetricsControlCharacter) { + if (metricsControl != null && metricsControl.toString().equals(fragment.toString())) { + // end marker + metricsControl = null; + } else { + metricsControl = (ControlCharacters.MetricsControlCharacter) fragment; + } + } + if (fragment instanceof IndentCharacters.IndentCharacter) { + IndentCharacters.IndentCharacter currentIndent = (IndentCharacters.IndentCharacter) fragment; + if (currentIndent.getLevel() == 0) { + // indentation of 0 resets indent + indentStack.clear(); + result.add(Indent.UNINDENT); + continue; + } else { + IndentCharacters.IndentCharacter last = null; + while (!indentStack.isEmpty() + && indentStack.peek() != null + && currentIndent.getLevel() <= indentStack + .peek().getLevel()) { + last = indentStack.pop(); + } + if (last != null && last.equals(currentIndent)) { + currentIndent = last; + } + indentStack.push(currentIndent); + result.add(currentIndent.createNewIndent(fontSize, + plainFont, color)); + } + } + } else { + PDFont font = getFont(bold, italic, plainFont, boldFont, + italicFont, boldItalicFont); + float baselineOffset = 0; + float currentFontSize = fontSize; + if (metricsControl != null) { + baselineOffset = metricsControl.getBaselineOffsetScale() * fontSize; + currentFontSize *= metricsControl.getFontScale(); + } + if (annotationMap.isEmpty()) { + StyledText styledText = new StyledText(fragment.toString(), + currentFontSize, font, color, baselineOffset); + result.add(styledText); + } else { + AnnotatedStyledText styledText = new AnnotatedStyledText( + fragment.toString(), currentFontSize, font, color, baselineOffset, + annotationMap.values()); + result.add(styledText); + } + } + } + return result; + } + + protected static PDFont getFont(boolean bold, boolean italic, + final PDFont plainFont, final PDFont boldFont, + final PDFont italicFont, final PDFont boldItalicFont) { + PDFont font = plainFont; + if (bold && !italic) { + font = boldFont; + } else if (!bold && italic) { + font = italicFont; + } else if (bold && italic) { + font = boldItalicFont; + } + return font; + } + + /** + * Creates a char sequence where new-line is replaced by the corresponding + * {@link ControlCharacter}. + * + * @param text the original text. + * @return the create char sequence. + */ + public static Iterable fromPlainText(final CharSequence text) { + return fromPlainText(Collections.singleton(text)); + } + + /** + * Creates a char sequence where new-line is replaced by the corresponding + * {@link ControlCharacter}. + * + * @param text the original text. + * @return the create char sequence. + */ + public static Iterable fromPlainText( + final Iterable text) { + Iterable result = splitByControlCharacter( + ControlCharacters.NEWLINE_FACTORY, text); + result = unescapeBackslash(result); + return result; + } + + /** + * Creates a char sequence where new-line, asterisk and underscore are + * replaced by their corresponding {@link ControlCharacter}. + * + * @param markup the markup. + * @return the create char sequence. + */ + public static Iterable fromMarkup(final CharSequence markup) { + return fromMarkup(Collections.singleton(markup)); + } + + /** + * Creates a char sequence where new-line, asterisk and underscore are + * replaced by their corresponding {@link ControlCharacter}. + * + * @param markup the markup. + * @return the create char sequence. + */ + public static Iterable fromMarkup( + final Iterable markup) { + Iterable text = markup; + text = splitByControlCharacter(ControlCharacters.NEWLINE_FACTORY, text); + text = splitByControlCharacter(ControlCharacters.METRICS_FACTORY, text); + text = splitByControlCharacter(ControlCharacters.BOLD_FACTORY, text); + text = splitByControlCharacter(ControlCharacters.ITALIC_FACTORY, text); + text = splitByControlCharacter(ControlCharacters.COLOR_FACTORY, text); + + for (AnnotationCharacters.AnnotationControlCharacterFactory annotationControlCharacterFactory : AnnotationCharacters + .getFactories()) { + text = splitByControlCharacter(annotationControlCharacterFactory, + text); + } + + text = splitByControlCharacter(IndentCharacters.INDENT_FACTORY, text); + + text = unescapeBackslash(text); + + return text; + } + + /** + * Splits the sequence by the given control character and replaces its + * markup representation by the {@link ControlCharacter}. + * + * @param controlCharacterFactory the control character to split by. + * @param markup the markup to split. + * @return the splitted and replaced sequence. + */ + protected static Iterable splitByControlCharacter( + ControlCharacters.ControlCharacterFactory controlCharacterFactory, + final Iterable markup) { + List result = new ArrayList(); + boolean beginOfLine = true; + for (CharSequence current : markup) { + if (current instanceof String) { + String string = (String) current; + int begin = 0; + + if (!controlCharacterFactory.patternMatchesBeginOfLine() + || beginOfLine) { + Matcher matcher = controlCharacterFactory.getPattern() + .matcher(string); + while (matcher.find()) { + String part = string.substring(begin, matcher.start()); + begin = matcher.end(); + + if (!part.isEmpty()) { + String unescaped = controlCharacterFactory + .unescape(part); + result.add(unescaped); + } + + result.add(controlCharacterFactory + .createControlCharacter(string, matcher, result)); + } + } + + if (begin < string.length()) { + String part = string.substring(begin); + String unescaped = controlCharacterFactory.unescape(part); + result.add(unescaped); + } + + beginOfLine = false; + } else { + if (current instanceof ControlCharacters.NewLineControlCharacter) { + beginOfLine = true; + } + result.add(current); + } + + } + return result; + } + + private static Iterable unescapeBackslash( + final Iterable chars) { + List result = new ArrayList(); + for (CharSequence current : chars) { + if (current instanceof String) { + result.add(ControlCharacters + .unescapeBackslash((String) current)); + } else { + result.add(current); + } + } + return result; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFragment.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFragment.java new file mode 100644 index 0000000..7970a35 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextFragment.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import java.awt.Color; + +/** + * A text fragment describes a drawable piece of text associated with a font, + * size and color. + */ +public interface TextFragment extends Area { + + /** + * @return the text. + */ + String getText(); + + /** + * @return the font and size to use to draw the text. + */ + FontDescriptor getFontDescriptor(); + + /** + * @return the color to use to draw the text. + */ + Color getColor(); +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextLine.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextLine.java new file mode 100644 index 0000000..98ba269 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextLine.java @@ -0,0 +1,274 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * A text of line containing only {@link StyledText}s. It may be terminated by a + * {@link #getNewLine() new line}. + */ +public class TextLine implements TextSequence { + + /** + * The font ascent. + */ + private static final String ASCENT = "ascent"; + /** + * The font height. + */ + private static final String HEIGHT = "height"; + /** + * The text width. + */ + private static final String WIDTH = "width"; + + private final List styledTextList = new ArrayList(); + private NewLine newLine; + private final Map cache = new HashMap(); + + private void clearCache() { + cache.clear(); + } + + private void setCachedValue(final String key, Object value) { + cache.put(key, value); + } + + @SuppressWarnings("unchecked") + private T getCachedValue(final String key, Class type) { + return (T) cache.get(key); + } + + /** + * Adds a styled text. + * + * @param fragment the fagment to add. + */ + public void add(final StyledText fragment) { + styledTextList.add(fragment); + clearCache(); + } + + /** + * Adds all styled texts of the given text line. + * + * @param textLine the text line to add. + */ + public void add(final TextLine textLine) { + for (StyledText fragment : textLine.getStyledTexts()) { + add(fragment); + } + } + + /** + * @return the terminating new line, may be null. + */ + public NewLine getNewLine() { + return newLine; + } + + /** + * Sets the new line. + * + * @param newLine the new line. + */ + public void setNewLine(NewLine newLine) { + this.newLine = newLine; + clearCache(); + } + + /** + * @return the styled texts building up this line. + */ + public List getStyledTexts() { + return Collections.unmodifiableList(styledTextList); + } + + @Override + public Iterator iterator() { + return new TextLineIterator(styledTextList.iterator(), newLine); + } + + /** + * @return true if the line contains neither styled text nor a + * new line. + */ + public boolean isEmpty() { + return styledTextList.isEmpty() && newLine == null; + } + + @Override + public float getWidth() throws IOException { + Float width = getCachedValue(WIDTH, Float.class); + if (width == null) { + width = 0f; + for (TextFragment fragment : this) { + width += fragment.getWidth(); + } + setCachedValue(WIDTH, width); + } + return width; + } + + @Override + public float getHeight() throws IOException { + Float height = getCachedValue(HEIGHT, Float.class); + if (height == null) { + height = 0f; + for (TextFragment fragment : this) { + height = Math.max(height, fragment.getHeight()); + } + setCachedValue(HEIGHT, height); + } + return height; + } + + /** + * @return the (max) ascent of this line. + * @throws IOException by pdfbox. + */ + protected float getAscent() throws IOException { + Float ascent = getCachedValue(ASCENT, Float.class); + if (ascent == null) { + ascent = 0f; + for (TextFragment fragment : this) { + float currentAscent = fragment.getFontDescriptor().getSize() + * fragment.getFontDescriptor().getFont() + .getFontDescriptor().getAscent() / 1000; + ascent = Math.max(ascent, currentAscent); + } + setCachedValue(ASCENT, ascent); + } + return ascent; + } + + @Override + public void drawText(PDPageContentStream contentStream, Position upperLeft, + Alignment alignment, DrawListener drawListener) throws IOException { + drawAligned(contentStream, upperLeft, alignment, getWidth(), drawListener); + } + + public void drawAligned(PDPageContentStream contentStream, Position upperLeft, + Alignment alignment, float availableLineWidth, + DrawListener drawListener) throws IOException { + contentStream.saveGraphicsState(); + contentStream.beginText(); + + float x = upperLeft.getX(); + float y = upperLeft.getY() - getAscent(); // the baseline + float offset = TextSequenceUtil.getOffset(this, availableLineWidth, alignment); + x += offset; + CompatibilityHelper.setTextTranslation(contentStream, x, y); + float extraWordSpacing = 0; + if (alignment == Alignment.Justify && (getNewLine() instanceof WrappingNewLine)) { + extraWordSpacing = (availableLineWidth - getWidth()) / (styledTextList.size() - 1); + } + + FontDescriptor lastFontDesc = null; + float lastBaselineOffset = 0; + Color lastColor = null; + float gap = 0; + for (StyledText styledText : styledTextList) { + if (!styledText.getFontDescriptor().equals(lastFontDesc)) { + lastFontDesc = styledText.getFontDescriptor(); + contentStream.setFont(lastFontDesc.getFont(), + lastFontDesc.getSize()); + } + if (!styledText.getColor().equals(lastColor)) { + lastColor = styledText.getColor(); + contentStream.setNonStrokingColor(lastColor); + } + if (styledText.getLeftMargin() > 0) { + gap += styledText.getLeftMargin(); + } + + boolean moveBaseline = styledText.getBaselineOffset() != lastBaselineOffset; + if (moveBaseline || gap > 0) { + float baselineDelta = lastBaselineOffset - styledText.getBaselineOffset(); + lastBaselineOffset = styledText.getBaselineOffset(); + CompatibilityHelper.moveTextPosition(contentStream, gap, baselineDelta); + x += gap; + } + if (styledText.getText().length() > 0) { + CompatibilityHelper.showText(contentStream, + styledText.getText()); + } + + if (drawListener != null) { + float currentUpperLeft = y + styledText.getAsent(); + drawListener.drawn(styledText, + new Position(x, currentUpperLeft), + styledText.getWidthWithoutMargin(), + styledText.getHeight()); + } + x += styledText.getWidthWithoutMargin(); + + gap = extraWordSpacing; + if (styledText.getRightMargin() > 0) { + gap += styledText.getRightMargin(); + } + } + contentStream.endText(); + contentStream.restoreGraphicsState(); + } + + @Override + public String toString() { + return "TextLine [styledText=" + styledTextList + ", newLine=" + + newLine + "]"; + } + + /** + * An iterator for the text line. See {@link TextLine#iterator()}. + */ + private static class TextLineIterator implements Iterator { + + private final Iterator styledText; + private NewLine newLine; + + /** + * Creates an iterator of the given styled texts with an optional + * trailing new line. + * + * @param styledText the text fragments to iterate. + * @param newLine the optional trailing new line. + */ + public TextLineIterator(Iterator styledText, NewLine newLine) { + super(); + this.styledText = styledText; + this.newLine = newLine; + } + + @Override + public boolean hasNext() { + return styledText.hasNext() || newLine != null; + } + + @Override + public TextFragment next() { + TextFragment next = null; + if (styledText.hasNext()) { + next = styledText.next(); + } else if (newLine != null) { + next = newLine; + newLine = null; + } + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextSequence.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextSequence.java new file mode 100644 index 0000000..18071b2 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextSequence.java @@ -0,0 +1,9 @@ +package org.xbib.graphics.layout.pdfbox.text; + +/** + * A tagging interface describing some drawable text consisting of text + * fragments. + */ +public interface TextSequence extends DrawableText, Iterable { + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextSequenceUtil.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextSequenceUtil.java new file mode 100644 index 0000000..c9b967d --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/TextSequenceUtil.java @@ -0,0 +1,582 @@ +package org.xbib.graphics.layout.pdfbox.text; + +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.elements.Dividable.Divided; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.util.Pair; +import org.xbib.graphics.layout.pdfbox.util.WordBreakerFactory; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for dealing with text sequences. + */ +public class TextSequenceUtil { + + /** + * Dissects the given sequence into {@link TextLine}s. + * + * @param text the text to extract the lines from. + * @return the list of text lines. + * @throws IOException by pdfbox + */ + public static List getLines(final TextSequence text) + throws IOException { + final List result = new ArrayList(); + + TextLine line = new TextLine(); + for (TextFragment fragment : text) { + if (fragment instanceof NewLine) { + line.setNewLine((NewLine) fragment); + result.add(line); + line = new TextLine(); + } else if (fragment instanceof ReplacedWhitespace) { + // ignore replaced whitespace + } else { + line.add((StyledText) fragment); + } + } + if (!line.isEmpty()) { + result.add(line); + } + return result; + } + + /** + * Word-wraps and divides the given text sequence. + * + * @param text the text to divide. + * @param maxWidth the max width used for word-wrapping. + * @param maxHeight the max height for divide. + * @return the Divided element containing the parts. + * @throws IOException by pdfbox + */ + public static Divided divide(final TextSequence text, final float maxWidth, + final float maxHeight) throws IOException { + TextFlow wrapped = wordWrap(text, maxWidth); + List lines = getLines(wrapped); + + Paragraph first = new Paragraph(); + Paragraph tail = new Paragraph(); + if (text instanceof TextFlow) { + TextFlow flow = (TextFlow) text; + first.setMaxWidth(flow.getMaxWidth()); + first.setLineSpacing(flow.getLineSpacing()); + tail.setMaxWidth(flow.getMaxWidth()); + tail.setLineSpacing(flow.getLineSpacing()); + } + if (text instanceof Paragraph) { + Paragraph paragraph = (Paragraph) text; + first.setAlignment(paragraph.getAlignment()); + first.setApplyLineSpacingToFirstLine(paragraph.isApplyLineSpacingToFirstLine()); + tail.setAlignment(paragraph.getAlignment()); + tail.setApplyLineSpacingToFirstLine(paragraph.isApplyLineSpacingToFirstLine()); + } + + int index = 0; + do { + TextLine line = lines.get(index); + first.add(line); + ++index; + } while (index < lines.size() && first.getHeight() < maxHeight); + + if (first.getHeight() > maxHeight) { + // remove last line + --index; + TextLine line = lines.get(index); + for (@SuppressWarnings("unused") + TextFragment textFragment : line) { + first.removeLast(); + } + } + + for (int i = index; i < lines.size(); ++i) { + tail.add(lines.get(i)); + } + return new Divided(first, tail); + } + + /** + * Word-wraps the given text sequence in order to fit the max width. + * + * @param text the text to word-wrap. + * @param maxWidth the max width to fit. + * @return the word-wrapped text. + * @throws IOException by pdfbox + */ + public static TextFlow wordWrap(final TextSequence text, + final float maxWidth) throws IOException { + + float indentation = 0; + TextFlow result = new TextFlow(); + float lineLength = indentation; + boolean isWrappedLine = false; + for (TextFragment fragment : text) { + if (fragment instanceof NewLine) { + isWrappedLine = fragment instanceof WrappingNewLine; + result.add(fragment); + lineLength = indentation; + if (indentation > 0) { + result.add(new Indent(indentation).toStyledText()); + } + } else if (fragment instanceof Indent) { + if (indentation > 0) { + // reset indentation + result.removeLast(); + indentation = 0; + } + indentation = fragment.getWidth(); + lineLength = fragment.getWidth(); + result.add(((Indent) fragment).toStyledText()); + } else { + TextFlow words = splitWords(fragment); + for (TextFragment word : words) { + WordWrapContext context = new WordWrapContext(word, + lineLength, indentation, isWrappedLine); + do { + context = wordWrap(context, maxWidth, result); + } while (context.isMoreToWrap()); + + indentation = context.getIndentation(); + lineLength = context.getLineLength(); + isWrappedLine = context.isWrappedLine(); + } + } + } + return result; + } + + private static WordWrapContext wordWrap(final WordWrapContext context, + final float maxWidth, final TextFlow result) throws IOException { + TextFragment word = context.getWord(); + TextFragment moreToWrap = null; + float indentation = context.getIndentation(); + float lineLength = context.getLineLength(); + boolean isWrappedLine = context.isWrappedLine(); + + if (isWrappedLine && lineLength == indentation) { + // start of line, replace leading blanks if + TextFragment[] replaceLeadingBlanks = replaceLeadingBlanks(word); + word = replaceLeadingBlanks[0]; + if (replaceLeadingBlanks.length > 1) { + result.add(replaceLeadingBlanks[1]); + } + } + + FontDescriptor fontDescriptor = word.getFontDescriptor(); + float length = word.getWidth(); + + if (maxWidth > 0 && lineLength + length > maxWidth) { + // word exceeds max width, so create new line + + // break hard, if the text does not fit in a full (next) line + boolean breakHard = indentation + length > maxWidth; + + Pair brokenWord = breakWord(word, length, maxWidth + - lineLength, maxWidth - indentation, breakHard); + if (brokenWord != null) { + // word is broken + word = brokenWord.getFirst(); + length = word.getWidth(); + moreToWrap = brokenWord.getSecond(); + + result.add(word); + if (length > 0) { + lineLength += length; + } + + } else { + if (lineLength == indentation) { + // Begin of line and word could now be broke... + // Well, so we have to use it as it is, + // it won't get any better in the next line + result.add(word); + if (length > 0) { + lineLength += length; + } + + } else { + // give it another try in a new line, there + // will be more space. + moreToWrap = word; + if (result.getLast() != null) { + // since the current word is not used, take + // font descriptor of last line. Otherwise + // the line break might be to high + fontDescriptor = result.getLast().getFontDescriptor(); + } + } + } + + // wrap line only if not empty + if (lineLength > indentation) { + // and terminate it with a new line + result.add(new WrappingNewLine(fontDescriptor)); + isWrappedLine = true; + if (indentation > 0) { + result.add(new Indent(indentation).toStyledText()); + } + lineLength = indentation; + } + + } else { + // word fits, so just add it + result.add(word); + if (length > 0) { + lineLength += length; + } + } + + return new WordWrapContext(moreToWrap, lineLength, indentation, + isWrappedLine); + } + + /** + * Replaces leading whitespace by {@link ReplacedWhitespace}. + * + * @param word the fragment to replace + * @return + */ + private static TextFragment[] replaceLeadingBlanks(final TextFragment word) { + String text = word.getText(); + int splitIndex = 0; + while (splitIndex < text.length() + && Character.isWhitespace(text.charAt(splitIndex))) { + ++splitIndex; + } + + if (splitIndex == 0) { + return new TextFragment[]{word}; + } else { + ReplacedWhitespace whitespace = new ReplacedWhitespace( + text.substring(0, splitIndex), word.getFontDescriptor()); + StyledText newWord = null; + if (word instanceof StyledText) { + newWord = ((StyledText) word).inheritAttributes(text + .substring(splitIndex)); + } else { + newWord = new StyledText(text.substring(splitIndex), + word.getFontDescriptor(), word.getColor()); + } + return new TextFragment[]{newWord, whitespace}; + } + } + + /** + * De-wraps the given text, means any new lines introduced by wrapping will + * be removed. Also all whitespace removed by wrapping are re-introduced. + * + * @param text the text to de-wrap. + * @return the de-wrapped text. + * @throws IOException by PDFBox + */ + public static TextFlow deWrap(final TextSequence text) throws IOException { + TextFlow result = new TextFlow(); + for (TextFragment fragment : text) { + if (fragment instanceof WrappingNewLine) { + // skip + } else if (fragment instanceof ReplacedWhitespace) { + result.add(((ReplacedWhitespace) fragment).toReplacedFragment()); + } else { + result.add(fragment); + } + } + + if (text instanceof TextFlow) { + result.setLineSpacing(((TextFlow) text).getLineSpacing()); + } + return result; + } + + /** + * Convencience function that {@link #wordWrap(TextSequence, float) + * word-wraps} into {@link #getLines(TextSequence)}. + * + * @param text the text to word-wrap. + * @param maxWidth the max width to fit. + * @return the word-wrapped text lines. + * @throws IOException by pdfbox + */ + public static List wordWrapToLines(final TextSequence text, + final float maxWidth) throws IOException { + TextFlow wrapped = wordWrap(text, maxWidth); + List lines = getLines(wrapped); + return lines; + } + + /** + * Splits the fragment into words. + * + * @param text the text to split. + * @return the words as a text flow. + */ + public static TextFlow splitWords(final TextFragment text) { + TextFlow result = new TextFlow(); + if (text instanceof NewLine) { + result.add(text); + } else { + float leftMargin = 0; + float rightMargin = 0; + if (text instanceof StyledText && ((StyledText) text).hasMargin()) { + leftMargin = ((StyledText) text).getLeftMargin(); + rightMargin = ((StyledText) text).getRightMargin(); + } + + String[] words = text.getText().split(" ", -1); + for (int index = 0; index < words.length; ++index) { + String newWord = index == 0 ? words[index] : " " + words[index]; + + float currentLeftMargin = 0; + float currentRightMargin = 0; + if (index == 0) { + currentLeftMargin = leftMargin; + } + if (index == words.length - 1) { + currentRightMargin = rightMargin; + } + TextFragment derived = deriveFromExisting(text, newWord, + currentLeftMargin, currentRightMargin); + result.add(derived); + } + } + return result; + } + + /** + * Derive a new TextFragment from an existing one, means use attributes like + * font, color etc. + * + * @param toDeriveFrom the fragment to derive from. + * @param text the new text. + * @param leftMargin the new left margin. + * @param rightMargin the new right margin. + * @return the derived text fragment. + */ + protected static TextFragment deriveFromExisting( + final TextFragment toDeriveFrom, final String text, + final float leftMargin, final float rightMargin) { + if (toDeriveFrom instanceof StyledText) { + return ((StyledText) toDeriveFrom).inheritAttributes(text, + leftMargin, rightMargin); + } + return new StyledText(text, toDeriveFrom.getFontDescriptor(), + toDeriveFrom.getColor(), 0, leftMargin, rightMargin); + } + + private static Pair breakWord(TextFragment word, + float wordWidth, final float remainingLineWidth, float maxWidth, + boolean breakHard) throws IOException { + + float leftMargin = 0; + float rightMargin = 0; + if (word instanceof StyledText) { + StyledText styledText = (StyledText) word; + leftMargin = styledText.getLeftMargin(); + rightMargin = styledText.getRightMargin(); + } + + Pair brokenWord = WordBreakerFactory.getWorkBreaker() + .breakWord(word.getText(), word.getFontDescriptor(), + remainingLineWidth - leftMargin, breakHard); + if (brokenWord == null) { + return null; + } + + // break at calculated index + TextFragment head = deriveFromExisting(word, + brokenWord.getFirst(), leftMargin, 0); + TextFragment tail = deriveFromExisting(word, + brokenWord.getSecond(), 0, rightMargin); + + return new Pair(head, tail); + } + + /** + * Returns the width of the character M in the given font. + * + * @param fontDescriptor font and size. + * @return the width of M. + * @throws IOException by pdfbox + */ + public static float getEmWidth(final FontDescriptor fontDescriptor) + throws IOException { + return getStringWidth("M", fontDescriptor); + } + + /** + * Returns the width of the given text in the given font. + * + * @param text the text to measure. + * @param fontDescriptor font and size. + * @return the width of given text. + * @throws IOException by pdfbox + */ + public static float getStringWidth(final String text, + final FontDescriptor fontDescriptor) throws IOException { + return fontDescriptor.getSize() + * fontDescriptor.getFont().getStringWidth(text) / 1000; + } + + /** + * Draws the given text sequence to the PDPageContentStream at the given + * position. + * + * @param text the text to draw. + * @param contentStream the stream to draw to + * @param upperLeft the position of the start of the first line. + * @param drawListener the listener to + * {@link DrawListener#drawn(Object, Position, float, float) + * notify} on drawn objects. + * @param alignment how to align the text lines. + * @param maxWidth if > 0, the text may be word-wrapped to match the width. + * @param lineSpacing the line spacing factor. + * @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first + * line also. Makes sense in most cases to do so. + * @throws IOException by pdfbox + */ + public static void drawText(TextSequence text, + PDPageContentStream contentStream, Position upperLeft, + DrawListener drawListener, Alignment alignment, float maxWidth, + final float lineSpacing, final boolean applyLineSpacingToFirstLine) + throws IOException { + List lines = wordWrapToLines(text, maxWidth); + float maxLineWidth = Math.max(maxWidth, getMaxWidth(lines)); + Position position = upperLeft; + float lastLineHeight = 0; + for (int i = 0; i < lines.size(); i++) { + boolean applyLineSpacing = i > 0 || applyLineSpacingToFirstLine; + TextLine textLine = lines.get(i); + float currentLineHeight = textLine.getHeight(); + float lead = lastLineHeight; + if (applyLineSpacing) { + lead += (currentLineHeight * (lineSpacing - 1)); + } + lastLineHeight = currentLineHeight; + position = position.add(0, -lead); + textLine.drawAligned(contentStream, position, alignment, maxLineWidth, drawListener); + } + + } + + /** + * Gets the (left) offset of the line with respect to the target width and + * alignment. + * + * @param textLine the text + * @param targetWidth the target width + * @param alignment the alignment of the line. + * @return the left offset. + * @throws IOException by pdfbox + */ + public static float getOffset(final TextSequence textLine, + final float targetWidth, final Alignment alignment) + throws IOException { + switch (alignment) { + case Right: + return targetWidth - textLine.getWidth(); + case Center: + return (targetWidth - textLine.getWidth()) / 2f; + default: + return 0; + } + } + + /** + * Calculates the max width of all text lines. + * + * @param lines the lines for which to calculate the max width. + * @return the max width of the lines. + * @throws IOException by pdfbox. + */ + public static float getMaxWidth(final Iterable lines) + throws IOException { + float max = 0; + for (TextLine line : lines) { + max = Math.max(max, line.getWidth()); + } + return max; + } + + /** + * Calculates the width of the text + * + * @param textSequence the text. + * @param maxWidth if > 0, the text may be word-wrapped to match the width. + * @return the width of the text. + * @throws IOException by pdfbox. + */ + public static float getWidth(final TextSequence textSequence, + final float maxWidth) throws IOException { + List lines = wordWrapToLines(textSequence, maxWidth); + float max = 0; + for (TextLine line : lines) { + max = Math.max(max, line.getWidth()); + } + return max; + } + + /** + * Calculates the height of the text + * + * @param textSequence the text. + * @param maxWidth if > 0, the text may be word-wrapped to match the width. + * @param lineSpacing the line spacing factor. + * @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first + * line also. Makes sense in most cases to do so. + * @return the height of the text. + * @throws IOException by pdfbox + */ + public static float getHeight(final TextSequence textSequence, + final float maxWidth, final float lineSpacing, + final boolean applyLineSpacingToFirstLine) throws IOException { + List lines = wordWrapToLines(textSequence, maxWidth); + float sum = 0; + for (int i = 0; i < lines.size(); i++) { + boolean applyLineSpacing = i > 0 || applyLineSpacingToFirstLine; + TextLine line = lines.get(i); + float lineHeight = line.getHeight(); + if (applyLineSpacing) { + lineHeight *= lineSpacing; + } + sum += lineHeight; + } + return sum; + } + + private static class WordWrapContext { + private final TextFragment word; + private final float lineLength; + private final float indentation; + boolean isWrappedLine; + + public WordWrapContext(TextFragment word, float lineLength, + float indentation, boolean isWrappedLine) { + this.word = word; + this.lineLength = lineLength; + this.indentation = indentation; + this.isWrappedLine = isWrappedLine; + } + + public TextFragment getWord() { + return word; + } + + public float getLineLength() { + return lineLength; + } + + public float getIndentation() { + return indentation; + } + + public boolean isWrappedLine() { + return isWrappedLine; + } + + public boolean isMoreToWrap() { + return getWord() != null; + } + + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/WidthRespecting.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/WidthRespecting.java new file mode 100644 index 0000000..e8db1ed --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/WidthRespecting.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.layout.pdfbox.text; + +/** + * If a drawable is width-respecting, a max width may be set by layouts in order + * fit layout constraints. It is the drawable job to do its best to match the + * max width (e.g. word-wrap text). + */ +public interface WidthRespecting { + + /** + * @return the max width to respect. + */ + float getMaxWidth(); + + /** + * Sets the max width to respect. + * + * @param maxWidth the maximum width. + */ + void setMaxWidth(float maxWidth); +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/WrappingNewLine.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/WrappingNewLine.java new file mode 100644 index 0000000..832f253 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/WrappingNewLine.java @@ -0,0 +1,36 @@ +package org.xbib.graphics.layout.pdfbox.text; + + +/** + * A NewLine introduced by wrapping. This interface is useful for detecting + * new-lines not contained in the original text. + */ +public class WrappingNewLine extends NewLine { + + /** + * See {@link NewLine#NewLine()}. + */ + public WrappingNewLine() { + super(); + } + + /** + * See {@link NewLine#NewLine(FontDescriptor)}. + * + * @param fontDescriptor the font and size associated with this new line. + */ + public WrappingNewLine(FontDescriptor fontDescriptor) { + super(fontDescriptor); + } + + /** + * See {@link NewLine#NewLine(float)}. + * + * @param fontSize the font size, resp. the height of the new line. + */ + public WrappingNewLine(float fontSize) { + super(fontSize); + } + + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotated.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotated.java new file mode 100644 index 0000000..7dce078 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotated.java @@ -0,0 +1,18 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + + +/** + * Marker interface for annotated objects. + */ +public interface Annotated extends Iterable { + + /** + * Gets the annotations of a specific type. + * + * @param type the type of interest. + * @param the annotation type. + * @return the annotations of that type, or an empty collection. + */ + Iterable getAnnotationsOfType(Class type); + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotatedStyledText.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotatedStyledText.java new file mode 100644 index 0000000..0b0fb79 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotatedStyledText.java @@ -0,0 +1,108 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.xbib.graphics.layout.pdfbox.text.FontDescriptor; +import org.xbib.graphics.layout.pdfbox.text.StyledText; +import java.awt.Color; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Extension of styled text that supports annotations. + */ +public class AnnotatedStyledText extends StyledText implements Annotated { + + private final List annotations = new ArrayList(); + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param fontDescriptor the font to use. + * @param color the color to use. + * @param baselineOffset the offset of the baseline. + * @param leftMargin the margin left to the text. + * @param rightMargin the margin right to the text. + * @param annotations the annotations associated with the text. + */ + public AnnotatedStyledText(final String text, + final FontDescriptor fontDescriptor, final Color color, + final float leftMargin, final float rightMargin, + final float baselineOffset, + Collection annotations) { + super(text, fontDescriptor, color, baselineOffset, leftMargin, + rightMargin); + if (annotations != null) { + this.annotations.addAll(annotations); + } + } + + /** + * Creates a styled text. + * + * @param text the text to draw. Must not contain line feeds ('\n'). + * @param size the size of the font. + * @param font the font to use.. + * @param color the color to use. + * @param baselineOffset the offset of the baseline. + * @param annotations the annotations associated with the text. + */ + public AnnotatedStyledText(String text, float size, PDFont font, + Color color, final float baselineOffset, + Collection annotations) { + this(text, new FontDescriptor(font, size), color, baselineOffset, 0, 0, + annotations); + } + + @Override + public Iterator iterator() { + return annotations.iterator(); + } + + @SuppressWarnings("unchecked") + @Override + public Iterable getAnnotationsOfType(Class type) { + List result = null; + for (Annotation annotation : annotations) { + if (type.isAssignableFrom(annotation.getClass())) { + if (result == null) { + result = new ArrayList(); + } + result.add((T) annotation); + } + } + + if (result == null) { + return Collections.emptyList(); + } + return result; + } + + /** + * Adds an annotation. + * + * @param annotation the annotation to add. + */ + public void addAnnotation(final Annotation annotation) { + annotations.add(annotation); + } + + /** + * Adds all annotations. + * + * @param annos the annotations to add. + */ + public void addAllAnnotation(final Collection annos) { + annotations.addAll(annos); + } + + @Override + public AnnotatedStyledText inheritAttributes(String text, float leftMargin, + float rightMargin) { + return new AnnotatedStyledText(text, getFontDescriptor(), getColor(), + getBaselineOffset(), leftMargin, rightMargin, annotations); + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotation.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotation.java new file mode 100644 index 0000000..58415bd --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotation.java @@ -0,0 +1,8 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +/** + * Marker interface for annotations. + */ +public interface Annotation { + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationCharacters.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationCharacters.java new file mode 100644 index 0000000..a7e8fc4 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationCharacters.java @@ -0,0 +1,324 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +import org.xbib.graphics.layout.pdfbox.text.ControlCharacter; +import org.xbib.graphics.layout.pdfbox.text.ControlCharacters.ControlCharacterFactory; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.AnchorAnnotation; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.HyperlinkAnnotation; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.UnderlineAnnotation; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Container for annotation control characters. + */ +public class AnnotationCharacters { + + private final static List> FACTORIES = new CopyOnWriteArrayList>(); + + static { + register(new HyperlinkControlCharacterFactory()); + register(new AnchorControlCharacterFactory()); + register(new UnderlineControlCharacterFactory()); + } + + /** + * Use this method to register your (custom) annotation control character + * factory. + * + * @param factory the factory to register. + */ + public static void register( + final AnnotationControlCharacterFactory factory) { + FACTORIES.add(factory); + } + + /** + * @return all the default and custom annotation control character + * factories. + */ + public static Iterable> getFactories() { + return FACTORIES; + } + + private static class HyperlinkControlCharacterFactory implements + AnnotationControlCharacterFactory { + + private final static Pattern PATTERN = Pattern + .compile("(? charactersSoFar) { + return new HyperlinkControlCharacter(matcher.group(5), + matcher.group(3)); + } + + @Override + public Pattern getPattern() { + return PATTERN; + } + + @Override + public String unescape(String text) { + return text + .replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return false; + } + + } + + private static class AnchorControlCharacterFactory implements + AnnotationControlCharacterFactory { + + private final static Pattern PATTERN = Pattern + .compile("(? charactersSoFar) { + return new AnchorControlCharacter(matcher.group(3)); + } + + @Override + public Pattern getPattern() { + return PATTERN; + } + + @Override + public String unescape(String text) { + return text + .replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return false; + } + + } + + private static class UnderlineControlCharacterFactory implements + AnnotationControlCharacterFactory { + + private static final Pattern PATTERN = Pattern + .compile("(? charactersSoFar) { + return new UnderlineControlCharacter(matcher.group(4), + matcher.group(6)); + } + + @Override + public Pattern getPattern() { + return PATTERN; + } + + @Override + public String unescape(String text) { + return text + .replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return false; + } + + } + + /** + * A {link:#title1} indicates an internal link to the + * {@link AnchorControlCharacter anchor} title1. Any other link + * (not starting with # will be treated as an external link. It + * can be escaped with a backslash ('\'). + */ + public static class HyperlinkControlCharacter extends + AnnotationControlCharacter { + private HyperlinkAnnotation hyperlink; + + protected HyperlinkControlCharacter(final String hyperlink, + final String linkStyle) { + super("HYPERLINK", HyperlinkControlCharacterFactory.TO_ESCAPE); + if (hyperlink != null) { + LinkStyle style = LinkStyle.ul; + if (linkStyle != null) { + style = LinkStyle.valueOf(linkStyle); + } + this.hyperlink = new HyperlinkAnnotation(hyperlink, style); + } + } + + @Override + public HyperlinkAnnotation getAnnotation() { + return hyperlink; + } + + @Override + public Class getAnnotationType() { + return HyperlinkAnnotation.class; + } + } + + /** + * An {color:#ee22aa} indicates switching the color in markup, + * where the color is given as hex RGB code (ee22aa in this case). It can be + * escaped with a backslash ('\'). + */ + public static class AnchorControlCharacter extends + AnnotationControlCharacter { + private AnchorAnnotation anchor; + + protected AnchorControlCharacter(final String anchor) { + super("ANCHOR", AnchorControlCharacterFactory.TO_ESCAPE); + if (anchor != null) { + this.anchor = new AnchorAnnotation(anchor); + } + } + + @Override + public AnchorAnnotation getAnnotation() { + return anchor; + } + + @Override + public Class getAnnotationType() { + return AnchorAnnotation.class; + } + + } + + /** + * Control character for underline. It can be escaped with a backslash + * ('\'). + */ + public static class UnderlineControlCharacter extends + AnnotationControlCharacter { + + /** + * constant for the system property + * pdfbox.layout.underline.baseline.offset.scale.default. + */ + public final static String UNDERLINE_DEFAULT_BASELINE_OFFSET_SCALE_PROPERTY = "pdfbox.layout.underline.baseline.offset.scale.default"; + + private static Float defaultBaselineOffsetScale; + private final UnderlineAnnotation line; + + protected UnderlineControlCharacter() { + this(null, null); + } + + protected UnderlineControlCharacter(String baselineOffsetScaleValue, + String lineWeightValue) { + super("UNDERLINE", UnderlineControlCharacterFactory.TO_ESCAPE); + + float baselineOffsetScale = parseFloat(baselineOffsetScaleValue, + getdefaultBaselineOffsetScale()); + float lineWeight = parseFloat(lineWeightValue, 1f); + line = new UnderlineAnnotation(baselineOffsetScale, lineWeight); + } + + @Override + public UnderlineAnnotation getAnnotation() { + return line; + } + + @Override + public Class getAnnotationType() { + return UnderlineAnnotation.class; + } + + private static float parseFloat(String text, float defaultValue) { + if (text == null) { + return defaultValue; + } + try { + return Float.parseFloat(text); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + private static float getdefaultBaselineOffsetScale() { + if (defaultBaselineOffsetScale == null) { + defaultBaselineOffsetScale = Float + .parseFloat(System + .getProperty( + UNDERLINE_DEFAULT_BASELINE_OFFSET_SCALE_PROPERTY, + "-0.1")); + } + return defaultBaselineOffsetScale; + } + + } + + /** + * Specialized interface for control character factories for annotations. + * + * @param the type of the annotation control character. + */ + public interface AnnotationControlCharacterFactory> + extends ControlCharacterFactory { + T createControlCharacter(String text, Matcher matcher, + final List charactersSoFar); + + } + + /** + * Common base class for annotation control characters. + */ + public static abstract class AnnotationControlCharacter + extends ControlCharacter { + + protected AnnotationControlCharacter(final String description, + final String charaterToEscape) { + super(description, charaterToEscape); + } + + /** + * @return the associated annotation. + */ + public abstract T getAnnotation(); + + /** + * @return the type of the annotation. + */ + public abstract Class getAnnotationType(); + + } + + public static void main(String[] args) { + Pattern PATTERN = Pattern + .compile("(? 1: blanks, 4: size, 5: unit + // 7 + -> 6: blanks, 9: sign, 10: size, 11: unit + // 11 # -> 12: blanks, 15: number-sign, 16: size, 18: unit + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationDrawListener.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationDrawListener.java new file mode 100644 index 0000000..5b70036 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationDrawListener.java @@ -0,0 +1,103 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderContext; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderListener; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.DrawContext; +import org.xbib.graphics.layout.pdfbox.text.DrawListener; +import org.xbib.graphics.layout.pdfbox.text.DrawableText; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.IOException; + +/** + * This listener has to be passed to all + * {@link DrawableText#drawText(org.apache.pdfbox.pdmodel.PDPageContentStream, Position, Alignment, DrawListener) + * draw()} methods, in order collect all annotation metadata. After all drawing + * is done, you have to call {@link #finalizeAnnotations()} which creates all + * necessary annotations and sets them to the corresponding pages. This listener + * is used by the the rendering API, but you may also use it with the low-level + * text API. + */ +public class AnnotationDrawListener implements DrawListener, RenderListener { + + private final DrawContext drawContext; + private final Iterable annotationProcessors; + + /** + * Creates an AnnotationDrawListener with the given {@link DrawContext}. + * + * @param drawContext the context which provides the {@link PDDocument} and the + * {@link PDPage} currently drawn to. + */ + public AnnotationDrawListener(final DrawContext drawContext) { + this.drawContext = drawContext; + annotationProcessors = AnnotationProcessorFactory + .createAnnotationProcessors(); + } + + @Override + public void drawn(Object drawnObject, Position upperLeft, float width, + float height) { + if (!(drawnObject instanceof Annotated)) { + return; + } + for (AnnotationProcessor annotationProcessor : annotationProcessors) { + try { + annotationProcessor.annotatedObjectDrawn( + (Annotated) drawnObject, drawContext, upperLeft, width, + height); + } catch (IOException e) { + throw new RuntimeException( + "exception on annotation processing", e); + } + } + } + + /** + * @throws IOException by pdfbox. + * @deprecated user {@link #afterRender()} instead. + */ + @Deprecated + public void finalizeAnnotations() throws IOException { + afterRender(); + } + + @Override + public void beforePage(RenderContext renderContext) throws IOException { + for (AnnotationProcessor annotationProcessor : annotationProcessors) { + try { + annotationProcessor.beforePage(drawContext); + } catch (IOException e) { + throw new RuntimeException( + "exception on annotation processing", e); + } + } + } + + @Override + public void afterPage(RenderContext renderContext) throws IOException { + for (AnnotationProcessor annotationProcessor : annotationProcessors) { + try { + annotationProcessor.afterPage(drawContext); + } catch (IOException e) { + throw new RuntimeException( + "exception on annotation processing", e); + } + } + } + + + public void afterRender() throws IOException { + for (AnnotationProcessor annotationProcessor : annotationProcessors) { + try { + annotationProcessor.afterRender(drawContext.getPdDocument()); + } catch (IOException e) { + throw new RuntimeException( + "exception on annotation processing", e); + } + } + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationProcessor.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationProcessor.java new file mode 100644 index 0000000..27089dd --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationProcessor.java @@ -0,0 +1,51 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.xbib.graphics.layout.pdfbox.text.DrawContext; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.IOException; + +/** + * Processes an annotation. + */ +public interface AnnotationProcessor { + + /** + * Called if an annotated object has been drawn. + * + * @param drawnObject the drawn object. + * @param drawContext the drawing context. + * @param upperLeft the upper left position the object has been drawn to. + * @param width the width of the drawn object. + * @param height the height of the drawn object. + * @throws IOException by pdfbox. + */ + void annotatedObjectDrawn(final Annotated drawnObject, + final DrawContext drawContext, Position upperLeft, float width, + float height) throws IOException; + + /** + * Called before a page is drawn. + * + * @param drawContext the drawing context. + * @throws IOException by pdfbox. + */ + void beforePage(final DrawContext drawContext) throws IOException; + + /** + * Called after a page is drawn. + * + * @param drawContext the drawing context. + * @throws IOException by pdfbox. + */ + void afterPage(final DrawContext drawContext) throws IOException; + + /** + * Called after all rendering has been performed. + * + * @param document the document. + * @throws IOException by pdfbox. + */ + void afterRender(final PDDocument document) throws IOException; + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationProcessorFactory.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationProcessorFactory.java new file mode 100644 index 0000000..b59492f --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/AnnotationProcessorFactory.java @@ -0,0 +1,45 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Factory used to create all available {@link AnnotationProcessor}s. You may + * {@link #register(Class) register} your own annotation processor in order to + * process custom annotations. + */ +public class AnnotationProcessorFactory { + + private final static List> ANNOTATION_PROCESSORS = new CopyOnWriteArrayList>(); + + static { + register(HyperlinkAnnotationProcessor.class); + register(UnderlineAnnotationProcessor.class); + } + + /** + * Use this method to register your (custom) annotation processors. + * + * @param annotationProcessor the processor to register. + */ + public static void register(final Class annotationProcessor) { + ANNOTATION_PROCESSORS.add(annotationProcessor); + } + + /** + * @return a (new) instance of all available annotation processors, both + * built-in and custom. + */ + public static Iterable createAnnotationProcessors() { + List annotationProcessors = new ArrayList(); + for (Class annotationProcessorClass : ANNOTATION_PROCESSORS) { + try { + annotationProcessors.add(annotationProcessorClass.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + throw new RuntimeException("failed to create AnnotationProcessor", e); + } + } + return annotationProcessors; + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotations.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotations.java new file mode 100644 index 0000000..6294f2f --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/Annotations.java @@ -0,0 +1,114 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +/** + * Container for all annotations. + */ +public class Annotations { + + /** + * Represents a underline annotation + */ + public static class UnderlineAnnotation implements Annotation { + + private float baselineOffsetScale = 0f; + private float lineWeight = 1f; + + public UnderlineAnnotation(float baselineOffsetScale, float lineWeight) { + this.baselineOffsetScale = baselineOffsetScale; + this.lineWeight = lineWeight; + } + + public float getBaselineOffsetScale() { + return baselineOffsetScale; + } + + public float getLineWeight() { + return lineWeight; + } + + @Override + public String toString() { + return "UnderlineAnnotation [baselineOffsetScale=" + + baselineOffsetScale + ", lineWeight=" + lineWeight + "]"; + } + + } + + /** + * Represents a hyperlink annotation + */ + public static class HyperlinkAnnotation implements Annotation { + + public enum LinkStyle { + /** + * Underline. + */ + ul, + /** + * None. + */ + none + } + + private final String hyperlinkUri; + private final LinkStyle linkStyle; + + /** + * Creates a hyperlink annotation. + * + * @param hyperlinkUri the hyperlinkUri. + * @param linkStyle the link style. + */ + public HyperlinkAnnotation(String hyperlinkUri, LinkStyle linkStyle) { + this.hyperlinkUri = hyperlinkUri; + this.linkStyle = linkStyle; + } + + /** + * @return the hyperlink URI. + */ + public String getHyperlinkURI() { + return hyperlinkUri; + } + + public LinkStyle getLinkStyle() { + return linkStyle; + } + + @Override + public String toString() { + return "HyperlinkAnnotation [hyperlinkUri=" + hyperlinkUri + + ", linkStyle=" + linkStyle + "]"; + } + + } + + /** + * Represents a anchor annotation + */ + public static class AnchorAnnotation implements Annotation { + private final String anchor; + + /** + * Creates a anchor annotation. + * + * @param anchor the anchor name. + */ + public AnchorAnnotation(String anchor) { + this.anchor = anchor; + } + + /** + * @return the anchor name. + */ + public String getAnchor() { + return anchor; + } + + @Override + public String toString() { + return "AnchorAnnotation [anchor=" + anchor + "]"; + } + + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/HyperlinkAnnotationProcessor.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/HyperlinkAnnotationProcessor.java new file mode 100644 index 0000000..a3096af --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/HyperlinkAnnotationProcessor.java @@ -0,0 +1,196 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination; +import org.xbib.graphics.layout.pdfbox.text.DrawContext; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.AnchorAnnotation; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.HyperlinkAnnotation; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * This annotation processor handles both {@link HyperlinkAnnotation}s and + * {@link AnchorAnnotation}s, and adds the needed hyperlink metadata to the PDF + * document. + */ +public class HyperlinkAnnotationProcessor implements AnnotationProcessor { + + private final Map anchorMap = new HashMap(); + private final Map> linkMap = new HashMap>(); + + @Override + public void annotatedObjectDrawn(Annotated drawnObject, + DrawContext drawContext, Position upperLeft, float width, + float height) throws IOException { + + if (!(drawnObject instanceof AnnotatedStyledText)) { + return; + } + AnnotatedStyledText annotatedText = (AnnotatedStyledText) drawnObject; + handleHyperlinkAnnotations(annotatedText, drawContext, upperLeft, + width, height); + handleAnchorAnnotations(annotatedText, drawContext, upperLeft); + } + + protected void handleAnchorAnnotations(AnnotatedStyledText annotatedText, + DrawContext drawContext, Position upperLeft) { + Iterable anchorAnnotations = annotatedText + .getAnnotationsOfType(AnchorAnnotation.class); + for (AnchorAnnotation anchorAnnotation : anchorAnnotations) { + anchorMap.put( + anchorAnnotation.getAnchor(), + new PageAnchor(drawContext.getCurrentPage(), upperLeft + .getX(), upperLeft.getY())); + } + } + + protected void handleHyperlinkAnnotations( + AnnotatedStyledText annotatedText, DrawContext drawContext, + Position upperLeft, float width, float height) { + Iterable hyperlinkAnnotations = annotatedText + .getAnnotationsOfType(HyperlinkAnnotation.class); + for (HyperlinkAnnotation hyperlinkAnnotation : hyperlinkAnnotations) { + List links = linkMap.get(drawContext.getCurrentPage()); + if (links == null) { + links = new ArrayList(); + linkMap.put(drawContext.getCurrentPage(), links); + } + PDRectangle bounds = new PDRectangle(); + bounds.setLowerLeftX(upperLeft.getX()); + bounds.setLowerLeftY(upperLeft.getY() - height); + bounds.setUpperRightX(upperLeft.getX() + width); + bounds.setUpperRightY(upperLeft.getY()); + + links.add(new Hyperlink(bounds, annotatedText.getColor(), + hyperlinkAnnotation.getLinkStyle(), hyperlinkAnnotation + .getHyperlinkURI())); + } + } + + @Override + public void beforePage(DrawContext drawContext) { + // nothing to do here + } + + @Override + public void afterPage(DrawContext drawContext) { + // nothing to do here + } + + @Override + public void afterRender(PDDocument document) throws IOException { + for (Entry> entry : linkMap.entrySet()) { + PDPage page = entry.getKey(); + List links = entry.getValue(); + for (Hyperlink hyperlink : links) { + PDAnnotationLink pdLink = null; + if (hyperlink.getHyperlinkURI().startsWith("#")) { + pdLink = createGotoLink(hyperlink); + } else { + pdLink = CompatibilityHelper.createLink(page, + hyperlink.getRect(), hyperlink.getColor(), + hyperlink.getLinkStyle(), + hyperlink.getHyperlinkURI()); + } + page.getAnnotations().add(pdLink); + } + + } + } + + private PDAnnotationLink createGotoLink(Hyperlink hyperlink) { + String anchor = hyperlink.getHyperlinkURI().substring(1); + PageAnchor pageAnchor = anchorMap.get(anchor); + if (pageAnchor == null) { + throw new IllegalArgumentException(String.format( + "anchor named '%s' not found", anchor)); + } + PDPageXYZDestination xyzDestination = new PDPageXYZDestination(); + xyzDestination.setPage(pageAnchor.getPage()); + xyzDestination.setLeft((int) pageAnchor.getX()); + xyzDestination.setTop((int) pageAnchor.getY()); + return CompatibilityHelper.createLink(pageAnchor.getPage(), hyperlink.getRect(), + hyperlink.getColor(), hyperlink.getLinkStyle(), xyzDestination); + } + + private static class PageAnchor { + private final PDPage page; + private final float x; + private final float y; + + public PageAnchor(PDPage page, float x, float y) { + this.page = page; + this.x = x; + this.y = y; + } + + public PDPage getPage() { + return page; + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + @Override + public String toString() { + return "PageAnchor [page=" + page + ", x=" + x + ", y=" + y + "]"; + } + + } + + private static class Hyperlink { + private final PDRectangle rect; + private final Color color; + private final String hyperlinkUri; + private final LinkStyle linkStyle; + + public Hyperlink(PDRectangle rect, Color color, LinkStyle linkStyle, + String hyperlinkUri) { + this.rect = rect; + this.color = color; + this.hyperlinkUri = hyperlinkUri; + this.linkStyle = linkStyle; + } + + public PDRectangle getRect() { + return rect; + } + + public Color getColor() { + return color; + } + + public String getHyperlinkURI() { + return hyperlinkUri; + } + + public LinkStyle getLinkStyle() { + return linkStyle; + } + + @Override + public String toString() { + return "Hyperlink [rect=" + rect + ", color=" + color + + ", hyperlinkUri=" + hyperlinkUri + ", linkStyle=" + + linkStyle + "]"; + } + + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/UnderlineAnnotationProcessor.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/UnderlineAnnotationProcessor.java new file mode 100644 index 0000000..c7b8184 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/text/annotations/UnderlineAnnotationProcessor.java @@ -0,0 +1,97 @@ +package org.xbib.graphics.layout.pdfbox.text.annotations; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.xbib.graphics.layout.pdfbox.shape.Stroke; +import org.xbib.graphics.layout.pdfbox.text.DrawContext; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.StyledText; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.UnderlineAnnotation; +import java.awt.Color; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * This annotation processor handles the {@link UnderlineAnnotation}s, and adds + * the needed hyperlink metadata to the PDF document. + */ +public class UnderlineAnnotationProcessor implements AnnotationProcessor { + + private final List linesOnPage = new ArrayList(); + + @Override + public void annotatedObjectDrawn(Annotated drawnObject, + DrawContext drawContext, Position upperLeft, float width, + float height) throws IOException { + + if (!(drawnObject instanceof StyledText)) { + return; + } + + StyledText drawnText = (StyledText) drawnObject; + for (UnderlineAnnotation underlineAnnotation : drawnObject + .getAnnotationsOfType(UnderlineAnnotation.class)) { + float fontSize = drawnText.getFontDescriptor().getSize(); + float ascent = fontSize + * drawnText.getFontDescriptor().getFont() + .getFontDescriptor().getAscent() / 1000; + + float baselineOffset = fontSize * underlineAnnotation.getBaselineOffsetScale(); + float thickness = (0.01f + fontSize * 0.05f) + * underlineAnnotation.getLineWeight(); + + Position start = new Position(upperLeft.getX(), upperLeft.getY() + - ascent + baselineOffset); + Position end = new Position(start.getX() + width, start.getY()); + Stroke stroke = Stroke.builder().lineWidth(thickness).build(); + Line line = new Line(start, end, stroke, drawnText.getColor()); + linesOnPage.add(line); + } + } + + @Override + public void beforePage(DrawContext drawContext) throws IOException { + linesOnPage.clear(); + } + + @Override + public void afterPage(DrawContext drawContext) throws IOException { + for (Line line : linesOnPage) { + line.draw(drawContext.getCurrentPageContentStream()); + } + linesOnPage.clear(); + } + + @Override + public void afterRender(PDDocument document) throws IOException { + linesOnPage.clear(); + } + + private static class Line { + + private final Position start; + private final Position end; + private final Stroke stroke; + private final Color color; + + public Line(Position start, Position end, Stroke stroke, Color color) { + super(); + this.start = start; + this.end = end; + this.stroke = stroke; + this.color = color; + } + + public void draw(PDPageContentStream contentStream) throws IOException { + if (color != null) { + contentStream.setStrokingColor(color); + } + if (stroke != null) { + stroke.applyTo(contentStream); + } + contentStream.moveTo(start.getX(), start.getY()); + contentStream.lineTo(end.getX(), end.getY()); + } + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/CompatibilityHelper.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/CompatibilityHelper.java new file mode 100644 index 0000000..51ba57d --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/CompatibilityHelper.java @@ -0,0 +1,326 @@ +package org.xbib.graphics.layout.pdfbox.util; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.color.PDColor; +import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination; +import org.apache.pdfbox.rendering.ImageType; +import org.apache.pdfbox.rendering.PDFRenderer; +import org.apache.pdfbox.util.Matrix; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle; +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Provide compatible methods for API changes from pdfbox 1x to 2x. + */ +public class CompatibilityHelper { + + private static final String BULLET = "\u2022"; + + private static final String DOUBLE_ANGLE = "\u00bb"; + + private static final String IMAGE_CACHE = "IMAGE_CACHE"; + + private static final Map>> documentCaches = new WeakHashMap<>(); + + private static PDBorderStyleDictionary noBorder; + + /** + * Returns the bullet character for the given level. Actually only two + * bullets are used for odd and even levels. For odd levels the + * {@link #BULLET bullet} character is used, for even it is the + * {@link #DOUBLE_ANGLE double angle}. You may customize this by setting the + * system properties pdfbox.layout.bullet.odd and/or + * pdfbox.layout.bullet.even. + * + * @param level the level to return the bullet for. + * @return the bullet character for the leve. + */ + public static String getBulletCharacter(final int level) { + if (level % 2 == 1) { + return System.getProperty("pdfbox.layout.bullet.odd", BULLET); + } + return System.getProperty("pdfbox.layout.bullet.even", DOUBLE_ANGLE); + } + + public static void clip(final PDPageContentStream contentStream) + throws IOException { + contentStream.clip(); + } + + public static void transform(final PDPageContentStream contentStream, + float a, float b, float c, float d, float e, float f) + throws IOException { + contentStream.transform(new Matrix(a, b, c, d, e, f)); + } + + public static void curveTo(final PDPageContentStream contentStream, float x1, float y1, float x2, float y2, float x3, float y3) throws IOException { + contentStream.curveTo(x1, y1, x2, y2, x3, y3); + } + + public static void curveTo1(final PDPageContentStream contentStream, float x1, float y1, float x3, float y3) throws IOException { + contentStream.curveTo1(x1, y1, x3, y3); + } + + public static void fillNonZero(final PDPageContentStream contentStream) throws IOException { + contentStream.fill(); + } + + public static void showText(final PDPageContentStream contentStream, + final String text) throws IOException { + contentStream.showText(text); + } + + public static void setTextTranslation( + final PDPageContentStream contentStream, final float x, + final float y) throws IOException { + contentStream.setTextMatrix(Matrix.getTranslateInstance(x, y)); + } + + public static void moveTextPosition( + final PDPageContentStream contentStream, final float x, + final float y) throws IOException { + contentStream.transform(new Matrix(1, 0, 0, 1, x, y)); + } + + public static PDPageContentStream createAppendablePDPageContentStream( + final PDDocument pdDocument, final PDPage page) throws IOException { + return new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true); + } + + public static void drawImage(final BufferedImage image, + final PDDocument document, final PDPageContentStream contentStream, + Position upperLeft, final float width, final float height) + throws IOException { + PDImageXObject cachedImage = getCachedImage(document, image); + float x = upperLeft.getX(); + float y = upperLeft.getY() - height; + contentStream.drawImage(cachedImage, x, y, width, height); + } + + public static int getPageRotation(final PDPage page) { + return page.getRotation(); + } + + /** + * Renders the given page as an RGB image. + * + * @param document the document containing the page. + * @param pageIndex the index of the page to render. + * @param resolution the image resolution. + * @return the rendered image + * @throws IOException by pdfbox + */ + public static BufferedImage createImageFromPage(final PDDocument document, int pageIndex, final int resolution) throws IOException { + PDFRenderer pdfRenderer = new PDFRenderer(document); + return pdfRenderer.renderImageWithDPI(pageIndex, resolution, ImageType.RGB); + } + + public static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color, + LinkStyle linkStyle, final String uri) { + PDAnnotationLink pdLink = createLink(page, rect, color, linkStyle); + + PDActionURI actionUri = new PDActionURI(); + actionUri.setURI(uri); + pdLink.setAction(actionUri); + return pdLink; + } + + public static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color, + LinkStyle linkStyle, final PDDestination destination) { + PDAnnotationLink pdLink = createLink(page, rect, color, linkStyle); + + PDActionGoTo gotoAction = new PDActionGoTo(); + gotoAction.setDestination(destination); + pdLink.setAction(gotoAction); + return pdLink; + } + + /** + * Sets the color in the annotation. + * + * @param annotation the annotation. + * @param color the color to set. + */ + public static void setAnnotationColor(final PDAnnotation annotation, Color color) { + annotation.setColor(toPDColor(color)); + } + + + private static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color, + LinkStyle linkStyle) { + PDAnnotationLink pdLink = new PDAnnotationLink(); + pdLink.setBorderStyle(toBorderStyle(linkStyle)); + PDRectangle rotatedRect = transformToPageRotation(rect, page); + pdLink.setRectangle(rotatedRect); + setAnnotationColor(pdLink, color); + return pdLink; + } + + private static PDBorderStyleDictionary toBorderStyle( + final LinkStyle linkStyle) { + if (linkStyle == LinkStyle.none) { + return getNoBorder(); + } + PDBorderStyleDictionary borderStyle = new PDBorderStyleDictionary(); + borderStyle.setStyle(PDBorderStyleDictionary.STYLE_UNDERLINE); + return borderStyle; + } + + private static PDColor toPDColor(final Color color) { + float[] components = new float[]{ + color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f}; + return new PDColor(components, PDDeviceRGB.INSTANCE); + } + + /** + * Return the quad points representation of the given rect. + * + * @param rect the rectangle. + * @return the quad points. + */ + public static float[] toQuadPoints(final PDRectangle rect) { + return toQuadPoints(rect, 0, 0); + } + + /** + * Return the quad points representation of the given rect. + * + * @param rect the rectangle. + * @param xOffset the offset in x-direction to add. + * @param yOffset the offset in y-direction to add. + * @return the quad points. + */ + public static float[] toQuadPoints(final PDRectangle rect, float xOffset, + float yOffset) { + float[] quads = new float[8]; + quads[0] = rect.getLowerLeftX() + xOffset; // x1 + quads[1] = rect.getUpperRightY() + yOffset; // y1 + quads[2] = rect.getUpperRightX() + xOffset; // x2 + quads[3] = quads[1]; // y2 + quads[4] = quads[0]; // x3 + quads[5] = rect.getLowerLeftY() + yOffset; // y3 + quads[6] = quads[2]; // x4 + quads[7] = quads[5]; // y5 + return quads; + } + + /** + * Transform the quad points in order to match the page rotation + * + * @param quadPoints the quad points. + * @param page the page. + * @return the transformed quad points. + */ + public static float[] transformToPageRotation( + final float[] quadPoints, final PDPage page) { + AffineTransform transform = transformToPageRotation(page); + if (transform == null) { + return quadPoints; + } + float[] rotatedPoints = new float[quadPoints.length]; + transform.transform(quadPoints, 0, rotatedPoints, 0, 4); + return rotatedPoints; + } + + /** + * Transform the rectangle in order to match the page rotation + * + * @param rect the rectangle. + * @param page the page. + * @return the transformed rectangle. + */ + public static PDRectangle transformToPageRotation( + final PDRectangle rect, final PDPage page) { + AffineTransform transform = transformToPageRotation(page); + if (transform == null) { + return rect; + } + float[] points = new float[]{rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getUpperRightX(), rect.getUpperRightY()}; + float[] rotatedPoints = new float[4]; + transform.transform(points, 0, rotatedPoints, 0, 2); + PDRectangle rotated = new PDRectangle(); + rotated.setLowerLeftX(rotatedPoints[0]); + rotated.setLowerLeftY(rotatedPoints[1]); + rotated.setUpperRightX(rotatedPoints[2]); + rotated.setUpperRightY(rotatedPoints[3]); + return rotated; + } + + private static AffineTransform transformToPageRotation(final PDPage page) { + int pageRotation = getPageRotation(page); + if (pageRotation == 0) { + return null; + } + float pageWidth = page.getMediaBox().getHeight(); + float pageHeight = page.getMediaBox().getWidth(); + AffineTransform transform = new AffineTransform(); + transform.rotate(pageRotation * Math.PI / 180, pageHeight / 2, + pageWidth / 2); + double offset = Math.abs(pageHeight - pageWidth) / 2; + transform.translate(-offset, offset); + return transform; + } + + private static PDBorderStyleDictionary getNoBorder() { + if (noBorder == null) { + noBorder = new PDBorderStyleDictionary(); + noBorder.setWidth(0); + } + return noBorder; + } + + + private static synchronized Map> getDocumentCache( + final PDDocument document) { + Map> cache = documentCaches.get(document); + if (cache == null) { + cache = new HashMap>(); + documentCaches.put(document, cache); + } + return cache; + } + + private static synchronized Map getImageCache( + final PDDocument document) { + Map> documentCache = getDocumentCache(document); + @SuppressWarnings("unchecked") + Map imageCache = (Map) documentCache + .get(IMAGE_CACHE); + if (imageCache == null) { + imageCache = new HashMap(); + documentCache.put(IMAGE_CACHE, imageCache); + } + return imageCache; + } + + private static synchronized PDImageXObject getCachedImage( + final PDDocument document, final BufferedImage image) + throws IOException { + Map imageCache = getImageCache(document); + PDImageXObject pdxObjectImage = imageCache.get(image); + if (pdxObjectImage == null) { + pdxObjectImage = LosslessFactory.createFromImage(document, image); + imageCache.put(image, pdxObjectImage); + } + return pdxObjectImage; + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Enumerator.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Enumerator.java new file mode 100644 index 0000000..50b3fcc --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Enumerator.java @@ -0,0 +1,17 @@ +package org.xbib.graphics.layout.pdfbox.util; + +/** + * Defines an enumerator. + */ +public interface Enumerator { + + /** + * @return the next enumeration. + */ + String next(); + + /** + * @return the default separator. + */ + String getDefaultSeperator(); +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/EnumeratorFactory.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/EnumeratorFactory.java new file mode 100644 index 0000000..5884a11 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/EnumeratorFactory.java @@ -0,0 +1,65 @@ +package org.xbib.graphics.layout.pdfbox.util; + +import org.xbib.graphics.layout.pdfbox.util.Enumerators.AlphabeticEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.ArabicEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.LowerCaseAlphabeticEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.LowerCaseRomanEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.RomanEnumerator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Enumerators are created using this factory. It allows you to register and use + * your own enumerations, if the built ins does not satisfy your needs.
      + * Currently supported are: + * + * + * + * + * + * + * + *
      NameKeySeperator
      Arabic1.
      RomanI.
      Roman Lower Casei.
      AlphabeticA)
      Alphabetic Lower Casea)
      + */ +public class EnumeratorFactory { + + private final static Map> ENUMERATORS = new ConcurrentHashMap>(); + + static { + register("1", ArabicEnumerator.class); + register("I", RomanEnumerator.class); + register("i", LowerCaseRomanEnumerator.class); + register("A", AlphabeticEnumerator.class); + register("a", LowerCaseAlphabeticEnumerator.class); + } + + /** + * Registers an Enumerator class for a given key. + * + * @param key the key (character) used in markup. + * @param enumeratorClass the enumerator class. + */ + public static void register(final String key, + final Class enumeratorClass) { + ENUMERATORS.put(key, enumeratorClass); + } + + /** + * Creates an Enumerator for the given key. + * + * @param key the key of the enumerator. + * @return the created enumerator. + */ + public static Enumerator createEnumerator(final String key) { + Class enumeratorClass = ENUMERATORS.get(key); + if (enumeratorClass == null) { + throw new IllegalArgumentException("no enumerator found for '" + + key + "'"); + } + try { + return enumeratorClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException("failed to create enumerator", e); + } + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Enumerators.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Enumerators.java new file mode 100644 index 0000000..6446afb --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Enumerators.java @@ -0,0 +1,241 @@ +package org.xbib.graphics.layout.pdfbox.util; + + +/** + * Container class for the default enumerators. + */ +public class Enumerators { + + /** + * Uses arabic numbers for the enumeration, and dot as the default + * separator.
      + * + *
      +     * 1. At vero eos et accusam.
      +     * 2. Et justo duo dolores ea rebum.
      +     * 3. Stet clita ...
      +     * 
      + */ + public static class ArabicEnumerator implements Enumerator { + + private int count; + + public ArabicEnumerator() { + this(1); + } + + public ArabicEnumerator(final int startCount) { + this.count = startCount; + } + + @Override + public String next() { + return String.valueOf(count++); + } + + @Override + public String getDefaultSeperator() { + return "."; + } + } + + /** + * Uses lower case letters for the enumeration, and braces as the default + * separator.
      + * + *
      +     * a) At vero eos et accusam.
      +     * b) Et justo duo dolores ea rebum.
      +     * c) Stet clita ...
      +     * 
      + */ + public static class LowerCaseAlphabeticEnumerator extends + AlphabeticEnumerator { + + public LowerCaseAlphabeticEnumerator() { + super(); + } + + public LowerCaseAlphabeticEnumerator(final int startCount) { + super(startCount); + } + + @Override + public String next() { + return super.next().toLowerCase(); + } + } + + /** + * Uses upper case letters for the enumeration, and braces as the default + * separator.
      + * + *
      +     * A) At vero eos et accusam.
      +     * B) Et justo duo dolores ea rebum.
      +     * C) Stet clita ...
      +     * 
      + */ + public static class AlphabeticEnumerator implements Enumerator { + + final static char[] digits = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z'}; + + private int count; + + public AlphabeticEnumerator() { + this(1); + } + + public AlphabeticEnumerator(final int startCount) { + this.count = startCount; + } + + @Override + public String next() { + return toString(count++ - 1); + } + + @Override + public String getDefaultSeperator() { + return ")"; + } + + private static String toString(int i) { + char[] buf = new char[33]; + int charPos = 32; + + i = -i; + + while (i <= -digits.length) { + buf[charPos--] = digits[-(i % digits.length)]; + i = i / digits.length; + } + buf[charPos] = digits[-i]; + + return new String(buf, charPos, (33 - charPos)); + } + + } + + /** + * Uses lower case roman numbers for the enumeration, and dot as the default + * separator.
      + * + *
      +     *   i. At vero eos et accusam.
      +     *  ii. Et justo duo dolores ea rebum.
      +     * iii. Stet clita ...
      +     * 
      + */ + public static class LowerCaseRomanEnumerator extends RomanEnumerator { + + public LowerCaseRomanEnumerator() { + super(); + } + + public LowerCaseRomanEnumerator(final int startCount) { + super(startCount); + } + + @Override + public String next() { + return super.next().toLowerCase(); + } + } + + /** + * Uses upper case roman numbers for the enumeration, and dot as the default + * separator.
      + * + *
      +     *   I. At vero eos et accusam.
      +     *  II. Et justo duo dolores ea rebum.
      +     * III. Stet clita ...
      +     * 
      + */ + public static class RomanEnumerator implements Enumerator { + + private int count; + + public RomanEnumerator() { + this(1); + } + + public RomanEnumerator(final int startCount) { + this.count = startCount; + } + + @Override + public String next() { + return toRoman(count++); + } + + @Override + public String getDefaultSeperator() { + return "."; + } + + private String toRoman(int input) { + if (input < 1 || input > 3999) { + return "Invalid Roman Number Value"; + } + String s = ""; + while (input >= 1000) { + s += "M"; + input -= 1000; + } + while (input >= 900) { + s += "CM"; + input -= 900; + } + while (input >= 500) { + s += "D"; + input -= 500; + } + while (input >= 400) { + s += "CD"; + input -= 400; + } + while (input >= 100) { + s += "C"; + input -= 100; + } + while (input >= 90) { + s += "XC"; + input -= 90; + } + while (input >= 50) { + s += "L"; + input -= 50; + } + while (input >= 40) { + s += "XL"; + input -= 40; + } + while (input >= 10) { + s += "X"; + input -= 10; + } + while (input >= 9) { + s += "IX"; + input -= 9; + } + while (input >= 5) { + s += "V"; + input -= 5; + } + while (input >= 4) { + s += "IV"; + input -= 4; + } + while (input >= 1) { + s += "I"; + input -= 1; + } + return s; + } + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Pair.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Pair.java new file mode 100644 index 0000000..822bba9 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/Pair.java @@ -0,0 +1,66 @@ +package org.xbib.graphics.layout.pdfbox.util; + +/** + * Generic container for a pair of objects. + * + * @param the generic parameter. + */ +public class Pair { + + private final T first; + private final T second; + + public Pair(T first, T second) { + super(); + this.first = first; + this.second = second; + } + + public T getFirst() { + return first; + } + + public T getSecond() { + return second; + } + + @Override + public String toString() { + return "Tuple [first=" + first + ", second=" + second + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((first == null) ? 0 : first.hashCode()); + result = prime * result + ((second == null) ? 0 : second.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + @SuppressWarnings("rawtypes") + Pair other = (Pair) obj; + if (first == null) { + if (other.first != null) { + return false; + } + } else if (!first.equals(other.first)) { + return false; + } + if (second == null) { + return other.second == null; + } else return second.equals(other.second); + } + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreaker.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreaker.java new file mode 100644 index 0000000..7c3f502 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreaker.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.layout.pdfbox.util; + +import org.xbib.graphics.layout.pdfbox.text.FontDescriptor; +import java.io.IOException; + +/** + * This interface may be used to implement different strategies on how to break + * a word, if it does not fit into a line. + */ +public interface WordBreaker { + + /** + * Breaks the word in order to fit the given maximum width. + * + * @param word the word to break. + * @param fontDescriptor describing the font's type and size. + * @param maxWidth the maximum width to obey. + * @param breakHardIfNecessary indicates if the word should be broken hard to fit the width, + * in case there is no suitable position for breaking it + * adequately. + * @return the broken word, or null if it cannot be broken. + * @throws IOException by pdfbox + */ + Pair breakWord(final String word, + final FontDescriptor fontDescriptor, final float maxWidth, + final boolean breakHardIfNecessary) throws IOException; + +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreakerFactory.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreakerFactory.java new file mode 100644 index 0000000..d747e82 --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreakerFactory.java @@ -0,0 +1,66 @@ +package org.xbib.graphics.layout.pdfbox.util; + +import org.xbib.graphics.layout.pdfbox.util.WordBreakers.DefaultWordBreaker; +import org.xbib.graphics.layout.pdfbox.util.WordBreakers.NonBreakingWordBreaker; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Factory for creating a {@link WordBreaker}. This may be used to define a + * custom strategy for breaking words. By default the {@link DefaultWordBreaker} + * is used. Another predefined word breaker is the + * {@link NonBreakingWordBreaker} which may be used to get the legacy behavior. + * To switch to a different word breaker, just set the system property + * {@link #WORD_BREAKER_CLASS_PROPERTY pdfbox.layout.word.breaker} to the class + * name of the breaker to use. + */ +public class WordBreakerFactory { + + /** + * constant for the system property pdfbox.layout.word.breaker. + */ + public final static String WORD_BREAKER_CLASS_PROPERTY = "pdfbox.layout.word.breaker"; + + /** + * class name of the default word breaker. + */ + public final static String DEFAULT_WORD_BREAKER_CLASS_NAME = DefaultWordBreaker.class + .getName(); + + /** + * class name of the (legacy) non-breaking word breaker. + */ + public final static String LEGACY_WORD_BREAKER_CLASS_NAME = NonBreakingWordBreaker.class + .getName(); + + private final static WordBreaker DEFAULT_WORD_BREAKER = new DefaultWordBreaker(); + private final static Map WORD_BREAKERS = new ConcurrentHashMap(); + + /** + * @return the word breaker instance to use. + */ + public static WordBreaker getWorkBreaker() { + return getWorkBreaker(System.getProperty(WORD_BREAKER_CLASS_PROPERTY)); + } + + private static WordBreaker getWorkBreaker(String className) { + if (className == null) { + return DEFAULT_WORD_BREAKER; + } + WordBreaker wordBreaker = WORD_BREAKERS.get(className); + if (wordBreaker == null) { + wordBreaker = createWordBreakerInstance(className); + WORD_BREAKERS.put(className, wordBreaker); + } + return wordBreaker; + } + + private static WordBreaker createWordBreakerInstance(final String className) { + try { + return (WordBreaker) Class.forName(className).getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(String.format( + "failed to create word breaker '%s'", className), e); + } + } +} diff --git a/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreakers.java b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreakers.java new file mode 100644 index 0000000..0cca73a --- /dev/null +++ b/layout-pdfbox/src/main/java/org/xbib/graphics/layout/pdfbox/util/WordBreakers.java @@ -0,0 +1,148 @@ +package org.xbib.graphics.layout.pdfbox.util; + +import org.xbib.graphics.layout.pdfbox.text.FontDescriptor; +import org.xbib.graphics.layout.pdfbox.text.TextSequenceUtil; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Container class for the default word breakers. + */ +public class WordBreakers { + + /** + * May by used for legacy compatibility, does not break at all. + */ + public static class NonBreakingWordBreaker implements WordBreaker { + + @Override + public Pair breakWord(String word, + FontDescriptor fontDescriptor, float maxWidth, + boolean breakHardIfNecessary) throws IOException { + return null; + } + + } + + /** + * Abstract base class for implementing (custom) word breakers. Tries to + * break the word {@link #breakWordSoft(String, FontDescriptor, float) + * softly}, or - if this is not possible - + * {@link #breakWordHard(String, FontDescriptor, float) hard}. + */ + public static abstract class AbstractWordBreaker implements WordBreaker { + + @Override + public Pair breakWord(final String word, + final FontDescriptor fontDescriptor, final float maxWidth, + final boolean breakHardIfNecessary) throws IOException { + + Pair brokenWord = breakWordSoft(word, fontDescriptor, + maxWidth); + if (brokenWord == null && breakHardIfNecessary) { + brokenWord = breakWordHard(word, fontDescriptor, maxWidth); + } + return brokenWord; + } + + /** + * To be implemented by subclasses. Give your best to break the word + * softly using your strategy, otherwise return null. + * + * @param word the word to break. + * @param fontDescriptor describing the font's type and size. + * @param maxWidth the maximum width to obey. + * @return the broken word, or null if it cannot be broken. + * @throws IOException by pdfbox + */ + abstract protected Pair breakWordSoft(final String word, + final FontDescriptor fontDescriptor, final float maxWidth) + throws IOException; + + /** + * Breaks the word hard at the outermost position that fits the given + * max width. + * + * @param word the word to break. + * @param fontDescriptor describing the font's type and size. + * @param maxWidth the maximum width to obey. + * @return the broken word, or null if it cannot be broken. + * @throws IOException by pdfbox + */ + protected Pair breakWordHard(final String word, + final FontDescriptor fontDescriptor, final float maxWidth) + throws IOException { + int cutIndex = (int) (maxWidth / TextSequenceUtil.getEmWidth(fontDescriptor)); + float currentWidth = TextSequenceUtil.getStringWidth(word.substring(0, cutIndex), + fontDescriptor); + if (currentWidth > maxWidth) { + while (currentWidth > maxWidth) { + --cutIndex; + currentWidth = TextSequenceUtil.getStringWidth(word.substring(0, cutIndex), + fontDescriptor); + } + ++cutIndex; + } else if (currentWidth < maxWidth) { + while (currentWidth < maxWidth) { + ++cutIndex; + currentWidth = TextSequenceUtil.getStringWidth(word.substring(0, cutIndex), + fontDescriptor); + } + --cutIndex; + } + + return new Pair(word.substring(0, cutIndex), + word.substring(cutIndex)); + } + + } + + /** + * Breaks a word if one of the following characters is found after a + * non-digit letter: + *
        + *
      • .
      • + *
      • ,
      • + *
      • -
      • + *
      • /
      • + *
      + */ + public static class DefaultWordBreaker extends AbstractWordBreaker { + + /** + * A letter followed by either -, ., + * , or /. + */ + private final Pattern breakPattern = Pattern + .compile("[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]([\\-\\.\\,/])"); + + @Override + protected Pair breakWordSoft(final String word, + final FontDescriptor fontDescriptor, final float maxWidth) + throws IOException { + Matcher matcher = breakPattern.matcher(word); + int breakIndex = -1; + boolean maxWidthExceeded = false; + while (!maxWidthExceeded && matcher.find()) { + int currentIndex = matcher.end(); + if (currentIndex < word.length() - 1) { + if (TextSequenceUtil.getStringWidth(word.substring(0, currentIndex), + fontDescriptor) < maxWidth) { + breakIndex = currentIndex; + } else { + maxWidthExceeded = true; + } + } + } + + if (breakIndex < 0) { + return null; + } + return new Pair(word.substring(0, breakIndex), + word.substring(breakIndex)); + } + + } + +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Aligned.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Aligned.java new file mode 100644 index 0000000..5998340 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Aligned.java @@ -0,0 +1,47 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.util.WordBreakerFactory; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Aligned { + + @Test + public void test() throws Exception { + System.setProperty(WordBreakerFactory.WORD_BREAKER_CLASS_PROPERTY, + WordBreakerFactory.LEGACY_WORD_BREAKER_CLASS_NAME); + Document document = new Document(40, 60, 40, 60); + Paragraph paragraph = new Paragraph(); + paragraph.addText("This is some left aligned text", 11, + PDType1Font.HELVETICA); + paragraph.setAlignment(Alignment.Left); + paragraph.setMaxWidth(40); + document.add(paragraph, VerticalLayoutHint.LEFT); + paragraph = new Paragraph(); + paragraph.addText("This is some centered text", 11, + PDType1Font.HELVETICA); + paragraph.setAlignment(Alignment.Center); + paragraph.setMaxWidth(40); + document.add(paragraph, VerticalLayoutHint.CENTER); + paragraph = new Paragraph(); + paragraph.addText("This is some right aligned text", 11, + PDType1Font.HELVETICA); + paragraph.setAlignment(Alignment.Right); + paragraph.setMaxWidth(40); + document.add(paragraph, VerticalLayoutHint.RIGHT); + paragraph = new Paragraph(); + paragraph.addText("Text is right aligned, and paragraph centered", 11, + PDType1Font.HELVETICA); + paragraph.setAlignment(Alignment.Right); + paragraph.setMaxWidth(40); + document.add(paragraph, VerticalLayoutHint.CENTER); + OutputStream outputStream = new FileOutputStream("build/aligned.pdf"); + document.save(outputStream); + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Columns.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Columns.java new file mode 100644 index 0000000..6e77d35 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Columns.java @@ -0,0 +1,87 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.VerticalSpacer; +import org.xbib.graphics.layout.pdfbox.elements.render.ColumnLayout; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Columns { + + @Test + public void test() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + String text2 = "At *vero eos et accusam* et justo duo dolores et ea rebum. " + + "Stet clita kasd gubergren, no sea takimata\n\n" + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + Document document = new Document(40, 50, 40, 60); + + Paragraph title = new Paragraph(); + title.addMarkup("*This Text is organized in Colums*", 20, BaseFont.Times); + document.add(title, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + + // use column layout from now on + document.add(new ColumnLayout(2, 10)); + + Paragraph paragraph1 = new Paragraph(); + paragraph1.addMarkup(text1, 11, BaseFont.Times); + document.add(paragraph1); + + Paragraph paragraph2 = new Paragraph(); + paragraph2.addMarkup(text2, 12, BaseFont.Helvetica); + document.add(paragraph2); + + Paragraph paragraph3 = new Paragraph(); + paragraph3.addMarkup(text1, 8, BaseFont.Courier); + document.add(paragraph3); + + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph1); + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph3); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + + final OutputStream outputStream = new FileOutputStream("build/columns.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/CustomAnnotation.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/CustomAnnotation.java new file mode 100644 index 0000000..c282f41 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/CustomAnnotation.java @@ -0,0 +1,226 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.PageFormat; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import org.xbib.graphics.layout.pdfbox.text.DrawContext; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotated; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotatedStyledText; +import org.xbib.graphics.layout.pdfbox.text.annotations.Annotation; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationCharacters; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationCharacters.AnnotationControlCharacter; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationCharacters.AnnotationControlCharacterFactory; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationProcessor; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationProcessorFactory; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import java.awt.Color; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CustomAnnotation { + + /** + * Represents a highlight annotation that might be added to a + * {@link AnnotatedStyledText}. + */ + public static class HighlightAnnotation implements Annotation { + + private final Color color; + + public HighlightAnnotation(Color color) { + this.color = color; + } + + public Color getColor() { + return color; + } + } + + /** + * Processes {@link HighlightAnnotation}s by adding a colored highlight to + * the pdf. + */ + public static class HighlightAnnotationProcessor implements + AnnotationProcessor { + + @Override + public void annotatedObjectDrawn(Annotated drawnObject, + DrawContext drawContext, Position upperLeft, float width, + float height) throws IOException { + + Iterable HighlightAnnotations = drawnObject + .getAnnotationsOfType(HighlightAnnotation.class); + + for (HighlightAnnotation highlightAnnotation : HighlightAnnotations) { + + // use PDF text markup to implement the highlight + PDAnnotationTextMarkup markup = new PDAnnotationTextMarkup( + PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT); + + // use the bounding box of the drawn object to position the + // highlight + PDRectangle bounds = new PDRectangle(); + bounds.setLowerLeftX(upperLeft.getX()); + bounds.setLowerLeftY(upperLeft.getY() - height); + bounds.setUpperRightX(upperLeft.getX() + width); + bounds.setUpperRightY(upperLeft.getY() + 1); + markup.setRectangle(bounds); + float[] quadPoints = CompatibilityHelper.toQuadPoints(bounds); + quadPoints = CompatibilityHelper.transformToPageRotation( + quadPoints, drawContext.getCurrentPage()); + markup.setQuadPoints(quadPoints); + + // set the highlight color if given + if (highlightAnnotation.getColor() != null) { + CompatibilityHelper.setAnnotationColor(markup, highlightAnnotation.getColor()); + } + + // finally add the markup to the PDF + drawContext.getCurrentPage().getAnnotations().add(markup); + } + } + + @Override + public void beforePage(DrawContext drawContext) throws IOException { + // nothing to do here for us + } + + @Override + public void afterPage(DrawContext drawContext) throws IOException { + // nothing to do here for us + } + + @Override + public void afterRender(PDDocument document) throws IOException { + // nothing to do here for us + } + + } + + /** + * The control character is a representation of the parsed markup. It + * contains any information passed by the markup necessary for rendering, in + * our case here it is just the color for the highlight. + */ + public static class HighlightControlCharacter extends + AnnotationControlCharacter { + + private final HighlightAnnotation annotation; + + protected HighlightControlCharacter(final Color color) { + super("HIGHLIGHT", HighlightControlCharacterFactory.TO_ESCAPE); + annotation = new HighlightAnnotation(color); + } + + @Override + public HighlightAnnotation getAnnotation() { + return annotation; + } + + @Override + public Class getAnnotationType() { + return HighlightAnnotation.class; + } + } + + /** + * Provides a regex pattern to match the highlight markup, and creates an + * appropriate control character. In our case here the markup syntax is + * either {hl} or with optional color information + * {hl:#ee22aa}, where the color is given as hex RGB code + * (ee22aa in this case). It can be escaped with a backslash ('\'). + */ + private static class HighlightControlCharacterFactory implements + AnnotationControlCharacterFactory { + + private final static Pattern PATTERN = Pattern + .compile("(? charactersSoFar) { + Color color = null; + String hex = matcher.group(3); + if (hex != null) { + int r = Integer.parseUnsignedInt(hex.substring(0, 2), 16); + int g = Integer.parseUnsignedInt(hex.substring(2, 4), 16); + int b = Integer.parseUnsignedInt(hex.substring(4, 6), 16); + color = new Color(r, g, b); + } + return new HighlightControlCharacter(color); + } + + @Override + public Pattern getPattern() { + return PATTERN; + } + + @Override + public String unescape(String text) { + return text + .replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE); + } + + @Override + public boolean patternMatchesBeginOfLine() { + return false; + } + + } + + @Test + public void test() throws Exception { + + // register our custom highlight annotation processor + AnnotationProcessorFactory.register(HighlightAnnotationProcessor.class); + + Document document = new Document(PageFormat.with().A4() + .margins(40, 60, 40, 60).portrait().build()); + + Paragraph paragraph = new Paragraph(); + paragraph.addText("Hello there, here is ", 10, PDType1Font.HELVETICA); + + // now add some annotated text using our custom highlight annotation + HighlightAnnotation annotation = new HighlightAnnotation(Color.green); + AnnotatedStyledText highlightedText = new AnnotatedStyledText( + "highlighted text", 10, PDType1Font.HELVETICA, Color.black, 0f, + Collections.singleton(annotation)); + paragraph.add(highlightedText); + + paragraph + .addText( + ". Do whatever you want here...strike, squiggle, whatsoever\n\n", + 10, PDType1Font.HELVETICA); + paragraph.setMaxWidth(150); + document.add(paragraph); + + // register markup processing for the highlight annotation + AnnotationCharacters.register(new HighlightControlCharacterFactory()); + + paragraph = new Paragraph(); + paragraph + .addMarkup( + "Hello there, here is {hl:#ffff00}highlighted text{hl}. " + + "Do whatever you want here...strike, squiggle, whatsoever\n\n", + 10, BaseFont.Helvetica); + paragraph.setMaxWidth(150); + document.add(paragraph); + + final OutputStream outputStream = new FileOutputStream("build/customannotation.pdf"); + document.save(outputStream); + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/CustomRenderer.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/CustomRenderer.java new file mode 100644 index 0000000..53a0e00 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/CustomRenderer.java @@ -0,0 +1,136 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Element; +import org.xbib.graphics.layout.pdfbox.elements.HorizontalRuler; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.render.LayoutHint; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderContext; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderListener; +import org.xbib.graphics.layout.pdfbox.elements.render.Renderer; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.shape.Stroke; +import org.xbib.graphics.layout.pdfbox.shape.Stroke.CapStyle; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.TextFlow; +import org.xbib.graphics.layout.pdfbox.text.TextFlowUtil; +import org.xbib.graphics.layout.pdfbox.text.TextSequenceUtil; +import java.awt.Color; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class CustomRenderer { + + @Test + public void test() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + String text2 = "At *vero eos et accusam* et justo duo dolores et ea rebum." + + "Stet clita kasd gubergren, no sea takimata\n\n" + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + Document document = new Document(40, 60, 40, 60); + SectionRenderer sectionRenderer = new SectionRenderer(); + document.addRenderer(sectionRenderer); + document.addRenderListener(sectionRenderer); + + Paragraph paragraph = new Paragraph(); + paragraph.addMarkup(text1, 11, BaseFont.Times); + paragraph.addMarkup(text2, 12, BaseFont.Helvetica); + paragraph.addMarkup(text1, 8, BaseFont.Courier); + + document.add(new Section(1)); + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + document.add(new Section(2)); + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + document.add(new Section(3)); + document.add(paragraph); + document.add(paragraph); + + final OutputStream outputStream = new FileOutputStream("build/customrenderer.pdf"); + document.save(outputStream); + + } + + public static class SectionRenderer implements Renderer, RenderListener { + + private int sectionNumber; + + @Override + public boolean render(RenderContext renderContext, Element element, + LayoutHint layoutHint) throws IOException { + if (element instanceof Section) { + + if (renderContext.getPageIndex() > 0) { + // no new page on first page ;-) + renderContext.newPage(); + } + sectionNumber = ((Section) element).getNumber(); + + renderContext.render(renderContext, element, layoutHint); + + Element ruler = new HorizontalRuler(Stroke.builder().lineWidth(2) + .capStyle(CapStyle.RoundCap).build(), Color.black); + renderContext.render(renderContext, ruler, VerticalLayoutHint.builder().marginBottom(10).build()); + + return true; + } + return false; + } + + @Override + public void beforePage(RenderContext renderContext) { + + } + + @Override + public void afterPage(RenderContext renderContext) throws IOException { + String content = String.format("Section %s, Page %s", + sectionNumber, renderContext.getPageIndex() + 1); + TextFlow text = TextFlowUtil.createTextFlow(content, 11, + PDType1Font.TIMES_ROMAN); + float offset = renderContext.getPageFormat().getMarginLeft() + + TextSequenceUtil.getOffset(text, + renderContext.getWidth(), Alignment.Right); + text.drawText(renderContext.getContentStream(), new Position( + offset, 30), Alignment.Right, null); + } + + } + + public static class Section extends Paragraph { + private final int number; + + public Section(int number) throws IOException { + super(); + this.number = number; + addMarkup(String.format("*Section %d", number), 16, BaseFont.Times); + } + + public int getNumber() { + return number; + } + + } +} \ No newline at end of file diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Frames.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Frames.java new file mode 100644 index 0000000..6f00f71 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Frames.java @@ -0,0 +1,96 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Frame; +import org.xbib.graphics.layout.pdfbox.elements.PageFormat; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.shape.Ellipse; +import org.xbib.graphics.layout.pdfbox.shape.Rect; +import org.xbib.graphics.layout.pdfbox.shape.RoundRect; +import org.xbib.graphics.layout.pdfbox.shape.Stroke; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import org.xbib.graphics.layout.pdfbox.text.Constants; +import java.awt.Color; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Frames { + + @Test + public void test() throws Exception { + String text1 = "{color:#ffffff}Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."; + + String text2 = "At *vero eos et accusam* et justo duo dolores et ea rebum. " + + "Stet clita kasd gubergren, no sea takimata.\n\n" + + "Sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + Document document = new Document(new PageFormat(Constants.A5)); + + Paragraph paragraph = new Paragraph(); + paragraph.addMarkup("Am I living in a box?", 11, BaseFont.Times); + Frame frame = new Frame(paragraph); + frame.setShape(new Rect()); + frame.setBorder(Color.black, new Stroke()); + frame.setPadding(10, 10, 5, 5); + frame.setMargin(40, 40, 20, 10); + document.add(frame, VerticalLayoutHint.CENTER); + + paragraph = new Paragraph(); + paragraph.addMarkup(text1, 11, BaseFont.Times); + frame = new Frame(paragraph, 200f, null); + frame.setShape(new Rect()); + frame.setBackgroundColor(Color.black); + frame.setPadding(10, 10, 5, 5); + frame.setMargin(40, 40, 20, 10); + document.add(frame); + + paragraph = new Paragraph(); + paragraph.addMarkup("{color:#aa00aa}*Ain't no rectangle*", 22, BaseFont.Helvetica); + paragraph.setAlignment(Alignment.Center); + frame = new Frame(paragraph, 300f, 100f); + frame.setShape(new Ellipse()); + frame.setBorder(Color.green, new Stroke(2)); + frame.setBackgroundColor(Color.pink); + frame.setPadding(50, 0, 35, 0); +// frame.setMargin(30, 30, 20, 10); + document.add(frame); + + paragraph = new Paragraph(); + paragraph.addMarkup("Frames also paginate, see here:\n\n", 13, BaseFont.Times); + paragraph.addMarkup(text2, 11, BaseFont.Times); + paragraph.addMarkup(text2, 11, BaseFont.Times); + frame = new Frame(paragraph, null, null); + frame.setShape(new RoundRect(10)); + frame.setBorder(Color.magenta, new Stroke(3)); + frame.setBackgroundColor(new Color(255, 240, 180)); + frame.setPadding(20, 15, 10, 15); + frame.setMargin(50, 50, 20, 10); + + paragraph = new Paragraph(); + paragraph.addMarkup(text2, 11, BaseFont.Times); + paragraph.addMarkup(text2, 11, BaseFont.Times); + frame.add(paragraph); + + document.add(frame); + + final OutputStream outputStream = new FileOutputStream("build/frames.pdf"); + document.save(outputStream); + + } + +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/HelloDoc.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/HelloDoc.java new file mode 100644 index 0000000..6535995 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/HelloDoc.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class HelloDoc { + + @Test + public void test() throws Exception { + Document document = new Document(40, 60, 40, 60); + + Paragraph paragraph = new Paragraph(); + paragraph.addText("Hello Document", 20, + PDType1Font.HELVETICA); + document.add(paragraph); + + final OutputStream outputStream = new FileOutputStream("build/hellodoc.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Indentation.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Indentation.java new file mode 100644 index 0000000..7f1d235 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Indentation.java @@ -0,0 +1,158 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import org.xbib.graphics.layout.pdfbox.text.Indent; +import org.xbib.graphics.layout.pdfbox.text.SpaceUnit; +import org.xbib.graphics.layout.pdfbox.util.CompatibilityHelper; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.AlphabeticEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.ArabicEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.LowerCaseAlphabeticEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.LowerCaseRomanEnumerator; +import org.xbib.graphics.layout.pdfbox.util.Enumerators.RomanEnumerator; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Indentation { + + @Test + public void test() throws Exception { + String bulletOdd = CompatibilityHelper.getBulletCharacter(1) + " "; + String bulletEven = CompatibilityHelper.getBulletCharacter(2) + " "; + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat\n"; + + Document document = new Document(40, 60, 40, 60); + Paragraph paragraph = new Paragraph(); + paragraph + .addMarkup( + "This is an example for the new indent feature. Let's do some empty space indentation:\n", + 11, BaseFont.Times); + paragraph.add(new Indent(50, SpaceUnit.pt)); + paragraph.addMarkup("Here we go indented.\n", 11, BaseFont.Times); + paragraph.addMarkup( + "The Indentation holds for the rest of the paragraph, or... \n", + 11, BaseFont.Times); + paragraph.add(new Indent(70, SpaceUnit.pt)); + paragraph.addMarkup("any new indent comes.\n", 11, BaseFont.Times); + document.add(paragraph); + + paragraph = new Paragraph(); + paragraph + .addMarkup( + "New paragraph, now indentation is gone. But we can indent with a label also:\n", + 11, BaseFont.Times); + paragraph.add(new Indent("This is some label", 100, SpaceUnit.pt, 11, + PDType1Font.TIMES_BOLD)); + paragraph.addMarkup("Here we go indented.\n", 11, BaseFont.Times); + paragraph + .addMarkup( + "And again, the Indentation holds for the rest of the paragraph, or any new indent comes.\nLabels can be aligned:\n", + 11, BaseFont.Times); + paragraph.add(new Indent("Left", 100, SpaceUnit.pt, 11, + PDType1Font.TIMES_BOLD, Alignment.Left)); + paragraph.addMarkup("Indent with label aligned to the left.\n", 11, + BaseFont.Times); + paragraph.add(new Indent("Center", 100, SpaceUnit.pt, 11, + PDType1Font.TIMES_BOLD, Alignment.Center)); + paragraph.addMarkup("Indent with label aligned to the center.\n", 11, + BaseFont.Times); + paragraph.add(new Indent("Right", 100, SpaceUnit.pt, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("Indent with label aligned to the right.\n", 11, + BaseFont.Times); + document.add(paragraph); + + paragraph = new Paragraph(); + paragraph.addMarkup( + "So, what can you do with that? How about lists:\n", 11, + BaseFont.Times); + paragraph.add(new Indent(bulletOdd, 4, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("This is a list item\n", 11, BaseFont.Times); + paragraph.add(new Indent(bulletOdd, 4, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("Another list item\n", 11, BaseFont.Times); + paragraph.add(new Indent(bulletEven, 8, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("Sub list item\n", 11, BaseFont.Times); + paragraph.add(new Indent(bulletOdd, 4, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("And yet another one\n", 11, BaseFont.Times); + document.add(paragraph); + + paragraph = new Paragraph(); + paragraph.addMarkup("Also available with indents: Enumerators:\n", 11, + BaseFont.Times); + RomanEnumerator e1 = new RomanEnumerator(); + LowerCaseAlphabeticEnumerator e2 = new LowerCaseAlphabeticEnumerator(); + paragraph.add(new Indent(e1.next() + ". ", 4, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("First item\n", 11, BaseFont.Times); + paragraph.add(new Indent(e1.next() + ". ", 4, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("Second item\n", 11, BaseFont.Times); + paragraph.add(new Indent(e2.next() + ") ", 8, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("A sub item\n", 11, BaseFont.Times); + paragraph.add(new Indent(e2.next() + ") ", 8, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("Another sub item\n", 11, BaseFont.Times); + paragraph.add(new Indent(e1.next() + ". ", 4, SpaceUnit.em, 11, + PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("Third item\n", 11, BaseFont.Times); + document.add(paragraph); + + paragraph = new Paragraph(); + paragraph.addMarkup("The following types are built in:\n", 11, + BaseFont.Times); + paragraph.add(new Indent(new ArabicEnumerator().next() + " ", 4, + SpaceUnit.em, 11, PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("ArabicEnumerator\n", 11, BaseFont.Times); + paragraph.add(new Indent(new RomanEnumerator().next() + " ", 4, + SpaceUnit.em, 11, PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("RomanEnumerator\n", 11, BaseFont.Times); + paragraph.add(new Indent(new LowerCaseRomanEnumerator().next() + " ", + 4, SpaceUnit.em, 11, PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("LowerCaseRomanEnumerator\n", 11, BaseFont.Times); + paragraph.add(new Indent(new AlphabeticEnumerator().next() + " ", 4, + SpaceUnit.em, 11, PDType1Font.TIMES_BOLD, Alignment.Right)); + paragraph.addMarkup("AlphabeticEnumerator\n", 11, BaseFont.Times); + paragraph.add(new Indent(new LowerCaseAlphabeticEnumerator().next() + + " ", 4, SpaceUnit.em, 11, PDType1Font.TIMES_BOLD, + Alignment.Right)); + paragraph.addMarkup("LowerCaseAlphabeticEnumerator\n", 11, + BaseFont.Times); + document.add(paragraph); + + paragraph = new Paragraph(); + text1 = "For your convenience, you can do all that much easier with markup, e.g. simple indentation\n" + + "--At vero eos et accusam\n\n" + + "-!And end the indentation. Now a list:\n" + + "-+This is a list item\n" + + "-+Another list item\n" + + " -+A sub list item\n" + + "-+And yet another one\n\n" + + "-!Even enumeration is supported:\n" + + "-#This is a list item\n" + + "-#Another list item\n" + + " -#{a:}A sub list item\n" + + "-#And yet another one\n\n" + + "-!And you can customize it:\n" + + "-#{I ->:5}This is a list item\n" + + "-#{I ->:5}Another list item\n" + + " -#{a ~:30pt}A sub list item\n" + + "-#{I ->:5}And yet another one\n\n"; + paragraph.addMarkup(text1, 11, BaseFont.Times); + document.add(paragraph); + + final OutputStream outputStream = new FileOutputStream("build/indentation.pdf"); + document.save(outputStream); + } + +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Landscape.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Landscape.java new file mode 100644 index 0000000..d375f11 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Landscape.java @@ -0,0 +1,111 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.ControlElement; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.PageFormat; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.VerticalSpacer; +import org.xbib.graphics.layout.pdfbox.elements.render.ColumnLayout; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayout; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Landscape { + + @Test + public void main() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + String text2 = "At *vero eos et accusam* et justo duo dolores et ea rebum. " + + "Stet clita kasd gubergren, no sea takimata\n\n" + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + Paragraph paragraph1 = new Paragraph(); + paragraph1.addMarkup(text1, 11, BaseFont.Times); + Paragraph paragraph2 = new Paragraph(); + paragraph2.addMarkup(text2, 12, BaseFont.Helvetica); + Paragraph paragraph3 = new Paragraph(); + paragraph3.addMarkup(text1, 8, BaseFont.Courier); + + Paragraph titleA4 = new Paragraph(); + titleA4.addMarkup("*Format A4 in Portrait*", 20, BaseFont.Times); + Paragraph titleA5 = new Paragraph(); + titleA5.addMarkup("*Format A5 in Landscape*", 20, BaseFont.Times); + + PageFormat a5_landscape = PageFormat.with().A5().landscape().margins(10, 50, 0, 30).build(); + PageFormat a4_portrait = PageFormat.with().margins(40, 50, 40, 60).build(); + Document document = new Document(a4_portrait); + + document.add(titleA4, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + document.add(paragraph2); + + document.add(a5_landscape); + document.add(ControlElement.NEWPAGE); + document.add(new VerticalLayout()); + document.add(titleA5, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph3); + + document.add(a4_portrait); + document.add(ControlElement.NEWPAGE); + document.add(new VerticalLayout()); + document.add(titleA4, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + document.add(paragraph2); + + document.add(a5_landscape); + document.add(ControlElement.NEWPAGE); + document.add(new VerticalLayout()); + document.add(titleA5, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph3); + + final OutputStream outputStream = new FileOutputStream("build/landscape.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Letter.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Letter.java new file mode 100644 index 0000000..fdc822b --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Letter.java @@ -0,0 +1,87 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.ImageElement; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.VerticalSpacer; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import org.xbib.graphics.layout.pdfbox.text.Position; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Letter { + + @Test + public void test() throws Exception { + float hMargin = 40; + float vMargin = 50; + Document document = new Document(hMargin, hMargin, vMargin, vMargin); + + ImageElement image; + if (new File("arrow.png").exists()) { + image = new ImageElement("arrow.png"); + } else { + image = new ImageElement(Letter.class.getResourceAsStream("arrow.png")); + } + image.setWidth(image.getWidth() / 7); + image.setHeight(image.getHeight() / 7); + document.add(image, new VerticalLayoutHint(Alignment.Right, 0, 0, + 0, 0, true)); + + document.add(new VerticalSpacer(100)); + + Paragraph paragraph = new Paragraph(); + paragraph.addText("Blubberhausen, 01.04.2016", 11, + PDType1Font.HELVETICA); + document.add(paragraph, new VerticalLayoutHint(Alignment.Right, 0, 0, + 0, 0, true)); + + paragraph = new Paragraph(); + String address = "Ralf Stuckert\nAm Hollergraben 24\n67346 Blubberhausen"; + paragraph.addText(address, 11, PDType1Font.HELVETICA); + document.add(paragraph); + + paragraph = new Paragraph(); + paragraph.addMarkup("*Labore et dolore magna aliquyam erat*", 11, + BaseFont.Helvetica); + document.add(paragraph, new VerticalLayoutHint(Alignment.Left, 0, 0, + 40, 20)); + + String text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + paragraph = new Paragraph(); + paragraph.addMarkup(text, 11, BaseFont.Helvetica); + document.add(paragraph); + + document.add(paragraph); + + paragraph = new Paragraph(); + paragraph.addMarkup("Dolore magna aliquyam erat\nRalf Stuckert", 11, + BaseFont.Helvetica); + document.add(paragraph, new VerticalLayoutHint(Alignment.Left, 60, 0, + 40, 0)); + + paragraph = new Paragraph(); + paragraph.addMarkup("*Sanctus est:* Lorem ipsum dolor consetetur " + + "sadipscing sed diam nonumy eirmod tempor invidunt", 6, + BaseFont.Times); + paragraph.setAbsolutePosition(new Position(hMargin, vMargin)); + document.add(paragraph); + + final OutputStream outputStream = new FileOutputStream("build/letter.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/LineSpacing.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/LineSpacing.java new file mode 100644 index 0000000..e4cd354 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/LineSpacing.java @@ -0,0 +1,54 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.render.ColumnLayout; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class LineSpacing { + + @Test + public void test() throws Exception { + String text = "*Lorem ipsum* dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et _accusam et justo_ " + + "duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata " + + "sanctus est _Lorem ipsum dolor sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam."; + + // create document without margins + Document document = new Document(); + document.add(new ColumnLayout(2, 5)); + + Paragraph left = new Paragraph(); + // no line spacing for the first line + left.setApplyLineSpacingToFirstLine(false); + // use a bigger line spacing to visualize the effects of line spacing more drastically + left.setLineSpacing(1.5f); + left.setMaxWidth(document.getPageWidth() / 2); + left.addMarkup(text, 11, BaseFont.Times); + document.add(left); + + document.add(left); + document.add(left); + + document.add(ColumnLayout.NEWCOLUMN); + + Paragraph right = new Paragraph(); + right.setLineSpacing(1.5f); + right.setMaxWidth(document.getPageWidth() / 2); + right.addMarkup(text, 11, BaseFont.Times); + document.add(right); + + document.add(right); + document.add(right); + + final OutputStream outputStream = new FileOutputStream("build/linespacing.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Links.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Links.java new file mode 100644 index 0000000..071030c --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Links.java @@ -0,0 +1,73 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Links { + + @Test + public void main() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + String text2 = "At *vero {link[#hello]}eos{link} et accusam* et justo duo dolores et ea rebum." + + "Stet clita kasd gubergren, no sea takimata\n\n" + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + Document document = new Document(40, 60, 40, 60); + + + Paragraph paragraph0 = new Paragraph(); + paragraph0.addMarkup("This is a link to {link[https://github.com/ralfstuckert/pdfbox-layout]}PDFBox-Layout{link}.\n\n", 11, BaseFont.Times); + paragraph0.addMarkup("Now the same link with color instead of underline {color:#ff5000}{link:none[https://github.com/ralfstuckert/pdfbox-layout]}PDFBox-Layout{link}{color:#000000}.\n\n", 11, BaseFont.Times); + paragraph0.addMarkup("And here comes a link to an internal anchor name {color:#ff5000}{link[#hello]}hello{link}{color:#000000}.\n\n", 11, BaseFont.Times); + document.add(paragraph0); + + Paragraph paragraph1 = new Paragraph(); + paragraph1.addMarkup(text1, 11, BaseFont.Times); + document.add(paragraph1); + + Paragraph paragraph2 = new Paragraph(); + paragraph2.addMarkup(text2, 12, BaseFont.Helvetica); + document.add(paragraph2); + + Paragraph paragraph3 = new Paragraph(); + paragraph3.addMarkup(text1, 8, BaseFont.Courier); + document.add(paragraph3); + + Paragraph paragraph4 = new Paragraph(); + paragraph4.addMarkup("\n\n{anchor:hello}Here{anchor} comes the internal anchor named *hello*\n\n", 15, BaseFont.Courier); + + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph1); + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph3); + + document.add(paragraph4); + + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + + final OutputStream outputStream = new FileOutputStream("build/links.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Listener.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Listener.java new file mode 100644 index 0000000..718f859 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Listener.java @@ -0,0 +1,83 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderContext; +import org.xbib.graphics.layout.pdfbox.elements.render.RenderListener; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.TextFlow; +import org.xbib.graphics.layout.pdfbox.text.TextFlowUtil; +import org.xbib.graphics.layout.pdfbox.text.TextSequenceUtil; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Listener { + + @Test + public void test() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + String text2 = "At *vero eos et accusam* et justo duo dolores et ea rebum." + + "Stet clita kasd gubergren, no sea takimata\n\n" + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + + Document document = new Document(40, 60, 40, 60); + document.addRenderListener(new RenderListener() { + + @Override + public void beforePage(RenderContext renderContext) { + + } + + @Override + public void afterPage(RenderContext renderContext) + throws IOException { + String content = String.format("Page %s", + renderContext.getPageIndex() + 1); + TextFlow text = TextFlowUtil.createTextFlow(content, 11, + PDType1Font.TIMES_ROMAN); + float offset = renderContext.getPageFormat().getMarginLeft() + + TextSequenceUtil.getOffset(text, + renderContext.getWidth(), Alignment.Right); + text.drawText(renderContext.getContentStream(), new Position( + offset, 30), Alignment.Right, null); + } + }); + + Paragraph paragraph = new Paragraph(); + paragraph.addMarkup(text1, 11, BaseFont.Times); + paragraph.addMarkup(text2, 12, BaseFont.Helvetica); + paragraph.addMarkup(text1, 8, BaseFont.Courier); + + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + document.add(paragraph); + + final OutputStream outputStream = new FileOutputStream("build/listener.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/LowLevelText.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/LowLevelText.java new file mode 100644 index 0000000..57b2a5a --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/LowLevelText.java @@ -0,0 +1,120 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.shape.RoundRect; +import org.xbib.graphics.layout.pdfbox.shape.Shape; +import org.xbib.graphics.layout.pdfbox.shape.Stroke; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import org.xbib.graphics.layout.pdfbox.text.Constants; +import org.xbib.graphics.layout.pdfbox.text.DrawContext; +import org.xbib.graphics.layout.pdfbox.text.Position; +import org.xbib.graphics.layout.pdfbox.text.TextFlow; +import org.xbib.graphics.layout.pdfbox.text.TextFlowUtil; +import org.xbib.graphics.layout.pdfbox.text.TextSequenceUtil; +import org.xbib.graphics.layout.pdfbox.text.annotations.AnnotationDrawListener; +import java.awt.Color; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class LowLevelText { + + @Test + public void test() throws Exception { + + final PDDocument test = new PDDocument(); + final PDPage page = new PDPage(Constants.A4); + float pageWidth = page.getMediaBox().getWidth(); + float pageHeight = page.getMediaBox().getHeight(); + + test.addPage(page); + final PDPageContentStream contentStream = + new PDPageContentStream(test, page, PDPageContentStream.AppendMode.APPEND, true); + + // AnnotationDrawListener is only needed if you use annoation based stuff, e.g. hyperlinks + AnnotationDrawListener annotationDrawListener = new AnnotationDrawListener(new DrawContext() { + + @Override + public PDDocument getPdDocument() { + return test; + } + + @Override + public PDPage getCurrentPage() { + return page; + } + + @Override + public PDPageContentStream getCurrentPageContentStream() { + return contentStream; + } + + }); + annotationDrawListener.beforePage(null); + + TextFlow text = TextFlowUtil + .createTextFlowFromMarkup( + "Hello *bold _italic bold-end* italic-end_. Eirmod\ntempor invidunt ut \\*labore", + 11, BaseFont.Times); + + text.addText("Spongebob", 11, PDType1Font.COURIER); + text.addText(" is ", 20, PDType1Font.HELVETICA_BOLD_OBLIQUE); + text.addText("cool", 7, PDType1Font.HELVETICA); + + text.setMaxWidth(100); + float xOffset = TextSequenceUtil.getOffset(text, pageWidth, + Alignment.Right); + text.drawText(contentStream, new Position(xOffset, pageHeight - 50), + Alignment.Right, annotationDrawListener); + + String textBlock = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "{link[https://github.com/ralfstuckert/pdfbox-layout/]}pdfbox layout{link} " + + "sed diam nonumy eirmod invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + text = new TextFlow(); + text.addMarkup(textBlock, 8, BaseFont.Courier); + text.setMaxWidth(200); + xOffset = TextSequenceUtil.getOffset(text, pageWidth, Alignment.Center); + text.drawText(contentStream, new Position(xOffset, pageHeight - 100), + Alignment.Justify, annotationDrawListener); + + // draw a round rect box with text + text.setMaxWidth(350); + float x = 50; + float y = pageHeight - 300; + float paddingX = 20; + float paddingY = 15; + float boxWidth = text.getWidth() + 2 * paddingX; + float boxHeight = text.getHeight() + 2 * paddingY; + + Shape shape = new RoundRect(20); + shape.fill(test, contentStream, new Position(x, y), boxWidth, + boxHeight, Color.pink, null); + shape.draw(test, contentStream, new Position(x, y), boxWidth, + boxHeight, Color.blue, new Stroke(3), null); + // now the text + text.drawText(contentStream, new Position(x + paddingX, y - paddingY), + Alignment.Center, annotationDrawListener); + + annotationDrawListener.afterPage(null); + contentStream.close(); + + annotationDrawListener.afterRender(); + + final OutputStream outputStream = new FileOutputStream("build/lowleveltext.pdf"); + test.save(outputStream); + test.close(); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Margin.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Margin.java new file mode 100644 index 0000000..6705234 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Margin.java @@ -0,0 +1,58 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Margin { + + @Test + public void test() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, sed diam voluptua. At vero eos et accusam et justo " + + "duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata " + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et accusam et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."; + + String text2 = "short text, right aligned with some margin"; + + String text3 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, sed diam voluptua. At vero eos et accusam et justo " + + "duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata " + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et accusam et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."; + + Document document = new Document(40, 60, 40, 60); + Paragraph paragraph = new Paragraph(); + paragraph.addText(text1, 11, PDType1Font.HELVETICA); + document.add(paragraph, new VerticalLayoutHint(Alignment.Left, 0, 100, + 100, 100)); + + paragraph = new Paragraph(); + paragraph.addText(text2, 11, PDType1Font.HELVETICA); + document.add(paragraph, new VerticalLayoutHint(Alignment.Right, 0, 50, + 0, 0)); + + paragraph = new Paragraph(); + paragraph.addText(text3, 11, PDType1Font.HELVETICA); + document.add(paragraph, new VerticalLayoutHint(Alignment.Right, 150, + 150, 20, 0)); + + final OutputStream outputStream = new FileOutputStream("build/margin.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Markup.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Markup.java new file mode 100644 index 0000000..c55ddd2 --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Markup.java @@ -0,0 +1,81 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.text.Alignment; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Markup { + + @Test + public void test() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + Document document = new Document(40, 60, 40, 60); + Paragraph paragraph = new Paragraph(); + paragraph.addMarkup(text1, 11, BaseFont.Times); + document.add(paragraph); + + paragraph = new Paragraph(); + paragraph + .addMarkup( + "Markup supports *bold*, _italic_, and *even _mixed* markup_.\n\n", + 11, BaseFont.Times); + paragraph.addMarkup( + "Escape \\* with \\\\\\* and \\_ with \\\\\\_ in markup.\n\n", + 11, BaseFont.Times); + + paragraph + .addMarkup( + "And now also {color:#ff0000}c{color:#00ff00}o{color:#0000ff}l{color:#00cccc}o{color:#cc00cc}r{color:#000000}", + 11, BaseFont.Times); + paragraph.addMarkup(" , {_}subscript{_} and {^}superscript{^}.\n\n", + 11, BaseFont.Times); + + paragraph + .addMarkup( + "You can alternate the position and thickness of an __underline__, " + + "so you may also use this to __{0.25:}strike through__ or blacken __{0.25:20}things__ out\n\n", + 11, BaseFont.Times); + + document.add(paragraph, new VerticalLayoutHint(Alignment.Left, 0, 0, + 30, 0)); + + paragraph = new Paragraph(); + text1 = "\nAlso, you can do all that indentation stuff much easier with markup, e.g. simple indentation\n" + + "--At vero eos et accusam\n\n" + + "-!And end the indentation. Now a list:\n" + + "-+This is a list item\n" + + "-+Another list item\n" + + " -+A sub list item\n" + + "-+And yet another one\n\n" + + "-!Even enumeration is supported:\n" + + "-#This is a list item\n" + + "-#Another list item\n" + + " -#{a:}A sub list item\n" + + "-#And yet another one\n\n" + + "-!And you can customize it:\n" + + "-#{I ->:5}This is a list item\n" + + "-#{I ->:5}Another list item\n" + + " -#{a ~:30pt}A sub list item\n" + + "-#{I ->:5}And yet another one\n\n"; + paragraph.addMarkup(text1, 11, BaseFont.Times); + document.add(paragraph); + + final OutputStream outputStream = new FileOutputStream("build/markup.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/MultiplePages.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/MultiplePages.java new file mode 100644 index 0000000..fe2c26a --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/MultiplePages.java @@ -0,0 +1,76 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class MultiplePages { + + @Test + public void test() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + String text2 = "At *vero eos et accusam* et justo duo dolores et ea rebum." + + "Stet clita kasd gubergren, no sea takimata\n\n" + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + Document document = new Document(40, 60, 40, 60); + + Paragraph paragraph1 = new Paragraph(); + paragraph1.addMarkup(text1, 11, BaseFont.Times); + document.add(paragraph1); + + Paragraph paragraph2 = new Paragraph(); + paragraph2.addMarkup(text2, 12, BaseFont.Helvetica); + document.add(paragraph2); + + Paragraph paragraph3 = new Paragraph(); + paragraph3.addMarkup(text1, 8, BaseFont.Courier); + document.add(paragraph3); + + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph1); + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph3); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + + final OutputStream outputStream = new FileOutputStream("build/multiplepages.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Rotation.java b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Rotation.java new file mode 100644 index 0000000..3ca6f7d --- /dev/null +++ b/layout-pdfbox/src/test/java/org/xbib/graphics/layout/pdfbox/Rotation.java @@ -0,0 +1,116 @@ +package org.xbib.graphics.layout.pdfbox; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.layout.pdfbox.elements.ControlElement; +import org.xbib.graphics.layout.pdfbox.elements.Document; +import org.xbib.graphics.layout.pdfbox.elements.PageFormat; +import org.xbib.graphics.layout.pdfbox.elements.Paragraph; +import org.xbib.graphics.layout.pdfbox.elements.VerticalSpacer; +import org.xbib.graphics.layout.pdfbox.elements.render.ColumnLayout; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayout; +import org.xbib.graphics.layout.pdfbox.elements.render.VerticalLayoutHint; +import org.xbib.graphics.layout.pdfbox.text.BaseFont; +import java.io.FileOutputStream; +import java.io.OutputStream; + +public class Rotation { + + @Test + public void test() throws Exception { + String text1 = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " + + "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna " + + "aliquyam erat, _sed diam_ voluptua. At vero eos et *accusam et justo* " + + "duo dolores et ea rebum.\n\nStet clita kasd gubergren, no sea takimata " + + "sanctus est *Lorem ipsum _dolor* sit_ amet. Lorem ipsum dolor sit amet, " + + "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, *sed diam voluptua.\n\n" + + "At vero eos et accusam* et justo duo dolores et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n"; + + String text2 = "At *vero eos et accusam* et justo duo dolores et ea rebum. " + + "Stet clita kasd gubergren, no sea takimata\n\n" + + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, " + + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt " + + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero " + + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd " + + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n"; + + Paragraph paragraph1 = new Paragraph(); + paragraph1.addMarkup(text1, 11, BaseFont.Times); + Paragraph paragraph2 = new Paragraph(); + paragraph2.addMarkup(text2, 12, BaseFont.Helvetica); + Paragraph paragraph3 = new Paragraph(); + paragraph3.addMarkup(text1, 8, BaseFont.Courier); + + Paragraph titleA4 = new Paragraph(); + titleA4.addMarkup("*Format A4 Landscape*", 20, BaseFont.Times); + Paragraph titleA5 = new Paragraph(); + titleA5.addMarkup("*Format A4 Landscape rotated by -90 degrees*", 20, BaseFont.Times); + + PageFormat a4_landscape = PageFormat.with().margins(40, 50, 40, 60).landscape().build(); + PageFormat a4_landscape_rotated = PageFormat.with().margins(40, 50, 40, 60).landscape().rotation(-90).build(); + + Document document = new Document(a4_landscape); + + document.add(titleA4, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + + document.add(a4_landscape_rotated); + document.add(ControlElement.NEWPAGE); + document.add(new VerticalLayout()); + document.add(titleA5, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + + document.add(a4_landscape); + document.add(ControlElement.NEWPAGE); + document.add(new VerticalLayout()); + document.add(titleA4, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + + document.add(a4_landscape_rotated); + document.add(ControlElement.NEWPAGE); + document.add(new VerticalLayout()); + document.add(titleA5, VerticalLayoutHint.CENTER); + document.add(new VerticalSpacer(5)); + document.add(new ColumnLayout(2, 10)); + + document.add(paragraph2); + document.add(paragraph1); + document.add(paragraph1); + document.add(paragraph3); + document.add(paragraph2); + document.add(paragraph2); + document.add(paragraph3); + + final OutputStream outputStream = new FileOutputStream("build/rotation.pdf"); + document.save(outputStream); + + } +} diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/aligned.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/aligned.pdf new file mode 100644 index 0000000..0c10685 Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/aligned.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/arrow.png b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/arrow.png new file mode 100644 index 0000000..ef60fc3 Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/arrow.png differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/columns.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/columns.pdf new file mode 100644 index 0000000..b74725e Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/columns.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/customannotation.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/customannotation.pdf new file mode 100644 index 0000000..4f554c6 --- /dev/null +++ b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/customannotation.pdf @@ -0,0 +1,116 @@ +%PDF-1.4 +%öäüß +1 0 obj +<< +/Type /Catalog +/Version /1.4 +/Pages 2 0 R +>> +endobj +2 0 obj +<< +/Type /Pages +/Kids [3 0 R] +/Count 1 +>> +endobj +3 0 obj +<< +/Type /Page +/MediaBox [0.0 0.0 595.27563 841.8898] +/Parent 2 0 R +/Contents 4 0 R +/Resources 5 0 R +/Annots [6 0 R 7 0 R 8 0 R 9 0 R] +>> +endobj +4 0 obj +<< +/Filter [/FlateDecode] +/Length 10 0 R +>> +stream +xœÅRÑJÃ0}¿_q7xÛ&¹é«LÙ«ðy`l»Õ•¦™Î¿·í²¢8Ü@EBȹ¹‡œÎmPª\‰”•Î$k¥‘e"ŒÉ™Iô` -ÜXHú• $ä<L=‹M–¢}†ë»¾ChŸ€F–/`¶tuÝÌÑ®a†¡tÞ]ÅbÀV]‡£¬Š²îwpãÅ­…û¯â†Î‹·‡7ETX½¼–«à^œå[³;6VÛðÁ¢øÆ‚6ç-Ñ_m¦owí®*Šzª]399-¤ôg¡‘5ð~ž”ÿ^vÁäüqxéóó+á‘9Þ;eÑò +endstream +endobj +5 0 obj +<< +/Font 11 0 R +>> +endobj +6 0 obj +<< +/Type /Annot +/Subtype /Highlight +/QuadPoints [126.7 800.8898 175.06 800.8898 126.7 789.8898 175.06 789.8898] +/Rect [126.7 789.8898 175.06 800.8898] +/C [0.0 1.0 0.0] +>> +endobj +7 0 obj +<< +/Type /Annot +/Subtype /Highlight +/QuadPoints [40.0 788.8898 56.120003 788.8898 40.0 777.8898 56.120003 777.8898] +/Rect [40.0 777.8898 56.120003 788.8898] +/C [0.0 1.0 0.0] +>> +endobj +8 0 obj +<< +/Type /Annot +/Subtype /Highlight +/QuadPoints [126.7 752.8898 175.06 752.8898 126.7 741.8898 175.06 741.8898] +/Rect [126.7 741.8898 175.06 752.8898] +/C [1.0 1.0 0.0] +>> +endobj +9 0 obj +<< +/Type /Annot +/Subtype /Highlight +/QuadPoints [40.0 740.8898 56.120003 740.8898 40.0 729.8898 56.120003 729.8898] +/Rect [40.0 729.8898 56.120003 740.8898] +/C [1.0 1.0 0.0] +>> +endobj +10 0 obj +239 +endobj +11 0 obj +<< +/F0 12 0 R +>> +endobj +12 0 obj +<< +/Type /Font +/Subtype /Type1 +/BaseFont /Helvetica +/Encoding /WinAnsiEncoding +>> +endobj +xref +0 13 +0000000000 65535 f +0000000015 00000 n +0000000078 00000 n +0000000135 00000 n +0000000288 00000 n +0000000606 00000 n +0000000640 00000 n +0000000826 00000 n +0000001018 00000 n +0000001204 00000 n +0000001396 00000 n +0000001416 00000 n +0000001449 00000 n +trailer +<< +/Root 1 0 R +/ID [ ] +/Size 13 +>> +startxref +1547 +%%EOF diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/customrenderer.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/customrenderer.pdf new file mode 100644 index 0000000..3931e1a Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/customrenderer.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/frames.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/frames.pdf new file mode 100644 index 0000000..887b1f8 Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/frames.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/hellodoc.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/hellodoc.pdf new file mode 100644 index 0000000..ed9115e --- /dev/null +++ b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/hellodoc.pdf @@ -0,0 +1,76 @@ +%PDF-1.4 +%öäüß +1 0 obj +<< +/Type /Catalog +/Version /1.4 +/Pages 2 0 R +>> +endobj +2 0 obj +<< +/Type /Pages +/Kids [3 0 R] +/Count 1 +>> +endobj +3 0 obj +<< +/Type /Page +/MediaBox [0.0 0.0 595.27563 841.8898] +/Parent 2 0 R +/Contents 4 0 R +/Resources 5 0 R +>> +endobj +4 0 obj +<< +/Filter [/FlateDecode] +/Length 6 0 R +>> +stream +xœÊM +Â0Eáù]ÅÖI|Ióò’©¨8.@¢ ý¡E÷o)ßèÀY LB-ê‚iê£%M´è]ÎÅLT,smx`‚S…§l<£Ðrï4Ëê5ÖÇ«0ë ²oëÝ­ Ã|`ý ãy~þÆ6}÷¼TÜ7훿 +endstream +endobj +5 0 obj +<< +/Font 7 0 R +>> +endobj +6 0 obj +118 +endobj +7 0 obj +<< +/F0 8 0 R +>> +endobj +8 0 obj +<< +/Type /Font +/Subtype /Type1 +/BaseFont /Helvetica +/Encoding /WinAnsiEncoding +>> +endobj +xref +0 9 +0000000000 65535 f +0000000015 00000 n +0000000078 00000 n +0000000135 00000 n +0000000254 00000 n +0000000450 00000 n +0000000483 00000 n +0000000502 00000 n +0000000533 00000 n +trailer +<< +/Root 1 0 R +/ID [ ] +/Size 9 +>> +startxref +630 +%%EOF diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/indentation.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/indentation.pdf new file mode 100644 index 0000000..f39f9b2 Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/indentation.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/landscape.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/landscape.pdf new file mode 100644 index 0000000..287128e Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/landscape.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/letter.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/letter.pdf new file mode 100644 index 0000000..4756ccb Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/letter.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/linespacing.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/linespacing.pdf new file mode 100644 index 0000000..bc3292f Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/linespacing.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/links.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/links.pdf new file mode 100644 index 0000000..94a413f Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/links.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/listener.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/listener.pdf new file mode 100644 index 0000000..359dd4c Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/listener.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/lowleveltext.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/lowleveltext.pdf new file mode 100644 index 0000000..36db260 Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/lowleveltext.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/margin.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/margin.pdf new file mode 100644 index 0000000..465da57 --- /dev/null +++ b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/margin.pdf @@ -0,0 +1,79 @@ +%PDF-1.4 +%öäüß +1 0 obj +<< +/Type /Catalog +/Version /1.4 +/Pages 2 0 R +>> +endobj +2 0 obj +<< +/Type /Pages +/Kids [3 0 R] +/Count 1 +>> +endobj +3 0 obj +<< +/Type /Page +/MediaBox [0.0 0.0 595.27563 841.8898] +/Parent 2 0 R +/Contents 4 0 R +/Resources 5 0 R +>> +endobj +4 0 obj +<< +/Filter [/FlateDecode] +/Length 6 0 R +>> +stream +xœÍ–;oÛ0…wý +Ž)¨|?Æh§.A t¦mFfbI6EºÍ¿/%˜rëX~ ±x¡d’:Ÿtxî]gp˜b9Œ*8ã@P”K©„€ + œÉ~eUö­/˜½Î¾N2`ü!@ã:…r¡ R®À¤Ì>!0yÌ`7ÍÙÝÚ™ò˜> +endobj +6 0 obj +686 +endobj +7 0 obj +<< +/F0 8 0 R +>> +endobj +8 0 obj +<< +/Type /Font +/Subtype /Type1 +/BaseFont /Helvetica +/Encoding /WinAnsiEncoding +>> +endobj +xref +0 9 +0000000000 65535 f +0000000015 00000 n +0000000078 00000 n +0000000135 00000 n +0000000254 00000 n +0000001018 00000 n +0000001051 00000 n +0000001070 00000 n +0000001101 00000 n +trailer +<< +/Root 1 0 R +/ID [<8E57577CC59848BA1707730A4C5BAD9B> ] +/Size 9 +>> +startxref +1198 +%%EOF diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/markup.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/markup.pdf new file mode 100644 index 0000000..972f878 Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/markup.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/multiplepages.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/multiplepages.pdf new file mode 100644 index 0000000..8864dd4 Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/multiplepages.pdf differ diff --git a/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/rotation.pdf b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/rotation.pdf new file mode 100644 index 0000000..ea26c8e Binary files /dev/null and b/layout-pdfbox/src/test/resources/org/xbib/graphics/layout/pdfbox/rotation.pdf differ diff --git a/png/NOTICE.txt b/png/NOTICE.txt new file mode 100644 index 0000000..32aae6f --- /dev/null +++ b/png/NOTICE.txt @@ -0,0 +1,15 @@ +This is a Java 11 version of the project PNGJ + +https://github.com/leonbloy/pngj + +and ImageIO-Ext + +https://github.com/geosolutions-it/imageio-ext + +as of 28 Oct 2020. + +Also based on the project + +https://github.com/gredler/jdk9-png-writer-backport + +by Daniel Gredler diff --git a/png/build.gradle b/png/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/png/build/resources/main/META-INF/services/javax.imageio.spi.ImageWriterSpi b/png/build/resources/main/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 0000000..883ad6e --- /dev/null +++ b/png/build/resources/main/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1 @@ +o \ No newline at end of file diff --git a/png/build/tmp/compileJava/source-classes-mapping.txt b/png/build/tmp/compileJava/source-classes-mapping.txt new file mode 100644 index 0000000..dc6eb9b --- /dev/null +++ b/png/build/tmp/compileJava/source-classes-mapping.txt @@ -0,0 +1,234 @@ +org/xbib/graphics/imageio/plugins/png/pngj/PngReaderFilter.java + org.xbib.graphics.imageio.plugins.png.pngj.PngReaderFilter + org.xbib.graphics.imageio.plugins.png.pngj.PngReaderFilter$1 +org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterDefault.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriterDefault +org/xbib/graphics/imageio/plugins/png/pngj/IBytesConsumer.java + org.xbib.graphics.imageio.plugins.png.pngj.IBytesConsumer +org/xbib/graphics/imageio/plugins/png/ScanlineProvider.java + org.xbib.graphics.imageio.plugins.png.ScanlineProvider +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkMultiple.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkMultiple +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkCopyBehaviour.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkCopyBehaviour + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkCopyBehaviour$1 +org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterMultiple.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriterMultiple +org/xbib/graphics/imageio/plugins/png/RasterShortABGRProvider.java + org.xbib.graphics.imageio.plugins.png.RasterShortABGRProvider +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIEND.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIEND +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngMetadata.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngMetadata + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngMetadata$1 +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSingle.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkSingle +org/xbib/graphics/imageio/plugins/png/pngj/Deinterlacer.java + org.xbib.graphics.imageio.plugins.png.pngj.Deinterlacer +org/xbib/graphics/imageio/plugins/png/pngj/RowInfo.java + org.xbib.graphics.imageio.plugins.png.pngj.RowInfo +org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorLz4.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.DeflaterEstimatorLz4 +org/xbib/graphics/imageio/plugins/png/pngj/PngjOutputException.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjOutputException +org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandSkippingBytesProvider.java + org.xbib.graphics.imageio.plugins.png.RasterByteSingleBandSkippingBytesProvider +org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReaderPng.java + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqReaderPng + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqReaderPng$1 +org/xbib/graphics/imageio/plugins/png/pngj/PngReaderByte.java + org.xbib.graphics.imageio.plugins.png.pngj.PngReaderByte +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSTER.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkSTER +org/xbib/graphics/imageio/plugins/png/pngj/IImageLineFactory.java + org.xbib.graphics.imageio.plugins.png.pngj.IImageLineFactory +org/xbib/graphics/imageio/plugins/png/pngj/PngWriterHc.java + org.xbib.graphics.imageio.plugins.png.pngj.PngWriterHc +org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSetFactory.java + org.xbib.graphics.imageio.plugins.png.pngj.IImageLineSetFactory +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPLTE.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkPLTE +org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal2.java + org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal2 +org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandProvider.java + org.xbib.graphics.imageio.plugins.png.RasterByteSingleBandProvider +org/xbib/graphics/imageio/plugins/png/pngj/ChunkReader.java + org.xbib.graphics.imageio.plugins.png.pngj.ChunkReader + org.xbib.graphics.imageio.plugins.png.pngj.ChunkReader$ChunkReaderMode +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkFactory.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkFactory +org/xbib/graphics/imageio/plugins/png/RasterByteRepackSingleBandProvider.java + org.xbib.graphics.imageio.plugins.png.RasterByteRepackSingleBandProvider +org/xbib/graphics/imageio/plugins/png/RasterByteGrayAlphaProvider.java + org.xbib.graphics.imageio.plugins.png.RasterByteGrayAlphaProvider +org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamLz4.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.CompressorStreamLz4 +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkICCP.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkICCP +org/xbib/graphics/imageio/plugins/png/RasterByteABGRProvider.java + org.xbib.graphics.imageio.plugins.png.RasterByteABGRProvider +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunk.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunk + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunk$ChunkOrderingConstraint +org/xbib/graphics/imageio/plugins/png/ScanlineCursor.java + org.xbib.graphics.imageio.plugins.png.ScanlineCursor +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkPredicate.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkPredicate +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksListForWrite.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksListForWrite + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksListForWrite$1 +org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorHjg.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.DeflaterEstimatorHjg +org/xbib/graphics/imageio/plugins/png/pngj/ImageInfo.java + org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo +org/xbib/graphics/imageio/plugins/png/pngj/PngjBadSignature.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjBadSignature +org/xbib/graphics/imageio/plugins/png/pngj/ImageLineInt.java + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineInt + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineInt$1 +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIDAT.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIDAT +org/xbib/graphics/imageio/plugins/png/pngj/IdatChunkWriter.java + org.xbib.graphics.imageio.plugins.png.pngj.IdatChunkWriter +org/xbib/graphics/imageio/plugins/png/pngj/PngjPrematureEnding.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjPrematureEnding +org/xbib/graphics/imageio/plugins/png/pngj/ImageLineByte.java + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineByte + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineByte$1 +org/xbib/graphics/imageio/plugins/png/pngj/PngReaderApng.java + org.xbib.graphics.imageio.plugins.png.pngj.PngReaderApng + org.xbib.graphics.imageio.plugins.png.pngj.PngReaderApng$1 +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksList.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksList + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksList$1 + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksList$2 + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksList$3 +org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSet.java + org.xbib.graphics.imageio.plugins.png.pngj.IImageLineSet +org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqSkipping.java + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqSkipping + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqSkipping$1 +org/xbib/graphics/imageio/plugins/png/pngj/IImageLineArray.java + org.xbib.graphics.imageio.plugins.png.pngj.IImageLineArray +org/xbib/graphics/imageio/plugins/png/pngj/PngjException.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjException +org/xbib/graphics/imageio/plugins/png/pngj/ErrorBehaviour.java + org.xbib.graphics.imageio.plugins.png.pngj.ErrorBehaviour +org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqBuffering.java + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqBuffering +org/xbib/graphics/imageio/plugins/png/pngj/BufferedStreamFeeder.java + org.xbib.graphics.imageio.plugins.png.pngj.BufferedStreamFeeder +org/xbib/graphics/imageio/plugins/png/pngj/PngReaderInt.java + org.xbib.graphics.imageio.plugins.png.pngj.PngReaderInt +org/xbib/graphics/imageio/plugins/png/pngj/PngjExceptionInternal.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjExceptionInternal +org/xbib/graphics/imageio/plugins/png/pngj/PngjUnsupportedException.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjUnsupportedException +org/xbib/graphics/imageio/plugins/png/pngj/ImageLineSetDefault.java + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineSetDefault + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineSetDefault$1 + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineSetDefault$1$1 +org/xbib/graphics/imageio/plugins/png/RasterShortGrayAlphaProvider.java + org.xbib.graphics.imageio.plugins.png.RasterShortGrayAlphaProvider +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkLoadBehaviour.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkLoadBehaviour +org/xbib/graphics/imageio/plugins/png/pngj/ImageLineHelper.java + org.xbib.graphics.imageio.plugins.png.pngj.ImageLineHelper +org/xbib/graphics/imageio/plugins/png/pngj/IPngWriterFactory.java + org.xbib.graphics.imageio.plugins.png.pngj.IPngWriterFactory +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngBadCharsetException.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngBadCharsetException +org/xbib/graphics/imageio/plugins/png/AbstractScanlineProvider.java + org.xbib.graphics.imageio.plugins.png.AbstractScanlineProvider +org/xbib/graphics/imageio/plugins/png/pngj/PngjBadCrcException.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjBadCrcException +org/xbib/graphics/imageio/plugins/png/RasterIntABGRProvider.java + org.xbib.graphics.imageio.plugins.png.RasterIntABGRProvider +org/xbib/graphics/imageio/plugins/png/pngj/PngjInputException.java + org.xbib.graphics.imageio.plugins.png.pngj.PngjInputException +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkUNKNOWN.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkUNKNOWN +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTEXT.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkTEXT +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkITXT.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkITXT +org/xbib/graphics/imageio/plugins/png/ScanlineProviderFactory.java + org.xbib.graphics.imageio.plugins.png.ScanlineProviderFactory +org/xbib/graphics/imageio/plugins/png/pngj/IChunkFactory.java + org.xbib.graphics.imageio.plugins.png.pngj.IChunkFactory +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkZTXT.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkZTXT +org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunksSet.java + org.xbib.graphics.imageio.plugins.png.pngj.DeflatedChunksSet + org.xbib.graphics.imageio.plugins.png.pngj.DeflatedChunksSet$State +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTextVar.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkTextVar + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkTextVar$PngTxtInfo +org/xbib/graphics/imageio/plugins/png/RasterShortSingleBandProvider.java + org.xbib.graphics.imageio.plugins.png.RasterShortSingleBandProvider +org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamDeflater.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.CompressorStreamDeflater +org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStream.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.CompressorStream +org/xbib/graphics/imageio/plugins/png/pngj/FilterType.java + org.xbib.graphics.imageio.plugins.png.pngj.FilterType +org/xbib/graphics/imageio/plugins/png/pngj/IImageLine.java + org.xbib.graphics.imageio.plugins.png.pngj.IImageLine +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkHIST.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkHIST +org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunkReader.java + org.xbib.graphics.imageio.plugins.png.pngj.DeflatedChunkReader +org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriter.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriter + org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriter$1 +org/xbib/graphics/imageio/plugins/png/pngj/pixels/FiltersPerformance.java + org.xbib.graphics.imageio.plugins.png.pngj.pixels.FiltersPerformance + org.xbib.graphics.imageio.plugins.png.pngj.pixels.FiltersPerformance$1 +org/xbib/graphics/imageio/plugins/png/PNGWriter.java + org.xbib.graphics.imageio.plugins.png.PNGWriter +org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal.java + org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSRGB.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkSRGB +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkGAMA.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkGAMA +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIHDR.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIHDR +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkOFFS.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkOFFS +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkRaw.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkRaw +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkBKGD.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkBKGD +org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkHelper.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkHelper + org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkHelper$1 +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPHYS.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkPHYS +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSBIT.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkSBIT +org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReader.java + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqReader + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqReader$1 + org.xbib.graphics.imageio.plugins.png.pngj.ChunkSeqReader$2 +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFCTL.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkFCTL +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkCHRM.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkCHRM +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFDAT.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkFDAT +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSPLT.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkSPLT +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkACTL.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkACTL +org/xbib/graphics/imageio/plugins/png/pngj/PngReader.java + org.xbib.graphics.imageio.plugins.png.pngj.PngReader +org/xbib/graphics/imageio/plugins/png/pngj/PngWriter.java + org.xbib.graphics.imageio.plugins.png.pngj.PngWriter +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTRNS.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkTRNS +org/xbib/graphics/imageio/plugins/png/pngj/IdatSet.java + org.xbib.graphics.imageio.plugins.png.pngj.IdatSet + org.xbib.graphics.imageio.plugins.png.pngj.IdatSet$1 +org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTIME.java + org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkTIME diff --git a/png/build/tmp/compileTestJava/source-classes-mapping.txt b/png/build/tmp/compileTestJava/source-classes-mapping.txt new file mode 100644 index 0000000..ebf06f1 --- /dev/null +++ b/png/build/tmp/compileTestJava/source-classes-mapping.txt @@ -0,0 +1,19 @@ +org/xbib/graphics/imageio/plugins/png/CustomByteIndexImageTypesTest.java + org.xbib.graphics.imageio.plugins.png.CustomByteIndexImageTypesTest +org/xbib/graphics/imageio/plugins/png/ImageAssert.java + org.xbib.graphics.imageio.plugins.png.ImageAssert + org.xbib.graphics.imageio.plugins.png.ImageAssert$1 + org.xbib.graphics.imageio.plugins.png.ImageAssert$2 +org/xbib/graphics/imageio/plugins/png/PNGWriterTest.java + org.xbib.graphics.imageio.plugins.png.PNGWriterTest +org/xbib/graphics/imageio/plugins/png/BufferedImageTypesTest.java + org.xbib.graphics.imageio.plugins.png.BufferedImageTypesTest +org/xbib/graphics/imageio/plugins/png/PngSuiteImagesTest.java + org.xbib.graphics.imageio.plugins.png.PngSuiteImagesTest + org.xbib.graphics.imageio.plugins.png.PngSuiteImagesTest$1 +org/xbib/graphics/imageio/plugins/png/SampleImagePainter.java + org.xbib.graphics.imageio.plugins.png.SampleImagePainter +org/xbib/graphics/imageio/plugins/png/CustomUShortImageTypesTest.java + org.xbib.graphics.imageio.plugins.png.CustomUShortImageTypesTest +org/xbib/graphics/imageio/plugins/png/BufferedImageChildTest.java + org.xbib.graphics.imageio.plugins.png.BufferedImageChildTest diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/AbstractScanlineProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/AbstractScanlineProvider.java new file mode 100644 index 0000000..b3f9704 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/AbstractScanlineProvider.java @@ -0,0 +1,105 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.ComponentSampleModel; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; + +public abstract class AbstractScanlineProvider implements ScanlineProvider { + + protected final int width; + + protected final int height; + + protected final int scanlineLength; + + protected final ScanlineCursor cursor; + + protected final IndexColorModel palette; + + protected final byte bitDepth; + + protected int currentRow = 0; + + public AbstractScanlineProvider(Raster raster, int bitDepth, int scanlineLength) { + this(raster, (byte) bitDepth, scanlineLength, null); + } + + public AbstractScanlineProvider(Raster raster, int bitDepth, int scanlineLength, IndexColorModel palette) { + this(raster, (byte) bitDepth, scanlineLength, palette); + } + + protected AbstractScanlineProvider(Raster raster, byte bitDepth, int scanlineLength, IndexColorModel palette) { + this.width = raster.getWidth(); + this.height = raster.getHeight(); + this.bitDepth = bitDepth; + this.palette = palette; + this.cursor = new ScanlineCursor(raster); + this.scanlineLength = scanlineLength; + } + + @Override + public final int getWidth() { + return width; + } + + @Override + public final int getHeight() { + return height; + } + + @Override + public final byte getBitDepth() { + return bitDepth; + } + + @Override + public final IndexColorModel getPalette() { + return palette; + } + + @Override + public final int getScanlineLength() { + return scanlineLength; + } + + @Override + public void readFromPngRaw(byte[] raw, int len, int offset, int step) { + throw new UnsupportedOperationException("This bridge works write only"); + } + + @Override + public void endReadFromPngRaw() { + throw new UnsupportedOperationException("This bridge works write only"); + } + + public void writeToPngRaw(byte[] raw) { + this.next(raw, 1, raw.length - 1); + } + + /** + * Compute the pixelStride for the provided raster. + * The actual raster pixelStride will be returned in case the raster number of bands + * is not equal to the pixelStride. + * Otherwise the expected pixelStride will be returned. + * The expectedPixelStrides array can optionally have size = 2 (instead of 1). + * The second value will be returned in case the raster has alpha. + **/ + public static int computePixelStride(Raster raster, int[] expectedPixelStrides, boolean hasAlpha) { + int pixelStride = ((ComponentSampleModel) raster.getSampleModel()).getPixelStride(); + if (raster.getNumBands() != pixelStride) { + return pixelStride; + } + return (expectedPixelStrides.length == 2 && hasAlpha) ? expectedPixelStrides[1] : expectedPixelStrides[0]; + } + + /** + * Compute the pixelStride for the provided raster. + * The actual raster pixelStride will be returned in case the raster number of bands + * is not equal to the pixelStride. + * Otherwise the expected PixelStride will be returned assuming the raster has + * no alpha. + **/ + public static int computePixelStride(Raster raster, int[] expectedPixelStrides) { + return computePixelStride(raster, expectedPixelStrides, false); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/PNGWriter.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/PNGWriter.java new file mode 100644 index 0000000..7785cdf --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/PNGWriter.java @@ -0,0 +1,104 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.io.OutputStream; +import java.util.Map; +import java.util.Map.Entry; + +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngWriter; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksListForWrite; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkPLTE; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkTRNS; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngMetadata; + +/** + * Encodes a rednered image to PNG. + */ +public class PNGWriter { + + public void writePNG(RenderedImage image, + OutputStream outStream, + float quality, + FilterType filterType) { + writePNG(image, outStream, quality, filterType, null); + } + + public void writePNG(RenderedImage image, + OutputStream outStream, + float quality, + FilterType filterType, + Map text) { + // compute the compression level similarly to what the Clib code does + int level = Math.round(9 * (1f - quality)); + // get the optimal scanline provider for this image + ScanlineProvider scanlines = ScanlineProviderFactory.getProvider(image); + if (scanlines == null) { + throw new IllegalArgumentException("Could not find a scanline extractor for " + image); + } + ColorModel colorModel = image.getColorModel(); + boolean indexed = colorModel instanceof IndexColorModel; + ImageInfo ii = getImageInfo(image, scanlines, colorModel, indexed); + try (PngWriter pw = new PngWriter(outStream, ii)) { + pw.setShouldCloseStream(false); + pw.setCompLevel(level); + pw.setFilterType(filterType); + ChunksListForWrite chunkList = pw.getChunksList(); + PngMetadata metadata = pw.getMetadata(); + if (indexed) { + IndexColorModel icm = (IndexColorModel) colorModel; + PngChunkPLTE palette = metadata.createPLTEChunk(); + int ncolors = icm.getMapSize(); + palette.setNentries(ncolors); + for (int i = 0; i < ncolors; i++) { + final int red = icm.getRed(i); + final int green = icm.getGreen(i); + final int blue = icm.getBlue(i); + palette.setEntry(i, red, green, blue); + } + if (icm.hasAlpha()) { + PngChunkTRNS transparent = new PngChunkTRNS(ii); + int[] alpha = new int[ncolors]; + for (int i = 0; i < ncolors; i++) { + final int a = icm.getAlpha(i); + alpha[i] = a; + } + transparent.setPalAlpha(alpha); + chunkList.queue(transparent); + } + } + if (text != null && !text.isEmpty()) { + for (Entry entrySet : text.entrySet()) { + metadata.setText(entrySet.getKey(), entrySet.getValue(), true, false); + } + } + // write out the actual image lines + for (int row = 0; row < image.getHeight(); row++) { + pw.writeRow(scanlines); + } + pw.end(); + } + } + + /** + * Quick method used for checking if the image can be optimized with the + * selected scanline extractors or if the image must be rescaled to byte + * before writing the image. + */ + public boolean isScanlineSupported(RenderedImage image) { + ScanlineProvider scanlines = ScanlineProviderFactory.getProvider(image); + return scanlines != null; + } + + private ImageInfo getImageInfo(RenderedImage image, ScanlineProvider scanlines, + ColorModel colorModel, boolean indexed) { + int numColorComponents = colorModel.getNumColorComponents(); + boolean grayscale = !indexed && numColorComponents < 3; + byte bitDepth = scanlines.getBitDepth(); + boolean hasAlpha = !indexed && colorModel.hasAlpha(); + return new ImageInfo(image.getWidth(), image.getHeight(), bitDepth, hasAlpha, grayscale, indexed); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteABGRProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteABGRProvider.java new file mode 100644 index 0000000..b29a597 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteABGRProvider.java @@ -0,0 +1,56 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; + +/** + * A scanline provider optimized for Raster objects containing + * a 8bit BGR or ABGR image. + */ +public final class RasterByteABGRProvider extends AbstractScanlineProvider { + + final static int[] PIXEL_STRIDES = new int[]{3,4}; + final byte[] bytes; + final boolean bgrOrder; + final boolean hasAlpha; + final int pixelStride; + final int[] bandOffsets; + final int numBands; + + public RasterByteABGRProvider(Raster raster, boolean hasAlpha) { + super(raster, 8, raster.getWidth() * (computePixelStride(raster, PIXEL_STRIDES, hasAlpha))); + this.hasAlpha = hasAlpha; + this.bytes = ((DataBufferByte) raster.getDataBuffer()).getData(); + ComponentSampleModel sm = (ComponentSampleModel) raster.getSampleModel(); + this.bgrOrder = sm.getBandOffsets()[0] != 0; + this.pixelStride = sm.getPixelStride(); + this.bandOffsets = sm.getBandOffsets(); + this.numBands = sm.getNumBands(); + } + + @Override + public void next(final byte[] row, final int offset, final int length) { + int bytesIdx = cursor.next(); + int i = offset; + final int max = offset + length; + if (!bgrOrder && (numBands == pixelStride)) { + System.arraycopy(bytes, bytesIdx, row, offset, length); + } else { + while (i < max) { + for (int j = 0; j < numBands; j++) { + // We assign data pixels on the expected order + // So if bgrOrder (bandOffset is 2,1,0): + // row[i+2] = B + // row[i+1] = G + // row[i+0] = R + row[i + j] = bytes[bytesIdx + bandOffsets[j]]; + } + // Pixel stride may be longer than numBands due to bandSelect + // sharing same dataBuffer of the original image + bytesIdx += pixelStride; + i += numBands; + } + } + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteGrayAlphaProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteGrayAlphaProvider.java new file mode 100644 index 0000000..5e88b28 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteGrayAlphaProvider.java @@ -0,0 +1,51 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; + +/** + * A scanline provider optimized for Raster objects containing a 8bit gray and alpha bands + */ +public final class RasterByteGrayAlphaProvider extends AbstractScanlineProvider { + + final static int[] PIXEL_STRIDES = new int[]{2}; + + final byte[] bytes; + + boolean alphaFirst; + + int[] bandOffsets; + + int pixelStride; + + int numBands; + + public RasterByteGrayAlphaProvider(Raster raster) { + super(raster, 8, raster.getWidth() * computePixelStride(raster, PIXEL_STRIDES)); + this.bytes = ((DataBufferByte) raster.getDataBuffer()).getData(); + ComponentSampleModel sm = (ComponentSampleModel) raster.getSampleModel(); + this.bandOffsets = sm.getBandOffsets(); + this.numBands = sm.getNumBands(); + this.pixelStride = sm.getPixelStride(); + this.alphaFirst = bandOffsets[0] != 0; + } + + @Override + public void next(final byte[] row, final int offset, final int length) { + int bytesIdx = cursor.next(); + if (!alphaFirst && (numBands == pixelStride)) { + System.arraycopy(bytes, bytesIdx, row, offset, length); + } else { + int i = offset; + final int max = offset + length; + while (i < max) { + for (int j = 0; j < numBands; j++) { + row[i + j] = bytes[bytesIdx + bandOffsets[j]]; + } + bytesIdx += pixelStride; + i += numBands; + } + } + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteRepackSingleBandProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteRepackSingleBandProvider.java new file mode 100644 index 0000000..8b6a8fa --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteRepackSingleBandProvider.java @@ -0,0 +1,65 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; + +/** + * A scanline provider that packs more than one pixel per output byte. + */ +public final class RasterByteRepackSingleBandProvider extends AbstractScanlineProvider { + + final byte[] bytes; + + public RasterByteRepackSingleBandProvider(Raster raster, int bitDepth, int scanlineLength) { + super(raster, bitDepth, scanlineLength); + this.bytes = ((DataBufferByte) raster.getDataBuffer()).getData(); + } + + public RasterByteRepackSingleBandProvider(Raster raster, int bitDepth, int scanlineLength, + IndexColorModel palette) { + super(raster, bitDepth, scanlineLength, palette); + this.bytes = ((DataBufferByte) raster.getDataBuffer()).getData(); + } + + @Override + public void next(final byte[] row, final int offset, final int length) { + if (this.currentRow == height) { + throw new IllegalStateException("All scanlines have been read already"); + } + + int pxIdx = cursor.next(); + final int pxLimit = pxIdx + width; + int i = offset; + final int max = offset + length; + if (bitDepth == 4) { + while (i < max) { + final int low = bytes[pxIdx++]; + final int high = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + row[i++] = (byte) ((low << 4) | high); + } + } else if (bitDepth == 2) { + while (i < max) { + final int b1 = bytes[pxIdx++]; + final int b2 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b3 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b4 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + row[i++] = (byte) (b4 | (b3 << 2) | (b2 << 4) | (b1 << 6)); + } + } else { + while (i < max) { + final int b1 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b2 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b3 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b4 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b5 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b6 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b7 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + final int b8 = pxIdx < pxLimit ? bytes[pxIdx++] : 0; + row[i++] = (byte) (b8 | (b7 << 1) | (b6 << 2) | (b5 << 3) | (b4 << 4) | (b3 << 5) + | (b2 << 6) | (b1 << 7)); + } + } + currentRow++; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandProvider.java new file mode 100644 index 0000000..52dc79f --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandProvider.java @@ -0,0 +1,35 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; + +/** + * A scanline provider that can copy 1-1 data from the buffered image into the scanline without + * performing any kind of transformation. + */ +public final class RasterByteSingleBandProvider extends AbstractScanlineProvider { + + final byte[] bytes; + + public RasterByteSingleBandProvider(Raster raster, int bitDepth, int scanlineLength) { + super(raster, bitDepth, scanlineLength); + this.bytes = ((DataBufferByte) raster.getDataBuffer()).getData(); + } + + public RasterByteSingleBandProvider(Raster raster, int bitDepth, int scanlineLength, + IndexColorModel palette) { + super(raster, bitDepth, scanlineLength, palette); + this.bytes = ((DataBufferByte) raster.getDataBuffer()).getData(); + } + + @Override + public void next(final byte[] scanline, final int offset, final int length) { + if (this.currentRow == height) { + throw new IllegalStateException("All scanlines have been read already"); + } + final int next = cursor.next(); + System.arraycopy(bytes, next, scanline, offset, length); + currentRow++; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandSkippingBytesProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandSkippingBytesProvider.java new file mode 100644 index 0000000..d86afe0 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterByteSingleBandSkippingBytesProvider.java @@ -0,0 +1,54 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.DataBufferByte; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; + +/** + * A scanline provider that copy data from the buffered image into the scanline + * by skipping some bytes due to pixelStride not equal to number of bands + * (There might be some bandSelect happening). + */ +public final class RasterByteSingleBandSkippingBytesProvider extends AbstractScanlineProvider { + + final static int[] PIXEL_STRIDES = new int[]{1}; + + int pixelStride; + + final byte[] bytes; + + int[] bandOffsets; + + int numBands; + + public RasterByteSingleBandSkippingBytesProvider(Raster raster) { + super(raster, 8, raster.getWidth() * computePixelStride(raster, PIXEL_STRIDES)); + PixelInterleavedSampleModel sm = (PixelInterleavedSampleModel) raster.getSampleModel(); + this.bytes = ((DataBufferByte) raster.getDataBuffer()).getData(); + this.pixelStride = sm.getPixelStride(); + this.numBands = sm.getNumBands(); + this.bandOffsets = sm.getBandOffsets(); + } + + @Override + public void next(final byte[] scanline, final int offset, final int length) { + if (this.currentRow == height) { + throw new IllegalStateException("All scanlines have been read already"); + } + int bytesIdx = cursor.next(); + if (numBands == pixelStride) { + System.arraycopy(bytes, bytesIdx, scanline, offset, length); + } else { + int i = offset; + final int max = offset + length; + while (i < max) { + for (int j = 0; j < numBands; j++) { + scanline[i + j] = bytes[bytesIdx + bandOffsets[j]]; + } + bytesIdx += pixelStride; + i += numBands; + } + } + currentRow++; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterIntABGRProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterIntABGRProvider.java new file mode 100644 index 0000000..648802a --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterIntABGRProvider.java @@ -0,0 +1,60 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.DataBufferInt; +import java.awt.image.Raster; +import java.awt.image.SinglePixelPackedSampleModel; + +/** + * A scanline provider optimized for rasters with int packed RGB or RGBA pixels. + */ +public final class RasterIntABGRProvider extends AbstractScanlineProvider { + + final int[] pixels; + + final boolean bgrOrder; + + final boolean hasAlpha; + + public RasterIntABGRProvider(Raster raster, boolean hasAlpha) { + super(raster, 8, raster.getWidth() * (hasAlpha ? 4 : 3)); + this.pixels = ((DataBufferInt) raster.getDataBuffer()).getData(); + this.hasAlpha = hasAlpha; + if (hasAlpha) { + bgrOrder = false; + } else { + int[] offsets = ((SinglePixelPackedSampleModel) raster.getSampleModel()).getBitOffsets(); + bgrOrder = offsets[0] != 0; + } + } + + @Override + public void next(final byte[] row, final int offset, final int length) { + int pxIdx = cursor.next(); + int i = offset; + final int max = offset + length; + if (hasAlpha) { + while (i < max) { + final int color = pixels[pxIdx++]; + row[i++] = (byte) ((color >> 16) & 0xff); + row[i++] = (byte) ((color >> 8) & 0xff); + row[i++] = (byte) ((color) & 0xff); + row[i++] = (byte) ((color >> 24) & 0xff); + } + } else if (bgrOrder) { + while (i < max) { + final int color = pixels[pxIdx++]; + row[i++] = (byte) ((color >> 16) & 0xff); + row[i++] = (byte) ((color >> 8) & 0xff); + row[i++] = (byte) ((color) & 0xff); + } + } else { + while (i < max) { + final int color = pixels[pxIdx++]; + row[i++] = (byte) ((color) & 0xff); + row[i++] = (byte) ((color >> 8) & 0xff); + row[i++] = (byte) ((color >> 16) & 0xff); + } + } + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortABGRProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortABGRProvider.java new file mode 100644 index 0000000..91b094c --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortABGRProvider.java @@ -0,0 +1,90 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBufferUShort; +import java.awt.image.Raster; + +/** + * A scanline provider optimized for Raster objects containing a 16bit BGR or ABGR image. + */ +public final class RasterShortABGRProvider extends AbstractScanlineProvider { + + final short[] shorts; + + final boolean bgrOrder; + + final boolean hasAlpha; + + public RasterShortABGRProvider(Raster raster, boolean hasAlpha) { + super(raster, 16, (hasAlpha ? 8 : 6) * raster.getWidth()); + this.hasAlpha = hasAlpha; + shorts = ((DataBufferUShort) raster.getDataBuffer()).getData(); + bgrOrder = ((ComponentSampleModel) raster.getSampleModel()).getBandOffsets()[0] != 0; + } + + @Override + public void next(final byte[] scanline, final int offset, final int length) { + int shortsIdx = cursor.next(); + int i = offset; + final int max = offset + length; + if (hasAlpha) { + if (bgrOrder) { + while (i < max) { + final short a = shorts[shortsIdx++]; + final short b = shorts[shortsIdx++]; + final short g = shorts[shortsIdx++]; + final short r = shorts[shortsIdx++]; + scanline[i++] = (byte) ((r >> 8) & 0xFF); + scanline[i++] = (byte) (r & 0xFF); + scanline[i++] = (byte) ((g >> 8) & 0xFF); + scanline[i++] = (byte) (g & 0xFF); + scanline[i++] = (byte) ((b >> 8) & 0xFF); + scanline[i++] = (byte) (b & 0xFF); + scanline[i++] = (byte) ((a >> 8) & 0xFF); + scanline[i++] = (byte) (a & 0xFF); + } + } else { + while (i < max) { + final short r = shorts[shortsIdx++]; + final short g = shorts[shortsIdx++]; + final short b = shorts[shortsIdx++]; + final short a = shorts[shortsIdx++]; + scanline[i++] = (byte) ((r >> 8) & 0xFF); + scanline[i++] = (byte) (r & 0xFF); + scanline[i++] = (byte) ((g >> 8) & 0xFF); + scanline[i++] = (byte) (g & 0xFF); + scanline[i++] = (byte) ((b >> 8) & 0xFF); + scanline[i++] = (byte) (b & 0xFF); + scanline[i++] = (byte) ((a >> 8) & 0xFF); + scanline[i++] = (byte) (a & 0xFF); + } + } + } else { + if(bgrOrder) { + while (i < max) { + final short b = shorts[shortsIdx++]; + final short g = shorts[shortsIdx++]; + final short r = shorts[shortsIdx++]; + scanline[i++] = (byte) ((r >> 8) & 0xFF); + scanline[i++] = (byte) (r & 0xFF); + scanline[i++] = (byte) ((g >> 8) & 0xFF); + scanline[i++] = (byte) (g & 0xFF); + scanline[i++] = (byte) ((b >> 8) & 0xFF); + scanline[i++] = (byte) (b & 0xFF); + } + } else { + while (i < max) { + final short r = shorts[shortsIdx++]; + final short g = shorts[shortsIdx++]; + final short b = shorts[shortsIdx++]; + scanline[i++] = (byte) ((r >> 8) & 0xFF); + scanline[i++] = (byte) (r & 0xFF); + scanline[i++] = (byte) ((g >> 8) & 0xFF); + scanline[i++] = (byte) (g & 0xFF); + scanline[i++] = (byte) ((b >> 8) & 0xFF); + scanline[i++] = (byte) (b & 0xFF); + } + } + } + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortGrayAlphaProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortGrayAlphaProvider.java new file mode 100644 index 0000000..bdce95c --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortGrayAlphaProvider.java @@ -0,0 +1,49 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.DataBufferUShort; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; + +/** + * A scanline provider optimized for a Raster with 16 bit gray + 16 bits alpha. + */ +public final class RasterShortGrayAlphaProvider extends AbstractScanlineProvider { + + final short[] shorts; + + final boolean alphaFirst; + + public RasterShortGrayAlphaProvider(Raster raster) { + super(raster, 16, raster.getWidth() * 4); + this.shorts = ((DataBufferUShort) raster.getDataBuffer()).getData(); + int[] bandOffsets = ((PixelInterleavedSampleModel) raster.getSampleModel()).getBandOffsets(); + this.alphaFirst = bandOffsets[0] != 0; + } + + + public void next(final byte[] scanline, final int offset, final int length) { + int shortsIdx = cursor.next(); + int i = offset; + final int max = offset + length; + if(alphaFirst) { + while (i < max) { + final short alpha = shorts[shortsIdx++]; + final short gray = shorts[shortsIdx++]; + scanline[i++] = (byte) ((gray >> 8) & 0xFF); + scanline[i++] = (byte) (gray & 0xFF); + scanline[i++] = (byte) ((alpha >> 8) & 0xFF); + scanline[i++] = (byte) (alpha & 0xFF); + } + } else { + while (i < max) { + final short gray = shorts[shortsIdx++]; + final short alpha = shorts[shortsIdx++]; + scanline[i++] = (byte) ((gray >> 8) & 0xFF); + scanline[i++] = (byte) (gray & 0xFF); + scanline[i++] = (byte) ((alpha >> 8) & 0xFF); + scanline[i++] = (byte) (alpha & 0xFF); + } + } + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortSingleBandProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortSingleBandProvider.java new file mode 100644 index 0000000..6516f0d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/RasterShortSingleBandProvider.java @@ -0,0 +1,40 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.DataBufferUShort; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; + +/** + * A scanline provider optimized for a Raster with 16 bit gray pixels + * + * @author Andrea Aime - GeoSolutions + */ +public final class RasterShortSingleBandProvider extends AbstractScanlineProvider { + + final short[] shorts; + + public RasterShortSingleBandProvider(Raster raster) { + super(raster, 16, raster.getWidth() * 2); + this.shorts = ((DataBufferUShort) raster.getDataBuffer()).getData(); + } + + public RasterShortSingleBandProvider(Raster raster, int bidDepth, int scanlineLength, IndexColorModel palette) { + super(raster, bidDepth, scanlineLength, palette); + this.shorts = ((DataBufferUShort) raster.getDataBuffer()).getData(); + } + + + public void next(final byte[] scanline, final int offset, final int length) { + int shortsIdx = cursor.next(); + int i = offset; + int max = offset + length; + while (i < max) { + short gray = shorts[shortsIdx++]; + scanline[i++] = (byte) ((gray >> 8) & 0xFF); + if(i < max) { + scanline[i++] = (byte) (gray & 0xFF); + } + } + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineCursor.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineCursor.java new file mode 100644 index 0000000..c138bb6 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineCursor.java @@ -0,0 +1,52 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.ComponentSampleModel; +import java.awt.image.Raster; + +/** + * A helper class that supports the scanline provider in navigating the structure of a Java image. + */ +final class ScanlineCursor { + + final int scanlineStride; + + final int maxPosition; + + int position; + + public ScanlineCursor(Raster raster) { + // the data buffer can have lines that are longer than width * bytes per pixel, can have + // extra at the end + this.scanlineStride = getScanlineStride(raster); + // the data buffer itself could be longer + this.position = raster.getDataBuffer().getOffset(); + this.maxPosition = raster.getDataBuffer().getSize(); + } + + /** + * Returns the initial position of the current line, and moves to the next. + */ + public int next() { + final int result = position; + if (result >= maxPosition) { + throw new IllegalStateException( + "We got past the end of the buffer, current position is " + position + + " and max position value is " + maxPosition); + } + position += scanlineStride; + return result; + } + + /** + * Gets the scanline stride for the given raster. + */ + int getScanlineStride(Raster raster) { + if (raster.getSampleModel() instanceof ComponentSampleModel) { + ComponentSampleModel csm = ((ComponentSampleModel) raster.getSampleModel()); + return csm.getScanlineStride(); + } else { + return raster.getDataBuffer().getSize() / raster.getHeight(); + } + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineProvider.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineProvider.java new file mode 100644 index 0000000..c5408f0 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineProvider.java @@ -0,0 +1,41 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.image.IndexColorModel; + +import org.xbib.graphics.imageio.plugins.png.pngj.IImageLine; + +/** + * The bridge between images and PNG scanlines + */ +public interface ScanlineProvider extends IImageLine { + + /** + * Image width + */ + int getWidth(); + + /** + * Image height + */ + int getHeight(); + + /** + * The bit depth of this image, 1, 2, 4, 8 or 16 + */ + byte getBitDepth(); + + /** + * The number of byte[] elements in the scaline + */ + int getScanlineLength(); + + /** + * The next scanline, or throws an exception if we got past the end of the image + */ + void next(byte[] scaline, int offset, int length); + + /** + * Returns the palette for this image, or null if the image does not have one + */ + IndexColorModel getPalette(); +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineProviderFactory.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineProviderFactory.java new file mode 100644 index 0000000..feee785 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/ScanlineProviderFactory.java @@ -0,0 +1,151 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; + +/** + * Wraps a {@link RenderedImage} into a scanline provider + * optimized to turn its pixels into PNG scanlines at the best performance. + */ +public class ScanlineProviderFactory { + + public static ScanlineProvider getProvider(RenderedImage image) { + ColorModel cm = image.getColorModel(); + SampleModel sm = image.getSampleModel(); + Raster raster; + if (image instanceof BufferedImage) { + raster = ((BufferedImage) image).getRaster(); + // in case the raster has a parent, this is likely a subimage, we have to force + // a copy of the raster to get a data buffer we can scroll over without issues + if (raster.getParent() != null) { + raster = image.getData(new Rectangle(0, 0, raster.getWidth(), raster.getHeight())); + } + } else { + // TODO: we could build a tile oriented reader that fetches tiles in parallel here + raster = image.getData(); + } + // grab the right scanline extractor based on image features + if (cm instanceof ComponentColorModel && sm.getDataType() == DataBuffer.TYPE_BYTE) { + if (sm.getNumBands() == 3 || sm.getNumBands() == 4) { + return new RasterByteABGRProvider(raster, cm.hasAlpha()); + } else if (sm.getNumBands() == 2 && cm.hasAlpha()) { + return new RasterByteGrayAlphaProvider(raster); + } else if (sm.getNumBands() == 1) { + if (sm instanceof MultiPixelPackedSampleModel) { + if (cm.getPixelSize() == 8) { + return new RasterByteSingleBandProvider(raster, 8, raster.getWidth()); + } else if (cm.getPixelSize() == 4) { + int scanlineLength = (raster.getWidth() + 1) / 2; + return new RasterByteSingleBandProvider(raster, 4, scanlineLength); + } else if (cm.getPixelSize() == 2) { + int scanlineLength = (raster.getWidth() + 2) / 4; + return new RasterByteSingleBandProvider(raster, 2, scanlineLength); + } else if (cm.getPixelSize() == 1) { + int scanlineLength = (raster.getWidth() + 4) / 8; + return new RasterByteSingleBandProvider(raster, 1, scanlineLength); + } + } else { + if (cm.getPixelSize() == 8) { + if (sm instanceof PixelInterleavedSampleModel && + (((PixelInterleavedSampleModel)sm).getPixelStride() != 1)) { + return new RasterByteSingleBandSkippingBytesProvider(raster); + } + return new RasterByteSingleBandProvider(raster, 8, raster.getWidth()); + } else if (cm.getPixelSize() == 4) { + int scanlineLength = (raster.getWidth() + 1) / 2; + return new RasterByteRepackSingleBandProvider(raster, 4, scanlineLength); + } else if (cm.getPixelSize() == 2) { + int scanlineLength = (raster.getWidth() + 2) / 4; + return new RasterByteRepackSingleBandProvider(raster, 2, scanlineLength); + } else if (cm.getPixelSize() == 1) { + int scanlineLength = (raster.getWidth() + 4) / 8; + return new RasterByteRepackSingleBandProvider(raster, 1, scanlineLength); + } + } + } + } else if (cm instanceof ComponentColorModel && sm.getDataType() == DataBuffer.TYPE_USHORT) { + if (sm.getNumBands() == 3 || sm.getNumBands() == 4) { + return new RasterShortABGRProvider(raster, cm.hasAlpha()); + } else if (sm.getNumBands() == 2 && cm.hasAlpha()) { + return new RasterShortGrayAlphaProvider(raster); + } else if (sm.getNumBands() == 1) { + return new RasterShortSingleBandProvider(raster); + } + } else if (cm instanceof DirectColorModel && sm.getDataType() == DataBuffer.TYPE_INT) { + if (sm.getNumBands() == 3 || sm.getNumBands() == 4) { + return new RasterIntABGRProvider(raster, cm.hasAlpha()); + } + } else if (cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel) cm; + int pixelSize = icm.getPixelSize(); + // the RGBA quantizer can generate pixel sizes which are not powers of two, + // re-align to powers of two + if((pixelSize & (pixelSize - 1)) != 0) { + int nextPower = (int) (Math.floor(Math.log(pixelSize) / Math.log(2)) + 1); + pixelSize = (int) Math.pow(2, nextPower); + } + if (sm.getDataType() == DataBuffer.TYPE_BYTE) { + if (sm instanceof MultiPixelPackedSampleModel) { + if (pixelSize == 8) { + return new RasterByteSingleBandProvider(raster, 8, raster.getWidth(), icm); + } else if (pixelSize == 4) { + int scanlineLength = (raster.getWidth() + 1) / 2; + return new RasterByteSingleBandProvider(raster, 4, scanlineLength, icm); + } else if (pixelSize == 2) { + int scanlineLength = (raster.getWidth() + 2) / 4; + return new RasterByteSingleBandProvider(raster, 2, scanlineLength, icm); + } else if (pixelSize == 1) { + int scanlineLength = (raster.getWidth() + 4) / 8; + return new RasterByteSingleBandProvider(raster, 1, scanlineLength, icm); + } + } else { + if (pixelSize == 8) { + return new RasterByteSingleBandProvider(raster, 8, raster.getWidth(), icm); + } else if (pixelSize == 4) { + int scanlineLength = (raster.getWidth() + 1) / 2; + return new RasterByteRepackSingleBandProvider(raster, 4, scanlineLength, + icm); + } else if (pixelSize == 2) { + int scanlineLength = (raster.getWidth() + 2) / 4; + return new RasterByteRepackSingleBandProvider(raster, 2, scanlineLength, + icm); + } else if (pixelSize == 1) { + int scanlineLength = (raster.getWidth() + 4) / 8; + return new RasterByteRepackSingleBandProvider(raster, 1, scanlineLength, + icm); + } + } + } else if (sm.getDataType() == DataBuffer.TYPE_USHORT) { + if (sm instanceof MultiPixelPackedSampleModel) { + if (pixelSize == 16) { + int scanlineLength = raster.getWidth() * 2; + return new RasterShortSingleBandProvider(raster, 16, scanlineLength, icm); + } else if (pixelSize == 8) { + int scanlineLength = raster.getWidth() + ((raster.getWidth() % 2 == 0) ? 0 : 1); + return new RasterShortSingleBandProvider(raster, 8, scanlineLength, icm); + } else if (pixelSize == 4) { + int scanlineLength = (raster.getWidth() + 1) / 2; + return new RasterShortSingleBandProvider(raster, 4, scanlineLength, icm); + } else if (pixelSize == 2) { + int scanlineLength = (raster.getWidth() + 2) / 4; + return new RasterShortSingleBandProvider(raster, 2, scanlineLength, icm); + } else if (pixelSize == 1) { + int scanlineLength = (raster.getWidth() + 4) / 8; + return new RasterShortSingleBandProvider(raster, 1, scanlineLength, icm); + } + } + } + } + return null; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/BufferedStreamFeeder.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/BufferedStreamFeeder.java new file mode 100644 index 0000000..8e3313f --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/BufferedStreamFeeder.java @@ -0,0 +1,221 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +public class BufferedStreamFeeder implements Closeable { + + private InputStream stream; + private byte[] buf; + private int pendinglen; // bytes read+stored in buf, not yet still sent to IBytesConsumer + private int offset; + private boolean eof = false; // EOF on inputStream + private boolean closeStream = true; + private long bytesRead = 0; + + private static final int DEFAULTSIZE = 16384; + + /** + * By default, the stream will be closed on close() + */ + public BufferedStreamFeeder(InputStream is) { + this(is, DEFAULTSIZE); + } + + public BufferedStreamFeeder(InputStream is, int bufsize) { + this.stream = is; + buf = new byte[bufsize < 1 ? DEFAULTSIZE : bufsize]; + } + + /** + * Returns inputstream + * + * @return Input Stream from which bytes are read + */ + public InputStream getStream() { + return stream; + } + + /** + * @see BufferedStreamFeeder#feed(IBytesConsumer, int) + */ + public int feed(IBytesConsumer consumer) { + return feed(consumer, Integer.MAX_VALUE); + } + + /** + * Tries to feed the consumer with bytes read from the stream, at most + * maxbytes + *

      + * It can return less than maxbytes (that doesn't mean that the consumer or + * the input stream is done) + *

      + * Returns 0 if premature ending (no more to read, consumer not done)
      + * Returns -1 if nothing fed, but consumer is done + */ + public int feed(IBytesConsumer consumer, int maxbytes) { + refillBufferIfAppropiate(); + int consumed = 0; + final int tofeed = maxbytes > 0 && maxbytes < pendinglen ? maxbytes : pendinglen; + if (tofeed > 0) { + consumed = consumer.consume(buf, offset, tofeed); // never returns 0 + if (consumed > 0) { + offset += consumed; + pendinglen -= consumed; + assert pendinglen >= 0; + } + } else { + // nothing to fed ? premature ending ? + if (!eof) { + throw new PngjInputException("This should not happen"); + } + return consumer.isDone() ? -1 : 0 /* premature ending */; + } + if (consumed > 0) { + return consumed; + } else { // read bytes, but consumer refused to eat them ? (rare) + if (!consumer.isDone()) { + throw new PngjInputException("This should not happen!"); + } + return -1; + } + } + + /** + * Feeds as much bytes as it can to the consumer, in a loop.
      + * Returns bytes actually consumed
      + * This will stop when either the input stream is eof, or when the consumer + * refuses to eat more bytes. The caller can distinguish both cases by + * calling {@link #hasPendingBytes()} + */ + public long feedAll(IBytesConsumer consumer) { + long n = 0; + while (hasPendingBytes()) { + int n1 = feed(consumer); + if (n1 <= 0) { + break; + } + n += n1; + } + return n; + } + + /** + * Feeds exactly nbytes, retrying if necessary + * + * @param consumer Consumer + * @param nbytes Number of bytes + * @return nbytes if success, 0 if premature input ending, -1 if consumer + * done + */ + public int feedFixed(IBytesConsumer consumer, final int nbytes) { + int remain = nbytes; + while (remain > 0) { + int n = feed(consumer, remain); + if (n <= 0) { + return n; + } + remain -= n; + } + assert remain == 0; + return nbytes; + } + + /** + * If there are not pending bytes to be consumed, tries to fill the buffer + * reading bytes from the stream. + *

      + * If EOF is reached, sets eof=TRUE and calls close() + *

      + * Find in pendinglen the amounts of bytes read. + *

      + * If IOException, throws a PngjInputException + */ + protected void refillBufferIfAppropiate() { + if (pendinglen > 0 || eof) { + return; // only if not pending data + } + try { + // try to read + offset = 0; + pendinglen = stream.read(buf); + if (pendinglen == 0) // should never happen + { + throw new PngjInputException("This should not happen: stream.read(buf) returned 0"); + } else if (pendinglen < 0) { + close(); // this sets EOF and pendinglen=0 + } else { + bytesRead += pendinglen; + } + } catch (IOException e) { + throw new PngjInputException(e); + } + // on return, either pendinglen > 0 or eof == true + } + + /** + * Returuns true if we have more data to fed the consumer. This might try to + * grab more bytes from the stream if necessary + */ + public boolean hasPendingBytes() { + refillBufferIfAppropiate(); + return pendinglen > 0; + } + + /** + * @param closeStream If true, the underlying stream will be closed on when close() + * is called + */ + public void setCloseStream(boolean closeStream) { + this.closeStream = closeStream; + } + + /** + * Closes this object. + *

      + * Sets EOF=true, and closes the stream if closeStream is true + *

      + * This can be called internally, or from outside. + *

      + * Idempotent, secure, never throws exception. + **/ + public void close() { + eof = true; + buf = null; + pendinglen = 0; + offset = 0; + if (stream != null && closeStream) { + try { + stream.close(); + } catch (Exception e) { + // PngHelperInternal.LOGGER.log(Level.WARNING, "Exception closing stream", e); + } + } + stream = null; + } + + /** + * Sets a new underlying inputstream. This allows to reuse this object. The + * old underlying is not closed and the state is not reset (you should call + * close() previously if you want that) + * + * @param is + */ + public void setInputStream(InputStream is) { // to reuse this object + this.stream = is; + eof = false; + } + + /** + * @return EOF on stream, or close() was called + */ + public boolean isEof() { + return eof; + } + + public long getBytesRead() { + return bytesRead; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkReader.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkReader.java new file mode 100644 index 0000000..29c790c --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkReader.java @@ -0,0 +1,258 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.logging.Logger; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkRaw; + +/** + * Parses a PNG chunk, consuming bytes in one of three modes: + * {@link ChunkReaderMode#BUFFER}, {@link ChunkReaderMode#PROCESS}, + * {@link ChunkReaderMode#SKIP}. + *

      + * It calls {@link #chunkDone()} when done. Also calls + * {@link #processData(byte[], int, int)} if PROCESS mode. Apart + * from thas, it's totally agnostic (it doesn't know about IDAT chunks, or PNG + * general structure) + *

      + * The object wraps a ChunkRaw instance (content allocated and filled + * only if BUFFER mode). It should be short lived (one instance created for each + * chunk, and discarded after reading), but the wrapped chunkRaw can be + * (usually is) long lived. + */ +public abstract class ChunkReader implements IBytesConsumer { + private static final Logger LOGGER = Logger.getLogger(ChunkReader.class.getName()); + + /** + * see {@link ChunkReaderMode} + */ + public final ChunkReaderMode mode; + private final ChunkRaw chunkRaw; + + /** + * How many bytes have been read for this chunk, data only + */ + protected int read = 0; + private int crcn = 0; // how many bytes have been read from crc + + private boolean crcCheck; // by default, this is false for SKIP, true elsewhere + protected ErrorBehaviour errorBehav = ErrorBehaviour.STRICT; + + /** + * Modes of ChunkReader chunk processing. + */ + public enum ChunkReaderMode { + /** + * Stores full chunk data in buffer + */ + BUFFER, + /** + * Does not store content, processes on the fly, calling processData() + * for each partial read + */ + PROCESS, + /** + * Does not store nor process - implies crcCheck=false (by default). + */ + SKIP + } + + /** + * The constructor creates also a chunkRaw, preallocated if mode = + * ChunkReaderMode.BUFFER + * + * @param clen + * @param id + * @param offsetInPng Informational, is stored in chunkRaw + * @param mode + */ + public ChunkReader(int clen, String id, long offsetInPng, ChunkReaderMode mode) { + if (mode == null || id.length() != 4 || clen < 0) { + throw new PngjInputException("Bad chunk paramenters: " + mode); + } + this.mode = mode; + chunkRaw = new ChunkRaw(clen, id, mode == ChunkReaderMode.BUFFER); + chunkRaw.setOffset(offsetInPng); + this.crcCheck = mode != ChunkReaderMode.SKIP; // can be changed with setter + // PngHelperInternal.debug("ChunkReader " + this.getClass() + " id="+id + " mode:"+mode); + } + + /** + * Returns raw chunk (data can be empty or not, depending on + * ChunkReaderMode) + * + * @return Raw chunk - never null + */ + public ChunkRaw getChunkRaw() { + return chunkRaw; + } + + /** + * Consumes data for the chunk (data and CRC). This only consumes bytes + * owned by this chunk (data + crc , not id+len prefix) + *

      + * In ChunkReaderMode.PROCESS can call processData() (not more than once) + *

      + * If this ends the chunk (included CRC) it checks CRC (if checking) and + * calls chunkDone() + * + * @param buf + * @param off + * @param len + * @return How many bytes have been consumed + */ + public final int consume(byte[] buf, int off, int len) { + if (len == 0) { + return 0; + } + if (len < 0) { + throw new PngjException("negative length??"); + } + if (read == 0 && crcn == 0 && crcCheck) { + chunkRaw.updateCrc(chunkRaw.idbytes, 0, 4); // initializes crc calculation with the Chunk ID + } + int bytesForData = chunkRaw.len - read; // bytesForData : bytes to be actually read from chunk data + if (bytesForData > len) { + bytesForData = len; + } + // we want to call processData even for empty chunks (IEND:bytesForData=0) at + // least once + if (bytesForData > 0 || crcn == 0) { + // in buffer mode we compute the CRC at the end + if (crcCheck && mode != ChunkReaderMode.BUFFER && bytesForData > 0) { + chunkRaw.updateCrc(buf, off, bytesForData); + } + + if (mode == ChunkReaderMode.BUFFER) { + // just copy the contents to the internal buffer + if (chunkRaw.data != buf && bytesForData > 0) { + // If the buffer passed if the same as this one, we don't copy. + // The caller should know what he's doing + System.arraycopy(buf, off, chunkRaw.data, read, bytesForData); + } + } else if (mode == ChunkReaderMode.PROCESS) { + processData(read, buf, off, bytesForData); + } else { + // mode == ChunkReaderMode.SKIP; nothing to do + } + read += bytesForData; + off += bytesForData; + len -= bytesForData; + } + int crcRead = 0; + if (read == chunkRaw.len) { // data done - read crc? + crcRead = 4 - crcn; + if (crcRead > len) { + crcRead = len; + } + if (crcRead > 0) { + if (buf != chunkRaw.crcval) { + System.arraycopy(buf, off, chunkRaw.crcval, crcn, crcRead); + } + crcn += crcRead; + if (crcn == 4) { + if (crcCheck) { + if (mode == ChunkReaderMode.BUFFER) { // in buffer mode we compute the CRC on one single call + chunkRaw.updateCrc(chunkRaw.data, 0, chunkRaw.len); + } + chunkRaw.checkCrc(errorBehav == ErrorBehaviour.STRICT); + } + LOGGER.fine("Chunk done"); + chunkDone(); + } + } + } + return bytesForData > 0 || crcRead > 0 ? bytesForData + crcRead : -1 /* should not happen */; + } + + /** + * Chunks has been read + * + * @return true if we have read all chunk, including trailing CRC + */ + public final boolean isDone() { + return crcn == 4; // has read all 4 bytes from the crc + } + + /** + * Determines if CRC should be checked. This should be called before + * starting reading. + * + * @param crcCheck + * @see also #setErrorBehav(ErrorBehaviour) + */ + public void setCrcCheck(boolean crcCheck) { + if (read != 0 && crcCheck && !this.crcCheck) { + throw new PngjException("too late!"); + } + this.crcCheck = crcCheck; + } + + /** + * This method will only be called in PROCESS mode, probably several times, + * each time with a new fragment of data read from inside the chunk. For + * chunks with zero-length data, this will still be called once. + *

      + * It's guaranteed that the data corresponds exclusively to this chunk data + * (no crc, no data from no other chunks, ) + * + * @param offsetInchunk data bytes that had already been read/processed for this chunk + * @param buf + * @param off + * @param len + */ + protected abstract void processData(int offsetInchunk, byte[] buf, int off, int len); + + /** + * This method will be called (in all modes) when the full chunk -including + * crc- has been read + */ + protected abstract void chunkDone(); + + public boolean isFromDeflatedSet() { + return false; + } + + public ErrorBehaviour getErrorBehav() { + return errorBehav; + } + + /** + * see also {@link #setCrcCheck(boolean)} + **/ + public void setErrorBehav(ErrorBehaviour errorBehav) { + this.errorBehav = errorBehav; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((chunkRaw == null) ? 0 : chunkRaw.hashCode()); + return result; + } + + /** + * Equality (and hash) is basically delegated to the ChunkRaw + */ + @Override + public boolean equals(Object obj) { // delegates to chunkraw + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ChunkReader other = (ChunkReader) obj; + if (chunkRaw == null) { + return other.chunkRaw == null; + } else return chunkRaw.equals(other.chunkRaw); + } + + @Override + public String toString() { + return chunkRaw.toString(); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqBuffering.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqBuffering.java new file mode 100644 index 0000000..3b6d35d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqBuffering.java @@ -0,0 +1,35 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * This loads the png as a plain sequence of chunks, buffering all + *

      + * Useful to do things like insert or delete a ancilllary chunk. This does not + * distinguish IDAT from others + **/ +public class ChunkSeqBuffering extends ChunkSeqReader { + protected boolean checkCrc = true; + + public ChunkSeqBuffering() { + super(); + } + + @Override + protected boolean isIdatKind(String id) { + return false; + } + + @Override + protected boolean shouldCheckCrc(int len, String id) { + return checkCrc; + } + + public void setCheckCrc(boolean checkCrc) { + this.checkCrc = checkCrc; + } + + @Override + protected DeflatedChunksSet createIdatSet(String id) { + throw new RuntimeException("Should not get here"); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReader.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReader.java new file mode 100644 index 0000000..939ca89 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReader.java @@ -0,0 +1,473 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.xbib.graphics.imageio.plugins.png.pngj.ChunkReader.ChunkReaderMode; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkHelper; + +/** + * Consumes a stream of bytes that consist of a series of PNG-like chunks. + *

      + * This has little intelligence, it's quite low-level and general (it could even + * be used for a MNG stream, for example). It supports signature recognition and + * idat deflate + */ +public abstract class ChunkSeqReader implements IBytesConsumer, Closeable { + private static final Logger LOGGER = Logger.getLogger(ChunkSeqReader.class.getName()); + + private final byte[] expectedSignature; + private final int signatureLength; + + private final byte[] buf0 = new byte[8]; // for signature or chunk starts + private int buf0len = 0; + + protected boolean signatureDone = false; + protected boolean endChunkDone = false; + protected boolean closed = false; // ended, normally or not + + private int chunkCount = 0; + + private long bytesCount = 0; + + private DeflatedChunksSet curDeflatedSet; // one instance for each "idat-like set". Normally one. + + private ChunkReader curChunkReader; + + private long idatBytes; // this is only for the IDAT (not mrerely "idat-like") + + private ErrorBehaviour errorBehaviour = ErrorBehaviour.STRICT; + + /** + * Creates a ChunkSeqReader (with signature) + */ + public ChunkSeqReader() { + this(PngHelperInternal.getPngIdSignature()); + } + + /** + * Pass _expectedSignature = null (or empty) for no signature + */ + public ChunkSeqReader(byte[] _expectedSignature) { + this.expectedSignature = _expectedSignature; + signatureLength = expectedSignature == null ? 0 : expectedSignature.length; + signatureDone = signatureLength <= 0; + } + + /** + * Consumes (in general, partially) a number of bytes. A single call never + * involves more than one chunk. + *

      + * When the signature is read, it calls checkSignature() + *

      + * When the start of a chunk is detected, it calls + * {@link #startNewChunk(int, String, long)} + *

      + * When data from a chunk is being read, it delegates to + * {@link ChunkReader#feedBytes(byte[], int, int)} + *

      + * The caller might want to call this method more than once in succesion + *

      + * This should rarely be overriden + * + * @param buffer + * @param offset Offset in buffer + * @param len Valid bytes that can be consumed + * @return processed bytes, in the 1-len range. -1 if done. Only returns 0 + * if len=0. + **/ + @Override + public int consume(byte[] buffer, int offset, int len) { + if (closed) { + return -1; + } + if (len == 0) { + return 0; // nothing to do (should not happen) + } + if (len < 0) { + throw new PngjInputException("This should not happen. Bad length: " + len); + } + int processed = 0; + if (signatureDone) { + if (curChunkReader == null || curChunkReader.isDone()) { // new chunk: read first 8 bytes + int read0 = 8 - buf0len; + if (read0 > len) { + read0 = len; + } + System.arraycopy(buffer, offset, buf0, buf0len, read0); + buf0len += read0; + processed += read0; + bytesCount += read0; + // len -= read0; + // offset += read0; + if (buf0len == 8) { // end reading chunk length and id + chunkCount++; + int clen = PngHelperInternal.readInt4fromBytes(buf0, 0); + String cid = ChunkHelper.idFromBytes(buf0, 4); + startNewChunk(clen, cid, bytesCount - 8); + buf0len = 0; + } + } else { // reading chunk, delegates to curChunkReader + int read1 = curChunkReader.consume(buffer, offset, len); + if (read1 < 0) { + return -1; // should not happen + } + processed += read1; + bytesCount += read1; + } + } else { // reading signature + int read = signatureLength - buf0len; + if (read > len) { + read = len; + } + System.arraycopy(buffer, offset, buf0, buf0len, read); + buf0len += read; + if (buf0len == signatureLength) { + checkSignature(buf0); + buf0len = 0; + signatureDone = true; + } + processed += read; + bytesCount += read; + } + return processed; + } + + /** + * Trys to feeds exactly len bytes, calling + * {@link #consume(byte[], int, int)} retrying if necessary. + *

      + * This should only be used in callback mode + * + * @return Excess bytes (not feed). Normally should return 0 + */ + public int feedAll(byte[] buf, int off, int len) { + while (len > 0) { + int n = consume(buf, off, len); + if (n < 1) { + return len; + } + len -= n; + off += n; + } + assert len == 0; + return 0; + } + + /** + * Called for all chunks when a chunk start has been read (id and length), + * before the chunk data itself is read. It creates a new ChunkReader (field + * accesible via {@link #getCurChunkReader()}) in the corresponding mode, + * and eventually a curReaderDeflatedSet.(field accesible via + * {@link #getCurDeflatedSet()}) + *

      + * To decide the mode and options, it calls + * {@link #shouldCheckCrc(int, String)}, + * {@link #shouldSkipContent(int, String)}, {@link #isIdatKind(String)}. + * Those methods should be overriden in preference to this; if overriden, + * this should be called first. + *

      + * The respective {@link ChunkReader#chunkDone()} method is directed to this + * {@link #postProcessChunk(ChunkReader)}. + *

      + * Instead of overriding this, see also + * {@link #createChunkReaderForNewChunk(String, int, long, boolean)} + */ + protected void startNewChunk(int len, String id, long offset) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.fine("New chunk: " + id + " " + len + " off:" + offset); + } + // check id an length + if (id.length() != 4 || !ChunkHelper.CHUNK_ID_PAT.matcher(id).matches()) { + throw new PngjInputException("Bad chunk id: " + id); + } + if (len < 0) { + throw new PngjInputException("Bad chunk len: " + len); + } + if (id.equals(ChunkHelper.IDAT)) { + idatBytes += len; + } + boolean checkCrc = shouldCheckCrc(len, id); + boolean skip = shouldSkipContent(len, id); + boolean isIdatType = isIdatKind(id); + // PngHelperInternal.debug("start new chunk id=" + id + " off=" + offset + " + // skip=" + skip + " idat=" + + // isIdatType); + // first see if we should terminate an active curReaderDeflatedSet + boolean forCurrentIdatSet = false; + if (curDeflatedSet != null && !curDeflatedSet.isClosed()) { + forCurrentIdatSet = curDeflatedSet.ackNextChunkId(id); + } + if (isIdatType && !skip) { // IDAT non skipped: create a DeflatedChunkReader owned by a idatSet + if (!forCurrentIdatSet) { + if (curDeflatedSet != null && !curDeflatedSet.isDone()) { + throw new PngjInputException("new IDAT-like chunk when previous was not done"); + } + curDeflatedSet = createIdatSet(id); + } + curChunkReader = new DeflatedChunkReader(len, id, checkCrc, offset, curDeflatedSet) { + @Override + protected void chunkDone() { + super.chunkDone(); + postProcessChunk(this); + } + }; + + } else { // for non-idat chunks (or skipped idat like) + curChunkReader = createChunkReaderForNewChunk(id, len, offset, skip); + } + if (curChunkReader != null && !checkCrc) { + curChunkReader.setCrcCheck(false); + } + } + + /** + * This will be called for all chunks (even skipped), except for IDAT-like + * non-skiped chunks + *

      + * The default behaviour is to create a ChunkReader in BUFFER mode (or SKIP + * if skip==true) that calls {@link #postProcessChunk(ChunkReader)} (always) + * when done. + * + * @param id Chunk id + * @param len Chunk length + * @param offset offset inside PNG stream , merely informative + * @param skip flag: is true, the content will not be buffered (nor + * processed) + * @return a newly created ChunkReader that will create the ChunkRaw and + * then discarded + */ + protected ChunkReader createChunkReaderForNewChunk(String id, int len, long offset, boolean skip) { + return new ChunkReader(len, id, offset, skip ? ChunkReaderMode.SKIP : ChunkReaderMode.BUFFER) { + @Override + protected void chunkDone() { + postProcessChunk(this); + } + + @Override + protected void processData(int offsetinChhunk, byte[] buf, int off, int len) { + throw new PngjExceptionInternal("should never happen"); + } + }; + } + + /** + * This is called after a chunk is read, in all modes + *

      + * This implementation only chenks the id of the first chunk, and process + * the IEND chunk (sets done=true) + * * + * Further processing should be overriden (call this first!) + **/ + protected void postProcessChunk(ChunkReader chunkR) { // called after chunk is read + if (chunkCount == 1) { + // first chunk ? + String cid = firstChunkId(); + if (cid != null && !cid.equals(chunkR.getChunkRaw().id)) { + String msg = "Bad first chunk: " + chunkR.getChunkRaw().id + " expected: " + firstChunkId(); + if (errorBehaviour.c < ErrorBehaviour.SUPER_LENIENT.c) { + throw new PngjInputException(msg); + } else { + LOGGER.warning(msg); + } + } + } + if (endChunkId() != null && chunkR.getChunkRaw().id.equals(endChunkId())) { + // last chunk ? + endChunkDone = true; + close(); + } + } + + /** + * DeflatedChunksSet factory. This implementation is quite dummy, it usually + * should be overriden. + */ + protected abstract DeflatedChunksSet createIdatSet(String id); + + /** + * Decides if this Chunk is of "IDAT" kind (in concrete: if it is, and if + * it's not to be skiped, a DeflatedChunksSet will be created to deflate it + * and process+ the deflated data) + *

      + * This implementation always returns always false + * + * @param id + */ + protected boolean isIdatKind(String id) { + return false; + } + + /** + * Chunks can be skipped depending on id and/or length. Skipped chunks are + * still processed, but their data will be null, and CRC will never checked + * + * @param len + * @param id + */ + protected boolean shouldSkipContent(int len, String id) { + return false; + } + + protected boolean shouldCheckCrc(int len, String id) { + return true; + } + + /** + * Throws PngjInputException if bad signature + * + * @param buf Signature. Should be of length 8 + */ + protected void checkSignature(byte[] buf) { + if (!Arrays.equals(buf, PngHelperInternal.getPngIdSignature())) { + throw new PngjBadSignature("Bad signature:" + Arrays.toString(buf)); + } + } + + /** + * If false, we are still reading the signature + * + * @return true if signature has been read (or if we don't have signature) + */ + public boolean isSignatureDone() { + return signatureDone; + } + + /** + * If true, we have processe the IEND chunk + */ + @Override + public boolean isDone() { + return endChunkDone; + } + + /** + * Terminated, normally or not - If true, this will not accept more bytes + */ + public boolean isClosed() { + return closed; + } + + /** + * total of bytes read (buffered or not) + */ + public long getBytesCount() { + return bytesCount; + } + + /** + * @return Chunks already read, including partial reading (currently + * reading) + */ + public int getChunkCount() { + return chunkCount; + } + + /** + * Currently reading chunk, or just ended reading + * + * @return null only if still reading signature + */ + public ChunkReader getCurChunkReader() { + return curChunkReader; + } + + /** + * The latest deflated set (typically IDAT chunks) reader. Notice that there + * could be several idat sets (eg for APNG) + */ + public DeflatedChunksSet getCurDeflatedSet() { + return curDeflatedSet; + } + + /** + * Closes this object and release resources. For normal termination or + * abort. Secure and idempotent. + */ + public void close() { // forced closing + if (curDeflatedSet != null) { + curDeflatedSet.close(); + } + closed = true; + } + + /** + * Returns true if we are not in middle of a chunk: we have just ended + * reading past chunk , or we are at the start, or end of signature, or we + * are done + */ + public boolean isAtChunkBoundary() { + return bytesCount == 0 || bytesCount == 8 || closed || curChunkReader == null || curChunkReader.isDone(); + } + + /** + * Which should be the id of the first chunk. This will be checked + * + * @return null if you don't want to check it + */ + protected String firstChunkId() { + return "IHDR"; + } + + /** + * Helper method, reports amount of bytes inside IDAT chunks. + * + * @return Bytes in IDAT chunks + */ + public long getIdatBytes() { + return idatBytes; + } + + /** + * Which should be the id of the last chunk + */ + protected String endChunkId() { + return "IEND"; + } + + /** + * Reads all content from a file. Helper method, only for callback mode + */ + public void feedFromFile(File f) { + try { + feedFromInputStream(new FileInputStream(f), true); + } catch (FileNotFoundException e) { + throw new PngjInputException("Error reading from file '" + f + "' :" + e.getMessage()); + } + } + + /** + * Reads all content from an input stream. Helper method, only for callback + * mode + *

      + * Caller should call isDone() to assert all expected chunks have been read + *

      + * Warning: this does not close this object, unless ended + * + * @param is + * @param closeStream Closes the input stream when done (or if error) + */ + public void feedFromInputStream(InputStream is, boolean closeStream) { + BufferedStreamFeeder sf = new BufferedStreamFeeder(is); + sf.setCloseStream(closeStream); + try { + sf.feedAll(this); + } finally { + sf.close(); + } + } + + public void feedFromInputStream(InputStream is) { + feedFromInputStream(is, true); + } + + public void setErrorBehaviour(ErrorBehaviour errorBehaviour) { + this.errorBehaviour = errorBehaviour; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReaderPng.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReaderPng.java new file mode 100644 index 0000000..e1289db --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqReaderPng.java @@ -0,0 +1,341 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.xbib.graphics.imageio.plugins.png.pngj.ChunkReader.ChunkReaderMode; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkFactory; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkHelper; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkLoadBehaviour; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksList; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunk; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIDAT; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIEND; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIHDR; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkPLTE; + +/** + * Adds to ChunkSeqReader the storing of PngChunk, with a PngFactory, and + * imageInfo + deinterlacer. + *

      + * Most usual PNG reading should use this class, or a {@link PngReader}, which + * is a thin wrapper over this. + */ +public class ChunkSeqReaderPng extends ChunkSeqReader { + + protected ImageInfo imageInfo; // initialized at parsing the IHDR + protected ImageInfo curImageInfo; // can vary, for apng + protected Deinterlacer deinterlacer; + + /** + * From 0 to 6, see ChunksList CHUNK_GROUP_* + */ + protected int currentChunkGroup = -1; + + /** + * All chunks, but some of them can have the buffer empty (IDAT and skipped) + */ + protected ChunksList chunksList = null; + protected final boolean callbackMode; + private long bytesAncChunksLoaded = 0; // bytes loaded from buffered chunks non-critical chunks (data only) + + private boolean checkCrc = true; + + // --- parameters to be set prior to reading --- + private boolean includeNonBufferedChunks = false; + + private final Set chunksToSkip = new HashSet(); + private long maxTotalBytesRead = 0; + private long skipChunkMaxSize = 0; + private long maxBytesMetadata = 0; + private IChunkFactory chunkFactory; + private ChunkLoadBehaviour chunkLoadBehaviour = ChunkLoadBehaviour.LOAD_CHUNK_ALWAYS; + + public ChunkSeqReaderPng(boolean callbackMode) { + super(); + this.callbackMode = callbackMode; + chunkFactory = new ChunkFactory(); // default factory + } + + // call just after beginging new chunk parse + private void updateAndCheckChunkGroup(String id) { + if (id.equals(PngChunkIHDR.ID)) { // IDHR + if (currentChunkGroup < 0) { + currentChunkGroup = ChunksList.CHUNK_GROUP_0_IDHR; + } else { + throw new PngjInputException("unexpected chunk " + id); + } + } else if (id.equals(PngChunkPLTE.ID)) { // PLTE + if ((currentChunkGroup == ChunksList.CHUNK_GROUP_0_IDHR + || currentChunkGroup == ChunksList.CHUNK_GROUP_1_AFTERIDHR)) { + currentChunkGroup = ChunksList.CHUNK_GROUP_2_PLTE; + } else { + throw new PngjInputException("unexpected chunk here " + id); + } + } else if (id.equals(PngChunkIDAT.ID)) { // IDAT (no necessarily the first) + if ((currentChunkGroup >= ChunksList.CHUNK_GROUP_0_IDHR + && currentChunkGroup <= ChunksList.CHUNK_GROUP_4_IDAT)) { + currentChunkGroup = ChunksList.CHUNK_GROUP_4_IDAT; + } else { + throw new PngjInputException("unexpected chunk " + id); + } + } else if (id.equals(PngChunkIEND.ID)) { // END + if ((currentChunkGroup >= ChunksList.CHUNK_GROUP_4_IDAT)) { + currentChunkGroup = ChunksList.CHUNK_GROUP_6_END; + } else { + throw new PngjInputException("unexpected chunk " + id); + } + } else { // ancillary + if (currentChunkGroup <= ChunksList.CHUNK_GROUP_1_AFTERIDHR) { + currentChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR; + } else if (currentChunkGroup <= ChunksList.CHUNK_GROUP_3_AFTERPLTE) { + currentChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE; + } else { + currentChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT; + } + } + } + + @Override + public boolean shouldSkipContent(int len, String id) { + if (super.shouldSkipContent(len, id)) { + return true; + } + if (maxTotalBytesRead > 0 && len + getBytesCount() > maxTotalBytesRead) { + throw new PngjInputException("Maximum total bytes to read exceeeded: " + maxTotalBytesRead + " offset:" + + getBytesCount() + " len=" + len); + } + if (chunksToSkip.contains(id)) { + return true; // specific skip + } + if (ChunkHelper.isCritical(id)) { + return false;// critical chunks are never skipped + } + if (skipChunkMaxSize > 0 && len > skipChunkMaxSize) { + return true; // too big chunk + } + if (maxBytesMetadata > 0 && len > maxBytesMetadata - bytesAncChunksLoaded) { + return true; // too much ancillary chunks loaded + } + switch (chunkLoadBehaviour) { + case LOAD_CHUNK_IF_SAFE: + if (!ChunkHelper.isSafeToCopy(id)) { + return true; + } + break; + case LOAD_CHUNK_NEVER: + return true; + default: + break; + } + return false; + } + + public long getBytesChunksLoaded() { + return bytesAncChunksLoaded; + } + + public int getCurrentChunkGroup() { + return currentChunkGroup; + } + + public void setChunksToSkip(String... chunksToSkip) { + this.chunksToSkip.clear(); + for (String c : chunksToSkip) { + this.chunksToSkip.add(c); + } + } + + public void addChunkToSkip(String chunkToSkip) { + this.chunksToSkip.add(chunkToSkip); + } + + public void dontSkipChunk(String chunkToSkip) { + this.chunksToSkip.remove(chunkToSkip); + } + + public boolean firstChunksNotYetRead() { + return getCurrentChunkGroup() < ChunksList.CHUNK_GROUP_4_IDAT; + } + + @Override + protected void postProcessChunk(ChunkReader chunkR) { + // PngHelperInternal.debug("postProcessChunk " + chunkR.getChunkRaw().id); + super.postProcessChunk(chunkR); + if (chunkR.getChunkRaw().id.equals(PngChunkIHDR.ID)) { + PngChunkIHDR ch = new PngChunkIHDR(null); + ch.parseFromRaw(chunkR.getChunkRaw()); + imageInfo = ch.createImageInfo(); + curImageInfo = imageInfo; + if (ch.isInterlaced()) { + deinterlacer = new Deinterlacer(curImageInfo); + } + chunksList = new ChunksList(imageInfo); + } + if (chunkR.mode == ChunkReaderMode.BUFFER && countChunkTypeAsAncillary(chunkR.getChunkRaw().id)) { + bytesAncChunksLoaded += chunkR.getChunkRaw().len; + } + if (chunkR.mode == ChunkReaderMode.BUFFER || includeNonBufferedChunks) { + try { + PngChunk chunk = chunkFactory.createChunk(chunkR.getChunkRaw(), getImageInfo()); + chunksList.appendReadChunk(chunk, currentChunkGroup); + } catch (PngjException e) { + throw e; + } + } + if (isDone()) { + processEndPng(); + } + } + + protected boolean countChunkTypeAsAncillary(String id) { + return !ChunkHelper.isCritical(id); + } + + @Override + protected DeflatedChunksSet createIdatSet(String id) { + IdatSet ids = new IdatSet(id, callbackMode, getCurImgInfo(), deinterlacer); + return ids; + } + + public IdatSet getIdatSet() { + DeflatedChunksSet c = getCurDeflatedSet(); + return c instanceof IdatSet ? (IdatSet) c : null; + } + + @Override + protected boolean isIdatKind(String id) { + return id.equals(PngChunkIDAT.ID); + } + + @Override + public int consume(byte[] buf, int off, int len) { + return super.consume(buf, off, len); + } + + /** + * sets a custom chunk factory. This is typically called with a custom class + * extends ChunkFactory, to adds custom chunks to the default well-know ones + * + * @param chunkFactory + */ + public void setChunkFactory(IChunkFactory chunkFactory) { + this.chunkFactory = chunkFactory; + } + + /** + * Things to be done after closed. + */ + protected void processEndPng() { + // nothing to do + } + + public ImageInfo getImageInfo() { + return imageInfo; + } + + public boolean isInterlaced() { + return deinterlacer != null; + } + + public Deinterlacer getDeinterlacer() { + return deinterlacer; + } + + @Override + protected void startNewChunk(int len, String id, long offset) { + updateAndCheckChunkGroup(id); + super.startNewChunk(len, id, offset); + } + + @Override + public void close() { + if (currentChunkGroup != ChunksList.CHUNK_GROUP_6_END)// this could only happen if forced close + { + currentChunkGroup = ChunksList.CHUNK_GROUP_6_END; + } + super.close(); + } + + public List getChunks() { + return chunksList.getChunks(); + } + + public void setMaxTotalBytesRead(long maxTotalBytesRead) { + this.maxTotalBytesRead = maxTotalBytesRead; + } + + public long getSkipChunkMaxSize() { + return skipChunkMaxSize; + } + + public void setSkipChunkMaxSize(long skipChunkMaxSize) { + this.skipChunkMaxSize = skipChunkMaxSize; + } + + public long getMaxBytesMetadata() { + return maxBytesMetadata; + } + + public void setMaxBytesMetadata(long maxBytesMetadata) { + this.maxBytesMetadata = maxBytesMetadata; + } + + public long getMaxTotalBytesRead() { + return maxTotalBytesRead; + } + + @Override + protected boolean shouldCheckCrc(int len, String id) { + return checkCrc; + } + + public boolean isCheckCrc() { + return checkCrc; + } + + public void setCheckCrc(boolean checkCrc) { + this.checkCrc = checkCrc; + } + + public boolean isCallbackMode() { + return callbackMode; + } + + public Set getChunksToSkip() { + return chunksToSkip; + } + + public void setChunkLoadBehaviour(ChunkLoadBehaviour chunkLoadBehaviour) { + this.chunkLoadBehaviour = chunkLoadBehaviour; + } + + public ImageInfo getCurImgInfo() { + return curImageInfo; + } + + public void updateCurImgInfo(ImageInfo iminfo) { + if (!iminfo.equals(curImageInfo)) { + curImageInfo = iminfo; + } + if (deinterlacer != null) { + deinterlacer = new Deinterlacer(curImageInfo); // we could reset it, but... + } + } + + /** + * If true, the chunks with no data (because skipped or because processed + * like IDAT-type) are still stored in the PngChunks list, which might be + * more informative. + *

      + * Setting this to false saves a few bytes + *

      + * Default: false + * + * @param includeNonBufferedChunks + */ + public void setIncludeNonBufferedChunks(boolean includeNonBufferedChunks) { + this.includeNonBufferedChunks = includeNonBufferedChunks; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqSkipping.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqSkipping.java new file mode 100644 index 0000000..af8ff0d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ChunkSeqSkipping.java @@ -0,0 +1,76 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.ArrayList; +import java.util.List; +import org.xbib.graphics.imageio.plugins.png.pngj.ChunkReader.ChunkReaderMode; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkRaw; + +/** + * This simple reader skips all chunks contents and stores the chunkRaw in a + * list. Useful to read chunks structure. + *

      + * Optionally the contents might be processed. This doesn't distinguish IDAT + * chunks + */ +public class ChunkSeqSkipping extends ChunkSeqReader { + + private final List chunks = new ArrayList(); + private boolean skip = true; + + /** + * @param skipAll if true, contents will be truly skipped, and CRC will not be + * computed + */ + public ChunkSeqSkipping(boolean skipAll) { + super(); + skip = skipAll; + } + + public ChunkSeqSkipping() { + this(true); + } + + protected ChunkReader createChunkReaderForNewChunk(String id, int len, long offset, boolean skip) { + return new ChunkReader(len, id, offset, skip ? ChunkReaderMode.SKIP : ChunkReaderMode.PROCESS) { + @Override + protected void chunkDone() { + postProcessChunk(this); + } + + @Override + protected void processData(int offsetinChhunk, byte[] buf, int off, int len) { + processChunkContent(getChunkRaw(), offsetinChhunk, buf, off, len); + } + }; + } + + protected void processChunkContent(ChunkRaw chunkRaw, int offsetinChhunk, byte[] buf, int off, int len) { + // does nothing + } + + @Override + protected void postProcessChunk(ChunkReader chunkR) { + super.postProcessChunk(chunkR); + chunks.add(chunkR.getChunkRaw()); + } + + @Override + protected boolean shouldSkipContent(int len, String id) { + return skip; + } + + @Override + protected boolean isIdatKind(String id) { + return false; + } + + public List getChunks() { + return chunks; + } + + @Override + protected DeflatedChunksSet createIdatSet(String id) { + throw new RuntimeException("Should not get here"); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunkReader.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunkReader.java new file mode 100644 index 0000000..3e1254c --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunkReader.java @@ -0,0 +1,88 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkFDAT; + +/** + * Specialization of ChunkReader, for IDAT-like chunks. These chunks are part of + * a set of similar chunks (contiguos normally, not necessariyl) which conforms + * a zlib stream + */ +public abstract class DeflatedChunkReader extends ChunkReader { + + protected final DeflatedChunksSet deflatedChunksSet; + protected boolean alsoBuffer = false; + + protected boolean skipBytes = false; // fDAT (APNG) skips 4 bytes) + protected byte[] skippedBytes; // only for fDAT + protected int seqNumExpected = -1; // only for fDAT + + public DeflatedChunkReader(int clen, String chunkid, boolean checkCrc, long offsetInPng, + DeflatedChunksSet iDatSet) { + super(clen, chunkid, offsetInPng, ChunkReaderMode.PROCESS); + this.deflatedChunksSet = iDatSet; + if (chunkid.equals(PngChunkFDAT.ID)) { + skipBytes = true; + skippedBytes = new byte[4]; + } + iDatSet.appendNewChunk(this); + } + + /** + * Delegates to ChunkReaderDeflatedSet.processData() + */ + @Override + protected void processData(int offsetInchunk, byte[] buf, int off, int len) { + if (skipBytes && offsetInchunk < 4) {// only for APNG (sigh) + for (int oc = offsetInchunk; oc < 4 && len > 0; oc++, off++, len--) { + skippedBytes[oc] = buf[off]; + } + } + if (len > 0) { // delegate to idatSet + + deflatedChunksSet.processBytes(buf, off, len); + if (alsoBuffer) { // very rare! + System.arraycopy(buf, off, getChunkRaw().data, read, len); + } + } + } + + /** + * only a stupid check for fDAT (I wonder how many APGN readers do this) + */ + @Override + protected void chunkDone() { + if (skipBytes && getChunkRaw().id.equals(PngChunkFDAT.ID)) { + if (seqNumExpected >= 0) { + int seqNum = PngHelperInternal.readInt4fromBytes(skippedBytes, 0); + if (seqNum != seqNumExpected) { + throw new PngjInputException( + "bad chunk sequence for fDAT chunk " + seqNum + " expected " + seqNumExpected); + } + } + } + } + + @Override + public boolean isFromDeflatedSet() { + return true; + } + + /** + * In some rare cases you might want to also buffer the data? + */ + public void setAlsoBuffer() { + if (read > 0) { + throw new RuntimeException("too late"); + } + alsoBuffer = true; + getChunkRaw().allocData(); + } + + /** + * only relevant for fDAT + */ + public void setSeqNumExpected(int seqNumExpected) { + this.seqNumExpected = seqNumExpected; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunksSet.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunksSet.java new file mode 100644 index 0000000..5b631e7 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/DeflatedChunksSet.java @@ -0,0 +1,452 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * A set of IDAT-like chunks which, concatenated, form a zlib stream. + *

      + * The inflated stream is intented to be read as a sequence of "rows", of which + * the caller knows the lengths (not necessary equal!) and number. + *

      + * Eg: For IDAT non-interlaced images, a row has bytesPerRow + 1 filter byte
      + * For interlaced images, the lengths are variable. + *

      + * This class can work in sync (polled) mode or async (callback) mode. But for + * callback mode the method processRowCallback() must be overriden + *

      + * See {@link IdatSet}, which is mostly used and has a slightly simpler use.
      + * See DeflatedChunkSetTest for example of use. + */ +public class DeflatedChunksSet { + + protected byte[] row; // a "row" here means a raw (uncopressed filtered) part of the IDAT stream, + // normally a image row (or subimage row for interlaced) plus a filter byte + private int rowfilled; // effective/valid length of row + private int rowlen; // what amount of bytes is to be interpreted as a complete "row". can change + // (for interlaced) + private int rown; // only coincide with image row if non-interlaced - incremented by + // setNextRowSize() + + /* + * States WAITING_FOR_INPUT ROW_READY WORK_DONE TERMINATED + * + * processBytes() is externally called, prohibited in READY (in DONE it's + * ignored) + * + * WARNING: inflater.finished() != DONE (not enough, not neccesary) DONE means + * that we have already uncompressed all the data of interest. + * + * In non-callback mode, prepareForNextRow() is also externally called, in + * + * Flow: - processBytes() calls inflateData() + * + * inflateData() : if buffer is filled goes to READY + * + * else if ! inf.finished goes to WAITING + * + * else if any data goes to READY (incomplete data to be read) + * + * else goes to DONE + * + * in Callback mode, after going to READY, n=processCallback() is called and + * then prepareForNextRow(n) is called. + * + * in Polled mode, prepareForNextRow(n) must be called from outside (after + * checking state=READY) + * + * prepareForNextRow(n) goes to DONE if n==0 calls inflateData() again - end() + * goes to DONE + */ + private enum State { + WAITING_FOR_INPUT, // waiting for more bytes to be fed + ROW_READY, // ready for consumption (might be less than fully filled), ephemeral state for + // CALLBACK mode + DONE, // all data of interest has been read, but we might accept still more trailing + // chunks (we'll ignore them) + CLOSED; // we are done, and also won't accept more IDAT chunks + + public boolean isDone() { + return this == DONE || this == CLOSED; + } // the caller has already uncompressed all the data of interest or EOF + + public boolean isClosed() { + return this == CLOSED; + } // we dont accept more chunks + } + + State state = State.WAITING_FOR_INPUT; // never null + + private Inflater inf; + private final boolean infOwn; // true if we own the inflater (we created it) + + private DeflatedChunkReader curChunk; + + protected final boolean callbackMode; // true: CALLBACK (non-blocking) false: POLL (blocking) + + private long nBytesIn = 0; // count the total compressed bytes that have been fed + private long nBytesOut = 0; // count the total uncompressed bytes + int chunkNum = -1; // incremented at each new chunk start + int firstChunqSeqNum = -1; // expected seq num for first chunk. used only for fDAT (APNG) + + /** + * All IDAT-like chunks that form a same DeflatedChunksSet should have the + * same id + */ + public final String chunkid; + + /** + * @param initialRowLen Length in bytes of first "row" (see description) + * @param maxRowLen Max length in bytes of "rows" + * @param inflater Can be null. If not null, must be already reset (and it must + * be closed/released by caller!) + */ + public DeflatedChunksSet(String chunkid, boolean callbackMode, int initialRowLen, int maxRowLen, Inflater inflater, + byte[] buffer) { + this.chunkid = chunkid; + this.callbackMode = callbackMode; + this.rowlen = initialRowLen; + if (initialRowLen < 1 || maxRowLen < initialRowLen) { + throw new PngjException("bad inital row len " + initialRowLen); + } + if (inflater != null) { + this.inf = inflater; + infOwn = false; + } else { + this.inf = new Inflater(); + infOwn = true; // inflater is own, we will release on close() + } + this.row = buffer != null && buffer.length >= initialRowLen ? buffer : new byte[maxRowLen]; + rown = -1; + this.state = State.WAITING_FOR_INPUT; + try { + prepareForNextRow(initialRowLen); + } catch (RuntimeException e) { + close(); + throw e; + } + } + + public DeflatedChunksSet(String chunkid, boolean callbackMode, int initialRowLen, int maxRowLen) { + this(chunkid, callbackMode, initialRowLen, maxRowLen, null, null); + } + + protected void appendNewChunk(DeflatedChunkReader cr) { + // all chunks must have same id + if (!this.chunkid.equals(cr.getChunkRaw().id)) { + throw new PngjInputException( + "Bad chunk inside IdatSet, id:" + cr.getChunkRaw().id + ", expected:" + this.chunkid); + } + this.curChunk = cr; + chunkNum++; + if (firstChunqSeqNum >= 0) { + cr.setSeqNumExpected(chunkNum + firstChunqSeqNum); + } + } + + /** + * Feeds the inflater with the compressed bytes + *

      + * In poll mode, the caller should not call repeatedly this, without + * consuming first, checking isDataReadyForConsumer() + * + * @param buf + * @param off + * @param len + */ + protected void processBytes(byte[] buf, int off, int len) { + nBytesIn += len; + // PngHelperInternal.LOGGER.info("processing compressed bytes in chunkreader : " + // + len); + if (len < 1 || state.isDone()) { + return; + } + if (state == State.ROW_READY) { + throw new PngjInputException("this should only be called if waitingForMoreInput"); + } + if (inf.needsDictionary() || !inf.needsInput()) { + throw new RuntimeException("should not happen"); + } + inf.setInput(buf, off, len); + // PngHelperInternal.debug("entering processs bytes, state=" + state + + // " callback="+callbackMode); + if (callbackMode) { + while (inflateData()) { + int nextRowLen = processRowCallback(); + prepareForNextRow(nextRowLen); + if (isDone()) { + processDoneCallback(); + } + } + } else { + inflateData(); + } + } + + /* + * This never inflates more than one row This returns true if this has resulted + * in a row being ready and preprocessed with preProcessRow (in callback mode, + * we should call immediately processRowCallback() and + * prepareForNextRow(nextRowLen) + */ + private boolean inflateData() { + try { + // PngHelperInternal.debug("entering inflateData bytes, state=" + state + + // " callback="+callbackMode); + if (state == State.ROW_READY) { + throw new PngjException("invalid state");// assert + } + if (state.isDone()) { + return false; + } + int ninflated = 0; + if (row == null || row.length < rowlen) { + row = new byte[rowlen]; // should not happen + } + if (rowfilled < rowlen && !inf.finished()) { + try { + ninflated = inf.inflate(row, rowfilled, rowlen - rowfilled); + } catch (DataFormatException e) { + throw new PngjInputException("error decompressing zlib stream ", e); + } + rowfilled += ninflated; + nBytesOut += ninflated; + } + State nextstate = null; + if (rowfilled == rowlen) { + nextstate = State.ROW_READY; // complete row, process it + } else if (!inf.finished()) { + nextstate = State.WAITING_FOR_INPUT; + } else if (rowfilled > 0) { + nextstate = State.ROW_READY; // complete row, process it + } else { + nextstate = State.DONE; // eof, no more data + } + state = nextstate; + if (state == State.ROW_READY) { + preProcessRow(); + return true; + } + } catch (RuntimeException e) { + close(); + throw e; + } + return false; + } + + /** + * Called automatically in all modes when a full row has been fully + * inflated. + */ + protected void preProcessRow() { + + } + + /** + * Callback, must be implemented in callbackMode + *

      + * This should use {@link #getRowFilled()} and {@link #getInflatedRow()} to + * access the row. + *

      + * Must return byes of next row, for next callback. + */ + protected int processRowCallback() { + throw new PngjInputException("not implemented"); + } + + /** + * Callback, to be implemented in callbackMode + *

      + * This will be called once to notify state done + */ + protected void processDoneCallback() { + } + + /** + * Inflated buffer. + *

      + * The effective length is given by {@link #getRowFilled()} + */ + public byte[] getInflatedRow() { + return row; + } + + /** + * Should be called after the previous row was processed + *

      + * Pass 0 or negative to signal that we are done (not expecting more bytes) + *

      + * This resets {@link #rowfilled} + *

      + * The + */ + public void prepareForNextRow(int len) { + rowfilled = 0; + rown++; + if (len < 1) { + rowlen = 0; + markAsDone(); + } else if (inf.finished()) { + rowlen = 0; + markAsDone(); + } else { + state = State.WAITING_FOR_INPUT; + rowlen = len; + if (!callbackMode) { + inflateData(); + } + } + } + + /** + * In this state, the object is waiting for more input to deflate. + *

      + * Only in this state it's legal to feed this + */ + public boolean isWaitingForMoreInput() { + return state == State.WAITING_FOR_INPUT; + } + + /** + * In this state, the object is waiting the caller to retrieve inflated data + *

      + * Effective length: see {@link #getRowFilled()} + */ + public boolean isRowReady() { + return state == State.ROW_READY; + } + + /** + * In this state, all relevant data has been uncompressed and retrieved + * (exceptionally, the reading has ended prematurely). + *

      + * We can still feed this object, but the bytes will be swallowed/ignored. + */ + public boolean isDone() { + return state.isDone(); + } + + public boolean isClosed() { + return state.isClosed(); + } + + /** + * This will be called by the owner to report us the next chunk to come. We + * can make our own internal changes and checks. This returns true if we + * acknowledge the next chunk as part of this set + */ + public boolean ackNextChunkId(String id) { + if (state.isClosed()) { + return false; + } else if (id.equals(chunkid)) { + return true; + } else { + if (!allowOtherChunksInBetween(id)) { + if (state.isDone()) { + if (!state.isClosed()) { + close(); + } + return false; + } else { + throw new PngjInputException("Unexpected chunk " + id + " while " + chunkid + " set is not done"); + } + } else { + return true; + } + } + } + + /** + * This should be called when discarding this object, or for aborting. + * Secure, idempotent Don't use this just to notify this object that it has + * no more work to do, see {@link #markAsDone()} + */ + public void close() { + try { + if (!state.isClosed()) { + state = State.CLOSED; + } + if (infOwn && inf != null) { + inf.end();// we end the Inflater only if we created it + inf = null; + } + } catch (Exception e) { + } + } + + /** + * Forces the DONE state (except if it was CLOSED), this object won't uncompress more data. It's still + * not terminated, it will accept more IDAT chunks, but will ignore them. + */ + public void markAsDone() { + if (!isDone()) { + state = State.DONE; + } + } + + /** + * Target size of the current row, including filter byte.
      + * should coincide (or be less than) with row.length + */ + public int getRowLen() { + return rowlen; + } + + /** + * This the amount of valid bytes in the buffer + */ + public int getRowFilled() { + return rowfilled; + } + + /** + * Get current (last) row number. + *

      + * This corresponds to the raw numeration of rows as seen by the deflater. + * Not the same as the real image row, if interlaced. + */ + public int getRown() { + return rown; + } + + /** + * Some IDAT-like set can allow other chunks in between (APGN?). + *

      + * Normally false. + * + * @param id Id of the other chunk that appeared in middel of this set. + * @return true if allowed + */ + public boolean allowOtherChunksInBetween(String id) { + return false; + } + + /** + * Callback mode = async processing + */ + public boolean isCallbackMode() { + return callbackMode; + } + + /** + * total number of bytes that have been fed to this object + */ + public long getBytesIn() { + return nBytesIn; + } + + /** + * total number of bytes that have been uncompressed + */ + public long getBytesOut() { + return nBytesOut; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("idatSet : " + curChunk.getChunkRaw().id + " state=" + state + " rows=" + + rown + " bytes=" + nBytesIn + "/" + nBytesOut); + return sb.toString(); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/Deinterlacer.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/Deinterlacer.java new file mode 100644 index 0000000..051a7e5 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/Deinterlacer.java @@ -0,0 +1,208 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +public class Deinterlacer { + final ImageInfo imi; + private int pass; // 1-7 + private int rows, cols; + int dY, dX, oY, oX; // current step and offset (in pixels) + int oXsamples, dXsamples; // step in samples + + // current row in the virtual subsampled image; this increments (by 1) from 0 to rows/dy 7 times + private int currRowSubimg; + // in the real image, this will cycle from 0 to im.rows in different steps, 7 times + private int currRowReal; + private int currRowSeq; // not counting empty rows + + int totalRows; + private boolean ended = false; + + public Deinterlacer(ImageInfo iminfo) { + this.imi = iminfo; + pass = 0; + currRowSubimg = -1; + currRowReal = -1; + currRowSeq = 0; + ended = false; + totalRows = 0; // lazy compute + setPass(1); + setRow(0); + } + + /** + * this refers to the row currRowSubimg + */ + private void setRow(int n) { // This should be called only intercally, in sequential order + currRowSubimg = n; + currRowReal = n * dY + oY; + if (currRowReal < 0 || currRowReal >= imi.rows) { + throw new PngjExceptionInternal("bad row - this should not happen"); + } + } + + /** + * Skips passes with no rows. Return false is no more rows + */ + boolean nextRow() { + currRowSeq++; + if (rows == 0 || currRowSubimg >= rows - 1) { // next pass + if (pass == 7) { + ended = true; + return false; + } + setPass(pass + 1); + if (rows == 0) { + currRowSeq--; + return nextRow(); + } + setRow(0); + } else { + setRow(currRowSubimg + 1); + } + return true; + } + + boolean isEnded() { + return ended; + } + + void setPass(int p) { + if (this.pass == p) { + return; + } + pass = p; + byte[] pp = paramsForPass(p);// dx,dy,ox,oy + dX = pp[0]; + dY = pp[1]; + oX = pp[2]; + oY = pp[3]; + rows = imi.rows > oY ? (imi.rows + dY - 1 - oY) / dY : 0; + cols = imi.cols > oX ? (imi.cols + dX - 1 - oX) / dX : 0; + if (cols == 0) { + rows = 0; // well, really... + } + dXsamples = dX * imi.channels; + oXsamples = oX * imi.channels; + } + + static byte[] paramsForPass(final int p) {// dx,dy,ox,oy + switch (p) { + case 1: + return new byte[]{8, 8, 0, 0}; + case 2: + return new byte[]{8, 8, 4, 0}; + case 3: + return new byte[]{4, 8, 0, 4}; + case 4: + return new byte[]{4, 4, 2, 0}; + case 5: + return new byte[]{2, 4, 0, 2}; + case 6: + return new byte[]{2, 2, 1, 0}; + case 7: + return new byte[]{1, 2, 0, 1}; + default: + throw new PngjExceptionInternal("bad interlace pass" + p); + } + } + + /** + * current row number inside the "sub image" + */ + int getCurrRowSubimg() { + return currRowSubimg; + } + + /** + * current row number inside the "real image" + */ + int getCurrRowReal() { + return currRowReal; + } + + /** + * current pass number (1-7) + */ + int getPass() { + return pass; + } + + /** + * How many rows has the current pass? + **/ + int getRows() { + return rows; + } + + /** + * How many columns (pixels) are there in the current row + */ + int getCols() { + return cols; + } + + public int getPixelsToRead() { + return getCols(); + } + + public int getBytesToRead() { // not including filter byte + return (imi.bitspPixel * getPixelsToRead() + 7) / 8; + } + + public int getdY() { + return dY; + } + + /* + * in pixels + */ + public int getdX() { + return dX; + } + + public int getoY() { + return oY; + } + + /* + * in pixels + */ + public int getoX() { + return oX; + } + + public int getTotalRows() { + if (totalRows == 0) { // lazy compute + for (int p = 1; p <= 7; p++) { + byte[] pp = paramsForPass(p); // dx dy ox oy + int rows = imi.rows > pp[3] ? (imi.rows + pp[1] - 1 - pp[3]) / pp[1] : 0; + int cols = imi.cols > pp[2] ? (imi.cols + pp[0] - 1 - pp[2]) / pp[0] : 0; + if (rows > 0 && cols > 0) { + totalRows += rows; + } + } + } + return totalRows; + } + + /** + * total unfiltered bytes in the image, including the filter byte + */ + public long getTotalRawBytes() { // including the filter byte + long bytes = 0; + for (int p = 1; p <= 7; p++) { + byte[] pp = paramsForPass(p); // dx dy ox oy + int rows = imi.rows > pp[3] ? (imi.rows + pp[1] - 1 - pp[3]) / pp[1] : 0; + int cols = imi.cols > pp[2] ? (imi.cols + pp[0] - 1 - pp[2]) / pp[0] : 0; + int bytesr = (imi.bitspPixel * cols + 7) / 8; // without filter byte + if (rows > 0 && cols > 0) { + bytes += rows * (1 + (long) bytesr); + } + } + return bytes; + } + + public int getCurrRowSeq() { + return currRowSeq; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ErrorBehaviour.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ErrorBehaviour.java new file mode 100644 index 0000000..5bb6325 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ErrorBehaviour.java @@ -0,0 +1,14 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +public enum ErrorBehaviour { + STRICT(0), // default mode: any error aborts reading with exception + LENIENT1_CRC(1), // CRC errors only trigger warning (or nothing if not checking) + LENIENT2_ANCILLARY(3), // also: content errors in ancillary chunks are ignored + SUPER_LENIENT(5); // we try hard to read, even garbage, without throwing exceptions + final int c; + + ErrorBehaviour(int c) { + this.c = c; + } + +} \ No newline at end of file diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/FilterType.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/FilterType.java new file mode 100644 index 0000000..e64e312 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/FilterType.java @@ -0,0 +1,119 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.HashMap; + +/** + * Internal PNG predictor filter type + *

      + * Negative values are pseudo types, actually global strategies for writing, + * that (can) result on different real filters for different rows + */ +public enum FilterType { + /** + * No filter. + */ + FILTER_NONE(0), + /** + * SUB filter (uses same row) + */ + FILTER_SUB(1), + /** + * UP filter (uses previous row) + */ + FILTER_UP(2), + /** + * AVERAGE filter + */ + FILTER_AVERAGE(3), + /** + * PAETH predictor + */ + FILTER_PAETH(4), + /** + * Default strategy: select one of the standard filters depending on global + * image parameters + */ + FILTER_DEFAULT(-1), + /** + * Adaptative strategy, sampling each row, or almost + */ + FILTER_ADAPTIVE_FULL(-4), + /** + * Adaptive strategy, skippping some rows + */ + FILTER_ADAPTIVE_MEDIUM(-3), // samples about 1/4 row + /** + * Adaptative strategy, skipping many rows - more speed + */ + FILTER_ADAPTIVE_FAST(-2), // samples each 8 or 16 rows + /** + * Experimental + */ + FILTER_SUPER_ADAPTIVE(-10), // + /** + * Preserves the filter passed in original row. + */ + FILTER_PRESERVE(-40), + /** + * Uses all fiters, one for lines, cyciclally. Only for tests. + */ + FILTER_CYCLIC(-50), + /** + * Not specified, placeholder for unknown or NA filters. + */ + FILTER_UNKNOWN(-100); + + public final int val; + + FilterType(int val) { + this.val = val; + } + + private static final HashMap byVal; + + static { + byVal = new HashMap(); + for (FilterType ft : values()) { + byVal.put(ft.val, ft); + } + } + + public static FilterType getByVal(int i) { + return byVal.get(i); + } + + /** + * only considers standard + */ + public static boolean isValidStandard(int i) { + return i >= 0 && i <= 4; + } + + public static boolean isValidStandard(FilterType fy) { + return fy != null && isValidStandard(fy.val); + } + + public static boolean isAdaptive(FilterType fy) { + return fy.val <= -2 && fy.val >= -4; + } + + /** + * Returns all "standard" filters + */ + public static FilterType[] getAllStandard() { + return new FilterType[]{FILTER_NONE, FILTER_SUB, FILTER_UP, FILTER_AVERAGE, FILTER_PAETH}; + } + + public static FilterType[] getAllStandardNoneLast() { + return new FilterType[]{FILTER_SUB, FILTER_UP, FILTER_AVERAGE, FILTER_PAETH, FILTER_NONE}; + } + + public static FilterType[] getAllStandardExceptNone() { + return new FilterType[]{FILTER_SUB, FILTER_UP, FILTER_AVERAGE, FILTER_PAETH}; + } + + static FilterType[] getAllStandardForFirstRow() { + return new FilterType[]{FILTER_SUB, FILTER_NONE}; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IBytesConsumer.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IBytesConsumer.java new file mode 100644 index 0000000..e31a43d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IBytesConsumer.java @@ -0,0 +1,31 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Bytes consumer. + *

      + * An object implementing can be fed with bytes. + *

      + * It can consume in steps, so each time it's fed with n bytes it can eat + * between 1 and n bytes. + */ +public interface IBytesConsumer { + /** + * Eats some bytes, at most len (perhaps less). + *

      + * Returns bytes actually consumed. + *

      + * It returns -1 if the object didn't consume bytes because it was done or + * closed + *

      + * It should only returns 0 if len is 0 + */ + int consume(byte[] buf, int offset, int len); + + /** + * The consumer is DONE when it does not need more bytes, + * either because it ended normally, or abnormally + * Typically this implies it will return -1 if consume() is called afterwards, + * but it might happen that it will consume more (unneeded) bytes anwyway + */ + boolean isDone(); +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IChunkFactory.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IChunkFactory.java new file mode 100644 index 0000000..77b9b73 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IChunkFactory.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkRaw; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunk; + +/** + * Factory to create a {@link PngChunk} from a {@link ChunkRaw}. + *

      + * Used by {@link PngReader} + */ +public interface IChunkFactory { + + /** + * @param chunkRaw Chunk in raw form. Data can be null if it was skipped or + * processed directly (eg IDAT) + * @param imgInfo Not normally necessary, but some chunks want this info + * @return should never return null. + */ + PngChunk createChunk(ChunkRaw chunkRaw, ImageInfo imgInfo); + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLine.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLine.java new file mode 100644 index 0000000..72bcfd6 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLine.java @@ -0,0 +1,45 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * General format-translated image line. + *

      + * The methods from this interface provides translation from/to PNG raw + * unfiltered pixel data, for each image line. This doesn't make any assumptions + * of underlying storage. + *

      + * The user of this library will not normally use this methods, but instead will + * cast to a more concrete implementation, as {@link ImageLineInt} or + * {@link ImageLineByte} with its methods for accessing the pixel values. + */ +public interface IImageLine { + + /** + * Extract pixels from a raw unlfiltered PNG row. Len is the total amount of + * bytes in the array, including the first byte (filter type) + *

      + * Arguments offset and step (0 and 1 for non interlaced) are in PIXELS. + * It's guaranteed that when step==1 then offset=0 + *

      + * Notice that when step!=1 the data is partial, this method will be called + * several times + *

      + * Warning: the data in array 'raw' starts at position 0 and has 'len' + * consecutive bytes. 'offset' and 'step' refer to the pixels in destination + */ + void readFromPngRaw(byte[] raw, int len, int offset, int step); + + /** + * This is called when the read for the line has been completed (eg for + * interlaced). It's called exactly once for each line. This is provided in + * case the class needs to to some postprocessing. + */ + void endReadFromPngRaw(); + + /** + * Writes the line to a PNG raw byte array, in the unfiltered PNG format + * Notice that the first byte is the filter type, you should write it only + * if you know it. + */ + void writeToPngRaw(byte[] raw); + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineArray.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineArray.java new file mode 100644 index 0000000..bb4b968 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineArray.java @@ -0,0 +1,24 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * This interface is just for the sake of unifying some methods of + * {@link ImageLineHelper} that can use both {@link ImageLineInt} or + * {@link ImageLineByte}. It's not very useful outside that, and the user should + * not rely much on this. + */ +public interface IImageLineArray { + ImageInfo getImageInfo(); + + FilterType getFilterType(); + + /** + * length of array (should correspond to samples) + */ + int getSize(); + + /** + * Get i-th element of array (for 0 to size-1). The meaning of this is type + * dependent. For ImageLineInt and ImageLineByte is the sample value. + */ + int getElem(int i); +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineFactory.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineFactory.java new file mode 100644 index 0000000..f83dac3 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineFactory.java @@ -0,0 +1,8 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Image Line factory. + */ +public interface IImageLineFactory { + T createImageLine(ImageInfo iminfo); +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSet.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSet.java new file mode 100644 index 0000000..385b9e8 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSet.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Set of {@link IImageLine} elements. + *

      + * This is actually a "virtual" set, it can be implemented in several ways; for + * example + *

        + *
      • Cursor-like: stores only one line, which is implicitly moved when + * requested
      • + *
      • All lines: all lines stored as an array of IImageLine
      • + *
      • Subset of lines: eg, only first 3 lines, or odd numbered lines. Or a band + * of neighbours lines that is moved like a cursor.
      • The ImageLine that + * PngReader returns is hosted by a IImageLineSet (this abstraction allows the + * implementation to deal with interlaced images cleanly) but the library user + * does not normally needs to know that (or rely on that), except for the + * {@link PngReader#readRows()} method. + *
      + */ +public interface IImageLineSet { + + /** + * Asks for imageline corresponding to row n in the original image + * (zero based). This can trigger side effects in this object (eg, advance a + * cursor, set current row number...) In some scenarios, this should be + * consider as alias to (pseudocode) + * positionAtLine(n); getCurrentLine(); + *

      + * Throws exception if not available. The caller is supposed to know what + * he/she is doing + **/ + IImageLine getImageLine(int n); + + /** + * Like {@link #getImageLine(int)} but uses the raw numbering inside the + * LineSet This makes little sense for a cursor + * + * @param n Should normally go from 0 to {@link #size()} + * @return + */ + IImageLine getImageLineRawNum(int n); + + /** + * Returns true if the set contain row n (in the original + * image,zero based) currently allocated. + *

      + * If it's a single-cursor, this should return true only if it's positioned + * there. (notice that hasImageLine(n) can return false, but getImageLine(n) + * can be ok) + **/ + boolean hasImageLine(int n); + + /** + * Internal size of allocated rows This is informational, it should rarely + * be important for the caller. + **/ + int size(); + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSetFactory.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSetFactory.java new file mode 100644 index 0000000..626d208 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IImageLineSetFactory.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Factory of {@link IImageLineSet}, used by {@link PngReader}. + *

      + * + * @param Generic type of IImageLine + */ +public interface IImageLineSetFactory { + /** + * Creates a new {@link IImageLineSet} + *

      + * If singleCursor=true, the caller will read and write one row fully at a + * time, in order (it'll never try to read out of order lines), so the + * implementation can opt for allocate only one line. + * + * @param imgInfo Image info + * @param singleCursor : will read/write one row at a time + * @param nlines : how many lines we plan to read + * @param noffset : how many lines we want to skip from the original image + * (normally 0) + * @param step : row step (normally 1) + */ + IImageLineSet create(ImageInfo imgInfo, boolean singleCursor, int nlines, int noffset, int step); +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IPngWriterFactory.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IPngWriterFactory.java new file mode 100644 index 0000000..5a3c162 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IPngWriterFactory.java @@ -0,0 +1,7 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.OutputStream; + +public interface IPngWriterFactory { + PngWriter createPngWriter(OutputStream outputStream, ImageInfo imgInfo); +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IdatChunkWriter.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IdatChunkWriter.java new file mode 100644 index 0000000..ea3114a --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IdatChunkWriter.java @@ -0,0 +1,136 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.OutputStream; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkHelper; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkRaw; + +/** + * Outputs a sequence of IDAT-like chunk, that is filled progressively until the + * max chunk length is reached (or until flush()) + */ +public class IdatChunkWriter { + + private static final int MAX_LEN_DEFAULT = 32768; // 32K rather arbitrary - data only + + private final OutputStream outputStream; + private final int maxChunkLen; + private byte[] buf; + + private int offset = 0; + private int availLen; + private long totalBytesWriten = 0; // including header+crc + private int chunksWriten = 0; + + public IdatChunkWriter(OutputStream outputStream) { + this(outputStream, 0); + } + + public IdatChunkWriter(OutputStream outputStream, int maxChunkLength) { + this.outputStream = outputStream; + this.maxChunkLen = maxChunkLength > 0 ? maxChunkLength : MAX_LEN_DEFAULT; + buf = new byte[maxChunkLen]; + availLen = maxChunkLen - offset; + postReset(); + } + + public IdatChunkWriter(OutputStream outputStream, byte[] b) { + this.outputStream = outputStream; + this.buf = b != null ? b : new byte[MAX_LEN_DEFAULT]; + this.maxChunkLen = b.length; + availLen = maxChunkLen - offset; + postReset(); + } + + protected byte[] getChunkId() { + return ChunkHelper.b_IDAT; + } + + /** + * Writes a chhunk if there is more than minLenToWrite. + *

      + * This is normally called internally, but can be called explicitly to force + * flush. + */ + public final void flush() { + if (offset > 0 && offset >= minLenToWrite()) { + ChunkRaw c = new ChunkRaw(offset, getChunkId(), false); + c.data = buf; + c.writeChunk(outputStream); + totalBytesWriten += c.len + 12; + chunksWriten++; + offset = 0; + availLen = maxChunkLen; + postReset(); + } + } + + public int getOffset() { + return offset; + } + + public int getAvailLen() { + return availLen; + } + + /** + * triggers an flush+reset if appropiate + */ + public void incrementOffset(int n) { + offset += n; + availLen -= n; + if (availLen < 0) { + throw new PngjOutputException("Anomalous situation"); + } + if (availLen == 0) { + flush(); + } + } + + /** + * this should rarely be used, the normal way (to avoid double copying) is + * to get the buffer and write directly to it + */ + public void write(byte[] b, int o, int len) { + while (len > 0) { + int n = len <= availLen ? len : availLen; + System.arraycopy(b, o, buf, offset, n); + incrementOffset(n); + len -= n; + o += n; + } + } + + /** + * this will be called after reset + */ + protected void postReset() { + // fdat could override this (and minLenToWrite) to add a prefix + } + + protected int minLenToWrite() { + return 1; + } + + public void close() { + flush(); + offset = 0; + buf = null; + } + + /** + * You can write directly to this buffer, using {@link #getOffset()} and + * {@link #getAvailLen()}. You should call {@link #incrementOffset(int)} + * inmediately after. + */ + public byte[] getBuf() { + return buf; + } + + public long getTotalBytesWriten() { + return totalBytesWriten; + } + + public int getChunksWriten() { + return chunksWriten; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IdatSet.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IdatSet.java new file mode 100644 index 0000000..92d7973 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/IdatSet.java @@ -0,0 +1,256 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.Arrays; +import java.util.logging.Logger; +import java.util.zip.Checksum; +import java.util.zip.Inflater; + +/** + * This object process the concatenation of IDAT chunks. + *

      + * It extends {@link DeflatedChunksSet}, adding the intelligence to unfilter + * rows, and to understand row lenghts in terms of ImageInfo and (eventually) + * Deinterlacer + */ +public class IdatSet extends DeflatedChunksSet { + private static final Logger LOGGER = Logger.getLogger(IdatSet.class.getName()); + + protected byte[] rowUnfiltered; + protected byte[] rowUnfilteredPrev; + protected final ImageInfo imgInfo; // in the case of APNG this is the frame image + protected final Deinterlacer deinterlacer; + + final RowInfo rowinfo; // info for the last processed row, for debug + + protected int[] filterUseStat = new int[5]; // for stats + + /** + * @param id Chunk id (first chunk), should be shared by all concatenated + * chunks + * @param iminfo Image info + * @param deinterlacer Not null if interlaced + */ + public IdatSet(String id, boolean callbackMode, ImageInfo iminfo, Deinterlacer deinterlacer) { + this(id, callbackMode, iminfo, deinterlacer, null, null); + } + + /** + * Special constructor with preallocated buffer. + *

      + *

      + * Same as {@link #IdatSet(String, ImageInfo, Deinterlacer)}, but you can + * pass a Inflater (will be reset internally), and a buffer (will be used + * only if size is enough) + */ + public IdatSet(String id, boolean callbackMode, ImageInfo iminfo, Deinterlacer deinterlacer, Inflater inf, + byte[] buffer) { + super(id, callbackMode, deinterlacer != null ? deinterlacer.getBytesToRead() + 1 : iminfo.bytesPerRow + 1, + iminfo.bytesPerRow + 1, inf, buffer); + this.imgInfo = iminfo; + this.deinterlacer = deinterlacer; + this.rowinfo = new RowInfo(iminfo, deinterlacer); + LOGGER.fine("Creating IDAT set "); + } + + /** + * Applies PNG un-filter to inflated raw line. Result in + * {@link #getUnfilteredRow()} {@link #getRowLen()} + */ + public void unfilterRow() { + unfilterRow(rowinfo.bytesRow); + } + + // nbytes: NOT including the filter byte. leaves result in rowUnfiltered + protected void unfilterRow(int nbytes) { + if (rowUnfiltered == null || rowUnfiltered.length < row.length) { + rowUnfiltered = new byte[row.length]; + rowUnfilteredPrev = new byte[row.length]; + } + if (rowinfo.rowNsubImg == 0) { + Arrays.fill(rowUnfiltered, (byte) 0); // see swap that follows + } + // swap + byte[] tmp = rowUnfiltered; + rowUnfiltered = rowUnfilteredPrev; + rowUnfilteredPrev = tmp; + + int ftn = row[0]; + if (!FilterType.isValidStandard(ftn)) { + throw new PngjInputException("Filter type " + ftn + " invalid"); + } + FilterType ft = FilterType.getByVal(ftn); + filterUseStat[ftn]++; + rowUnfiltered[0] = row[0]; // we copy the filter type, can be useful + switch (ft) { + case FILTER_NONE: + unfilterRowNone(nbytes); + break; + case FILTER_SUB: + unfilterRowSub(nbytes); + break; + case FILTER_UP: + unfilterRowUp(nbytes); + break; + case FILTER_AVERAGE: + unfilterRowAverage(nbytes); + break; + case FILTER_PAETH: + unfilterRowPaeth(nbytes); + break; + default: + throw new PngjInputException("Filter type " + ftn + " not implemented"); + } + } + + private void unfilterRowAverage(final int nbytes) { + int i, j, x; + for (j = 1 - imgInfo.bytesPixel, i = 1; i <= nbytes; i++, j++) { + x = j > 0 ? (rowUnfiltered[j] & 0xff) : 0; + rowUnfiltered[i] = (byte) (row[i] + (x + (rowUnfilteredPrev[i] & 0xFF)) / 2); + } + } + + private void unfilterRowNone(final int nbytes) { + for (int i = 1; i <= nbytes; i++) { + rowUnfiltered[i] = row[i]; + } + } + + private void unfilterRowPaeth(final int nbytes) { + int i, j, x, y; + for (j = 1 - imgInfo.bytesPixel, i = 1; i <= nbytes; i++, j++) { + x = j > 0 ? (rowUnfiltered[j] & 0xFF) : 0; + y = j > 0 ? (rowUnfilteredPrev[j] & 0xFF) : 0; + rowUnfiltered[i] = (byte) (row[i] + + PngHelperInternal.filterPaethPredictor(x, rowUnfilteredPrev[i] & 0xFF, y)); + } + } + + private void unfilterRowSub(final int nbytes) { + int i, j; + for (i = 1; i <= imgInfo.bytesPixel; i++) { + rowUnfiltered[i] = row[i]; + } + for (j = 1, i = imgInfo.bytesPixel + 1; i <= nbytes; i++, j++) { + rowUnfiltered[i] = (byte) (row[i] + rowUnfiltered[j]); + } + } + + private void unfilterRowUp(final int nbytes) { + for (int i = 1; i <= nbytes; i++) { + rowUnfiltered[i] = (byte) (row[i] + rowUnfilteredPrev[i]); + } + } + + /** + * does the unfiltering of the inflated row, and updates row info + */ + @Override + protected void preProcessRow() { + super.preProcessRow(); + rowinfo.update(getRown()); + unfilterRow(); + rowinfo.updateBuf(rowUnfiltered, rowinfo.bytesRow + 1); + } + + /** + * Method for async/callback mode . + *

      + * In callback mode will be called as soon as each row is retrieved + * (inflated and unfiltered), after {@link #preProcessRow()} + *

      + * This is a dummy implementation (this normally should be overriden) that + * does nothing more than compute the length of next row. + *

      + * The return value is essential + *

      + * + * @return Length of next row, in bytes (including filter byte), + * non-positive if done + */ + @Override + protected int processRowCallback() { + int bytesNextRow = advanceToNextRow(); + return bytesNextRow; + } + + @Override + protected void processDoneCallback() { + super.processDoneCallback(); + } + + /** + * Signals that we are done with the previous row, begin reading the next + * one. + *

      + * In polled mode, calls setNextRowLen() + *

      + * Warning: after calling this, the unfilterRow is invalid! + * + * @return Returns nextRowLen + */ + public int advanceToNextRow() { + // PngHelperInternal.LOGGER.info("advanceToNextRow"); + int bytesNextRow; + if (deinterlacer == null) { + bytesNextRow = getRown() >= imgInfo.rows - 1 ? 0 : imgInfo.bytesPerRow + 1; + } else { + boolean more = deinterlacer.nextRow(); + bytesNextRow = more ? deinterlacer.getBytesToRead() + 1 : 0; + } + if (!callbackMode) { // in callback mode, setNextRowLen() is called internally + prepareForNextRow(bytesNextRow); + } + return bytesNextRow; + } + + public boolean isRowReady() { + return !isWaitingForMoreInput(); + + } + + /** + * Unfiltered row. + *

      + * This should be called only if {@link #isRowReady()} returns true. + *

      + * To get real length, use {@link #getRowLen()} + *

      + * + * @return Unfiltered row, includes filter byte + */ + public byte[] getUnfilteredRow() { + return rowUnfiltered; + } + + public Deinterlacer getDeinterlacer() { + return deinterlacer; + } + + void updateCrcs(Checksum... idatCrcs) { + for (Checksum idatCrca : idatCrcs) { + if (idatCrca != null)// just for testing + { + idatCrca.update(getUnfilteredRow(), 1, getRowFilled() - 1); + } + } + } + + @Override + public void close() { + super.close(); + rowUnfiltered = null;// not really necessary... + rowUnfilteredPrev = null; + } + + /** + * Only for debug/stats + * + * @return Array of 5 integers (sum equal numbers of rows) counting each + * filter use + */ + public int[] getFilterUseStat() { + return filterUseStat; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageInfo.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageInfo.java new file mode 100644 index 0000000..14ead0a --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageInfo.java @@ -0,0 +1,273 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.zip.Checksum; + +/** + * Simple immutable wrapper for basic image info. + * Some parameters are redundant, but the constructor receives an 'orthogonal' + * subset. + * ref: http://www.w3.org/TR/PNG/#11IHDR + */ +public class ImageInfo { + + /** + * Absolute allowed maximum value for rows and cols (2^24 ~16 million). + * (bytesPerRow must fit in a 32bit integer, though total amount of pixels + * not necessarily). + */ + public static final int MAX_COLS_ROW = 16777216; + + /** + * Cols= Image width, in pixels. + */ + public final int cols; + + /** + * Rows= Image height, in pixels + */ + public final int rows; + + /** + * Bits per sample (per channel) in the buffer (1-2-4-8-16). This is 8-16 + * for RGB/ARGB images, 1-2-4-8 for grayscale. For indexed images, number of + * bits per palette index (1-2-4-8) + */ + public final int bitDepth; + + /** + * Number of channels, as used internally: 3 for RGB, 4 for RGBA, 2 for GA + * (gray with alpha), 1 for grayscale or indexed. + */ + public final int channels; + + /** + * Flag: true if has alpha channel (RGBA/GA) + */ + public final boolean alpha; + + /** + * Flag: true if is grayscale (G/GA) + */ + public final boolean greyscale; + + /** + * Flag: true if image is indexed, i.e., it has a palette + */ + public final boolean indexed; + + /** + * Flag: true if image internally uses less than one byte per sample (bit + * depth 1-2-4) + */ + public final boolean packed; + + /** + * Bits used for each pixel in the buffer: channel * bitDepth + */ + public final int bitspPixel; + + /** + * rounded up value: this is only used internally for filter + */ + public final int bytesPixel; + + /** + * ceil(bitspp*cols/8) - does not include filter + */ + public final int bytesPerRow; + + /** + * Equals cols * channels + */ + public final int samplesPerRow; + + /** + * Amount of "packed samples" : when several samples are stored in a single + * byte (bitdepth 1,2 4) they are counted as one "packed sample". This is + * less that samplesPerRow only when bitdepth is 1-2-4 (flag packed = true) + *

      + * This equals the number of elements in the scanline array if working with + * packedMode=true + *

      + * For internal use, client code should rarely access this. + */ + public final int samplesPerRowPacked; + + private long totalPixels = -1; // lazy getter + + private long totalRawBytes = -1; // lazy getter + + /** + * Short constructor: assumes truecolor (RGB/RGBA) + */ + public ImageInfo(int cols, int rows, int bitdepth, boolean alpha) { + this(cols, rows, bitdepth, alpha, false, false); + } + + /** + * Full constructor + * + * @param cols Width in pixels + * @param rows Height in pixels + * @param bitdepth Bits per sample, in the buffer : 8-16 for RGB true color and + * greyscale + * @param alpha Flag: has an alpha channel (RGBA or GA) + * @param grayscale Flag: is gray scale (any bitdepth, with or without alpha) + * @param indexed Flag: has palette + */ + public ImageInfo(int cols, int rows, int bitdepth, boolean alpha, boolean grayscale, boolean indexed) { + this.cols = cols; + this.rows = rows; + this.alpha = alpha; + this.indexed = indexed; + this.greyscale = grayscale; + if (greyscale && indexed) { + throw new PngjException("palette and greyscale are mutually exclusive"); + } + this.channels = (grayscale || indexed) ? (alpha ? 2 : 1) : (alpha ? 4 : 3); + // http://www.w3.org/TR/PNG/#11IHDR + this.bitDepth = bitdepth; + this.packed = bitdepth < 8; + this.bitspPixel = (channels * this.bitDepth); + this.bytesPixel = (bitspPixel + 7) / 8; + this.bytesPerRow = (bitspPixel * cols + 7) / 8; + this.samplesPerRow = channels * this.cols; + this.samplesPerRowPacked = packed ? bytesPerRow : samplesPerRow; + // several checks + switch (this.bitDepth) { + case 1: + case 2: + case 4: + if (!(this.indexed || this.greyscale)) { + throw new PngjException("only indexed or grayscale can have bitdepth=" + this.bitDepth); + } + break; + case 8: + break; + case 16: + if (this.indexed) { + throw new PngjException("indexed can't have bitdepth=" + this.bitDepth); + } + break; + default: + throw new PngjException("invalid bitdepth=" + this.bitDepth); + } + if (cols < 1 || cols > MAX_COLS_ROW) { + throw new PngjException("invalid cols=" + cols + " ???"); + } + if (rows < 1 || rows > MAX_COLS_ROW) { + throw new PngjException("invalid rows=" + rows + " ???"); + } + if (samplesPerRow < 1) { + throw new PngjException("invalid image parameters (overflow?)"); + } + } + + /** + * returns a copy with different size + * + * @param cols if non-positive, the original is used + * @param rows if non-positive, the original is used + * @return a new copy with the specified size and same properties + */ + public ImageInfo withSize(int cols, int rows) { + return new ImageInfo(cols > 0 ? cols : this.cols, rows > 0 ? rows : this.rows, this.bitDepth, this.alpha, + this.greyscale, this.indexed); + } + + public long getTotalPixels() { + if (totalPixels < 0) { + totalPixels = cols * (long) rows; + } + return totalPixels; + } + + /** + * Total uncompressed bytes in IDAT, including filter byte. This is not + * valid for interlaced. + */ + public long getTotalRawBytes() { + if (totalRawBytes < 0) { + totalRawBytes = (bytesPerRow + 1) * (long) rows; + } + return totalRawBytes; + } + + @Override + public String toString() { + return "ImageInfo [cols=" + cols + ", rows=" + rows + ", bitDepth=" + bitDepth + ", channels=" + channels + + ", alpha=" + alpha + ", greyscale=" + greyscale + ", indexed=" + indexed + "]"; + } + + /** + * Brief info: COLSxROWS[dBITDEPTH][a][p][g] ( the default dBITDEPTH='d8' is + * ommited) + **/ + public String toStringBrief() { + return cols + "x" + rows + (bitDepth != 8 ? ("d" + bitDepth) : "") + (alpha ? "a" : "") + + (indexed ? "p" : "") + (greyscale ? "g" : ""); + } + + public String toStringDetail() { + return "ImageInfo [cols=" + cols + ", rows=" + rows + ", bitDepth=" + bitDepth + ", channels=" + channels + + ", bitspPixel=" + bitspPixel + ", bytesPixel=" + bytesPixel + ", bytesPerRow=" + bytesPerRow + + ", samplesPerRow=" + samplesPerRow + ", samplesPerRowP=" + samplesPerRowPacked + ", alpha=" + alpha + + ", greyscale=" + greyscale + ", indexed=" + indexed + ", packed=" + packed + "]"; + } + + void updateCrc(Checksum crc) { + crc.update((byte) rows); + crc.update((byte) (rows >> 8)); + crc.update((byte) (rows >> 16)); + crc.update((byte) cols); + crc.update((byte) (cols >> 8)); + crc.update((byte) (cols >> 16)); + crc.update((byte) (bitDepth)); + crc.update((byte) (indexed ? 1 : 2)); + crc.update((byte) (greyscale ? 3 : 4)); + crc.update((byte) (alpha ? 3 : 4)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (alpha ? 1231 : 1237); + result = prime * result + bitDepth; + result = prime * result + cols; + result = prime * result + (greyscale ? 1231 : 1237); + result = prime * result + (indexed ? 1231 : 1237); + result = prime * result + rows; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ImageInfo other = (ImageInfo) obj; + if (alpha != other.alpha) { + return false; + } + if (bitDepth != other.bitDepth) { + return false; + } + if (cols != other.cols) { + return false; + } + if (greyscale != other.greyscale) { + return false; + } + if (indexed != other.indexed) { + return false; + } + return rows == other.rows; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineByte.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineByte.java new file mode 100644 index 0000000..b5e04c9 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineByte.java @@ -0,0 +1,190 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Lightweight wrapper for an image scanline, used for read and write. + *

      + * This object can be (usually it is) reused while iterating over the image + * lines. + *

      + * See scanline field, to understand the format. + *

      + * Format: byte (one bytes per sample) (for 16bpp the extra byte is placed in an + * extra array) + */ +public class ImageLineByte implements IImageLine, IImageLineArray { + public final ImageInfo imgInfo; + + final byte[] scanline; + final byte[] scanline2; // only used for 16 bpp (less significant byte) Normally you'd prefer + // ImageLineInt in this case + + protected FilterType filterType; // informational ; only filled by the reader. not significant for + // interlaced + final int size; // = imgInfo.samplePerRowPacked, if packed:imgInfo.samplePerRow elswhere + + public ImageLineByte(ImageInfo imgInfo) { + this(imgInfo, null); + } + + public ImageLineByte(ImageInfo imgInfo, byte[] sci) { + this.imgInfo = imgInfo; + filterType = FilterType.FILTER_UNKNOWN; + size = imgInfo.samplesPerRow; + scanline = sci != null && sci.length >= size ? sci : new byte[size]; + scanline2 = imgInfo.bitDepth == 16 ? new byte[size] : null; + } + + /** + * Returns a factory for this object + */ + public static IImageLineFactory getFactory() { + return new IImageLineFactory() { + public ImageLineByte createImageLine(ImageInfo iminfo) { + return new ImageLineByte(iminfo); + } + }; + } + + public FilterType getFilterUsed() { + return filterType; + } + + /** + * One byte per sample. This can be used also for 16bpp images, but in this + * case this loses the less significant 8-bits ; see also getScanlineByte2 + * and getElem. + */ + public byte[] getScanlineByte() { + return scanline; + } + + /** + * only for 16bpp (less significant byte) + * + * @return null for less than 16bpp + */ + public byte[] getScanlineByte2() { + return scanline2; + } + + /** + * Basic info + */ + public String toString() { + return " cols=" + imgInfo.cols + " bpc=" + imgInfo.bitDepth + " size=" + scanline.length; + } + + public void readFromPngRaw(byte[] raw, final int len, final int offset, final int step) { + filterType = FilterType.getByVal(raw[0]); // only for non interlaced line the filter is significative + int len1 = len - 1; + int step1 = (step - 1) * imgInfo.channels; + if (imgInfo.bitDepth == 8) { + if (step == 1) {// 8bispp non-interlaced: most important case, should be optimized + System.arraycopy(raw, 1, scanline, 0, len1); + } else {// 8bispp interlaced + for (int s = 1, c = 0, i = offset * imgInfo.channels; s <= len1; s++, i++) { + scanline[i] = raw[s]; + c++; + if (c == imgInfo.channels) { + c = 0; + i += step1; + } + } + } + } else if (imgInfo.bitDepth == 16) { + if (step == 1) {// 16bispp non-interlaced + for (int i = 0, s = 1; i < imgInfo.samplesPerRow; i++) { + scanline[i] = raw[s++]; // get the first byte + scanline2[i] = raw[s++]; // get the first byte + } + } else { + for (int s = 1, c = 0, i = offset != 0 ? offset * imgInfo.channels : 0; s <= len1; i++) { + scanline[i] = raw[s++]; + scanline2[i] = raw[s++]; + c++; + if (c == imgInfo.channels) { + c = 0; + i += step1; + } + } + } + } else { // packed formats + int mask0, mask, shi, bd; + bd = imgInfo.bitDepth; + mask0 = ImageLineHelper.getMaskForPackedFormats(bd); + for (int i = offset * imgInfo.channels, r = 1, c = 0; r < len; r++) { + mask = mask0; + shi = 8 - bd; + do { + scanline[i] = (byte) ((raw[r] & mask) >> shi); + mask >>= bd; + shi -= bd; + i++; + c++; + if (c == imgInfo.channels) { + c = 0; + i += step1; + } + } while (mask != 0 && i < size); + } + } + } + + public void writeToPngRaw(byte[] raw) { + raw[0] = (byte) filterType.val; + if (imgInfo.bitDepth == 8) { + System.arraycopy(scanline, 0, raw, 1, size); + } else if (imgInfo.bitDepth == 16) { + for (int i = 0, s = 1; i < size; i++) { + raw[s++] = scanline[i]; + raw[s++] = scanline2[i]; + } + } else { // packed formats + int shi, bd, v; + bd = imgInfo.bitDepth; + shi = 8 - bd; + v = 0; + for (int i = 0, r = 1; i < size; i++) { + v |= (scanline[i] << shi); + shi -= bd; + if (shi < 0 || i == size - 1) { + raw[r++] = (byte) v; + shi = 8 - bd; + v = 0; + } + } + } + } + + public void endReadFromPngRaw() { + } + + public int getSize() { + return size; + } + + public int getElem(int i) { + return scanline2 == null ? scanline[i] & 0xFF : ((scanline[i] & 0xFF) << 8) | (scanline2[i] & 0xFF); + } + + public byte[] getScanline() { + return scanline; + } + + public ImageInfo getImageInfo() { + return imgInfo; + } + + public FilterType getFilterType() { + return filterType; + } + + /** + * This should rarely be used by client code. Only relevant if + * FilterPreserve==true + */ + public void setFilterType(FilterType ft) { + filterType = ft; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineHelper.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineHelper.java new file mode 100644 index 0000000..76f3455 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineHelper.java @@ -0,0 +1,515 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.Arrays; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkPLTE; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkTRNS; + +/** + * Bunch of utility static methods to proces an image line at the pixel level. + *

      + * WARNING: this has little testing/optimizing, and this API is not stable. some + * methods will probably be changed or removed if future releases. + *

      + * WARNING: most methods for getting/setting values work currently only for + * ImageLine or ImageLineByte + */ +public class ImageLineHelper { + + static int[] DEPTH_UNPACK_1; + static int[] DEPTH_UNPACK_2; + static int[] DEPTH_UNPACK_4; + static int[][] DEPTH_UNPACK; + + static { + initDepthScale(); + } + + private static void initDepthScale() { + DEPTH_UNPACK_1 = new int[2]; + for (int i = 0; i < 2; i++) { + DEPTH_UNPACK_1[i] = i * 255; + } + DEPTH_UNPACK_2 = new int[4]; + for (int i = 0; i < 4; i++) { + DEPTH_UNPACK_2[i] = (i * 255) / 3; + } + DEPTH_UNPACK_4 = new int[16]; + for (int i = 0; i < 16; i++) { + DEPTH_UNPACK_4[i] = (i * 255) / 15; + } + DEPTH_UNPACK = new int[][]{null, DEPTH_UNPACK_1, DEPTH_UNPACK_2, null, DEPTH_UNPACK_4}; + } + + /** + * When the bitdepth is less than 8, the imageLine is usually + * returned/expected unscaled. This method upscales it in place. Eg, if + * bitdepth=1, values 0-1 will be converted to 0-255 + */ + public static void scaleUp(IImageLineArray line) { + if (line.getImageInfo().indexed || line.getImageInfo().bitDepth >= 8) { + return; + } + final int[] scaleArray = DEPTH_UNPACK[line.getImageInfo().bitDepth]; + if (line instanceof ImageLineInt) { + ImageLineInt iline = (ImageLineInt) line; + for (int i = 0; i < iline.getSize(); i++) { + iline.scanline[i] = scaleArray[iline.scanline[i]]; + } + } else if (line instanceof ImageLineByte) { + ImageLineByte iline = (ImageLineByte) line; + for (int i = 0; i < iline.getSize(); i++) { + iline.scanline[i] = (byte) scaleArray[iline.scanline[i]]; + } + } else { + throw new PngjException("not implemented"); + } + } + + /** + * Reverse of {@link #scaleUp(IImageLineArray)} + */ + public static void scaleDown(IImageLineArray line) { + if (line.getImageInfo().indexed || line.getImageInfo().bitDepth >= 8) { + return; + } + if (line instanceof ImageLineInt) { + final int scalefactor = 8 - line.getImageInfo().bitDepth; + if (line instanceof ImageLineInt) { + ImageLineInt iline = (ImageLineInt) line; + for (int i = 0; i < line.getSize(); i++) { + iline.scanline[i] = iline.scanline[i] >> scalefactor; + } + } else if (line instanceof ImageLineByte) { + ImageLineByte iline = (ImageLineByte) line; + for (int i = 0; i < line.getSize(); i++) { + iline.scanline[i] = (byte) ((iline.scanline[i] & 0xFF) >> scalefactor); + } + } + } else { + throw new PngjException("not implemented"); + } + } + + public static byte scaleUp(int bitdepth, byte v) { + return bitdepth < 8 ? (byte) DEPTH_UNPACK[bitdepth][v] : v; + } + + public static byte scaleDown(int bitdepth, byte v) { + return bitdepth < 8 ? (byte) (v >> (8 - bitdepth)) : v; + } + + /** + * Given an indexed line with a palette, unpacks as a RGB array, or RGBA if + * a non nul PngChunkTRNS chunk is passed + * + * @param line ImageLine as returned from PngReader + * @param pal Palette chunk + * @param trns Transparency chunk, can be null (absent) + * @param buf Preallocated array, optional + * @return R G B (A), one sample 0-255 per array element. Ready for + * pngw.writeRowInt() + */ + public static int[] palette2rgb(ImageLineInt line, PngChunkPLTE pal, PngChunkTRNS trns, int[] buf) { + return palette2rgb(line, pal, trns, buf, false); + } + + /** + * Warning: the line should be upscaled, see + * {@link #scaleUp(IImageLineArray)} + */ + static int[] lineToARGB32(ImageLineByte line, PngChunkPLTE pal, PngChunkTRNS trns, int[] buf) { + boolean alphachannel = line.imgInfo.alpha; + int cols = line.getImageInfo().cols; + if (buf == null || buf.length < cols) { + buf = new int[cols]; + } + int index, rgb, alpha, ga, g; + if (line.getImageInfo().indexed) {// palette + int nindexesWithAlpha = trns != null ? trns.getPalletteAlpha().length : 0; + for (int c = 0; c < cols; c++) { + index = line.scanline[c] & 0xFF; + rgb = pal.getEntry(index); + alpha = index < nindexesWithAlpha ? trns.getPalletteAlpha()[index] : 255; + buf[c] = (alpha << 24) | rgb; + } + } else if (line.imgInfo.greyscale) { // gray + ga = trns != null ? trns.getGray() : -1; + for (int c = 0, c2 = 0; c < cols; c++) { + g = (line.scanline[c2++] & 0xFF); + alpha = alphachannel ? line.scanline[c2++] & 0xFF : (g != ga ? 255 : 0); + buf[c] = (alpha << 24) | g | (g << 8) | (g << 16); + } + } else { // true color + ga = trns != null ? trns.getRGB888() : -1; + for (int c = 0, c2 = 0; c < cols; c++) { + rgb = ((line.scanline[c2++] & 0xFF) << 16) | ((line.scanline[c2++] & 0xFF) << 8) + | (line.scanline[c2++] & 0xFF); + alpha = alphachannel ? line.scanline[c2++] & 0xFF : (rgb != ga ? 255 : 0); + buf[c] = (alpha << 24) | rgb; + } + } + return buf; + } + + /** + * Warning: the line should be upscaled, see + * {@link #scaleUp(IImageLineArray)} + */ + static byte[] lineToRGBA8888(ImageLineByte line, PngChunkPLTE pal, PngChunkTRNS trns, byte[] buf) { + boolean alphachannel = line.imgInfo.alpha; + int cols = line.imgInfo.cols; + int bytes = cols * 4; + if (buf == null || buf.length < bytes) { + buf = new byte[bytes]; + } + int index, rgb, ga; + byte val; + if (line.imgInfo.indexed) {// palette + int nindexesWithAlpha = trns != null ? trns.getPalletteAlpha().length : 0; + for (int c = 0, b = 0; c < cols; c++) { + index = line.scanline[c] & 0xFF; + rgb = pal.getEntry(index); + buf[b++] = (byte) ((rgb >> 16) & 0xFF); + buf[b++] = (byte) ((rgb >> 8) & 0xFF); + buf[b++] = (byte) (rgb & 0xFF); + buf[b++] = (byte) (index < nindexesWithAlpha ? trns.getPalletteAlpha()[index] : 255); + } + } else if (line.imgInfo.greyscale) { // + ga = trns != null ? trns.getGray() : -1; + for (int c = 0, b = 0; b < bytes; ) { + val = line.scanline[c++]; + buf[b++] = val; + buf[b++] = val; + buf[b++] = val; + buf[b++] = alphachannel ? line.scanline[c++] : ((val & 0xFF) == ga) ? (byte) 0 : (byte) 255; + } + } else { // true color + if (alphachannel) // same format! + { + System.arraycopy(line.scanline, 0, buf, 0, bytes); + } else { + for (int c = 0, b = 0; b < bytes; ) { + buf[b++] = line.scanline[c++]; + buf[b++] = line.scanline[c++]; + buf[b++] = line.scanline[c++]; + buf[b++] = (byte) (255); // tentative (probable) + if (trns != null && buf[b - 3] == (byte) trns.getRGB()[0] && buf[b - 2] == (byte) trns.getRGB()[1] + && buf[b - 1] == (byte) trns.getRGB()[2]) // not + // very + // efficient, + // but + // not + // frecuent + { + buf[b - 1] = 0; + } + } + } + } + return buf; + } + + static byte[] lineToRGB888(ImageLineByte line, PngChunkPLTE pal, byte[] buf) { + boolean alphachannel = line.imgInfo.alpha; + int cols = line.imgInfo.cols; + int bytes = cols * 3; + if (buf == null || buf.length < bytes) { + buf = new byte[bytes]; + } + byte val; + int[] rgb = new int[3]; + if (line.imgInfo.indexed) {// palette + for (int c = 0, b = 0; c < cols; c++) { + pal.getEntryRgb(line.scanline[c] & 0xFF, rgb); + buf[b++] = (byte) rgb[0]; + buf[b++] = (byte) rgb[1]; + buf[b++] = (byte) rgb[2]; + } + } else if (line.imgInfo.greyscale) { // + for (int c = 0, b = 0; b < bytes; ) { + val = line.scanline[c++]; + buf[b++] = val; + buf[b++] = val; + buf[b++] = val; + if (alphachannel) { + c++; // skip alpha + } + } + } else { // true color + if (!alphachannel) // same format! + { + System.arraycopy(line.scanline, 0, buf, 0, bytes); + } else { + for (int c = 0, b = 0; b < bytes; ) { + buf[b++] = line.scanline[c++]; + buf[b++] = line.scanline[c++]; + buf[b++] = line.scanline[c++]; + c++;// skip alpha + } + } + } + return buf; + } + + /** + * Same as palette2rgbx , but returns rgba always, even if trns is null + * + * @param line ImageLine as returned from PngReader + * @param pal Palette chunk + * @param trns Transparency chunk, can be null (absent) + * @param buf Preallocated array, optional + * @return R G B (A), one sample 0-255 per array element. Ready for + * pngw.writeRowInt() + */ + public static int[] palette2rgba(ImageLineInt line, PngChunkPLTE pal, PngChunkTRNS trns, int[] buf) { + return palette2rgb(line, pal, trns, buf, true); + } + + public static int[] palette2rgb(ImageLineInt line, PngChunkPLTE pal, int[] buf) { + return palette2rgb(line, pal, null, buf, false); + } + + /** + * this is not very efficient, only for tests and troubleshooting + */ + public static int[] convert2rgba(IImageLineArray line, PngChunkPLTE pal, PngChunkTRNS trns, int[] buf) { + ImageInfo imi = line.getImageInfo(); + int nsamples = imi.cols * 4; + if (buf == null || buf.length < nsamples) { + buf = new int[nsamples]; + } + int maxval = imi.bitDepth == 16 ? (1 << 16) - 1 : 255; + Arrays.fill(buf, maxval); + + if (imi.indexed) { + int tlen = trns != null ? trns.getPalletteAlpha().length : 0; + for (int s = 0; s < imi.cols; s++) { + int index = line.getElem(s); + pal.getEntryRgb(index, buf, s * 4); + if (index < tlen) { + buf[s * 4 + 3] = trns.getPalletteAlpha()[index]; + } + } + } else if (imi.greyscale) { + int[] unpack = null; + if (imi.bitDepth < 8) { + unpack = ImageLineHelper.DEPTH_UNPACK[imi.bitDepth]; + } + for (int s = 0, i = 0, p = 0; p < imi.cols; p++) { + buf[s++] = unpack != null ? unpack[line.getElem(i++)] : line.getElem(i++); + buf[s] = buf[s - 1]; + s++; + buf[s] = buf[s - 1]; + s++; + if (imi.channels == 2) { + buf[s++] = unpack != null ? unpack[line.getElem(i++)] : line.getElem(i++); + } else { + buf[s++] = maxval; + } + } + } else { + for (int s = 0, i = 0, p = 0; p < imi.cols; p++) { + buf[s++] = line.getElem(i++); + buf[s++] = line.getElem(i++); + buf[s++] = line.getElem(i++); + buf[s++] = imi.alpha ? line.getElem(i++) : maxval; + } + } + return buf; + } + + private static int[] palette2rgb(IImageLine line, PngChunkPLTE pal, PngChunkTRNS trns, int[] buf, + boolean alphaForced) { + boolean isalpha = trns != null; + int channels = isalpha ? 4 : 3; + ImageLineInt linei = (ImageLineInt) (line instanceof ImageLineInt ? line : null); + ImageLineByte lineb = (ImageLineByte) (line instanceof ImageLineByte ? line : null); + boolean isbyte = lineb != null; + int cols = linei != null ? linei.imgInfo.cols : lineb.imgInfo.cols; + int nsamples = cols * channels; + if (buf == null || buf.length < nsamples) { + buf = new int[nsamples]; + } + int nindexesWithAlpha = trns != null ? trns.getPalletteAlpha().length : 0; + for (int c = 0; c < cols; c++) { + int index = isbyte ? (lineb.scanline[c] & 0xFF) : linei.scanline[c]; + pal.getEntryRgb(index, buf, c * channels); + if (isalpha) { + int alpha = index < nindexesWithAlpha ? trns.getPalletteAlpha()[index] : 255; + buf[c * channels + 3] = alpha; + } + } + return buf; + } + + /** + * what follows is pretty uninteresting/untested/obsolete, subject to change + */ + /** + * Just for basic info or debugging. Shows values for first and last pixel. + * Does not include alpha + */ + public static String infoFirstLastPixels(ImageLineInt line) { + return line.imgInfo.channels == 1 + ? String.format("first=(%d) last=(%d)", line.scanline[0], line.scanline[line.scanline.length - 1]) + : String.format("first=(%d %d %d) last=(%d %d %d)", line.scanline[0], line.scanline[1], + line.scanline[2], line.scanline[line.scanline.length - line.imgInfo.channels], + line.scanline[line.scanline.length - line.imgInfo.channels + 1], + line.scanline[line.scanline.length - line.imgInfo.channels + 2]); + } + + /** + * Returns pixel as integer packed [A R G B] + *

      + * This only makes sense for ARGB images, with bitdepth=8 (this is not + * checked!) + **/ + public static int getPixelARGB8(IImageLine line, int column) { + if (line instanceof ImageLineInt) { + if (((ImageLineInt) line).imgInfo.channels != 4) { + return getPixelRGB8(line, column); + } + int offset = column * ((ImageLineInt) line).imgInfo.channels; + int[] scanline = ((ImageLineInt) line).getScanline(); + return (scanline[offset + 3] << 24) | (scanline[offset] << 16) | (scanline[offset + 1] << 8) + | (scanline[offset + 2]); + } else if (line instanceof ImageLineByte) { + if (((ImageLineByte) line).imgInfo.channels != 4) { + return getPixelRGB8(line, column); + } + int offset = column * ((ImageLineByte) line).imgInfo.channels; + byte[] scanline = ((ImageLineByte) line).getScanline(); + return (((scanline[offset + 3] & 0xff) << 24) | ((scanline[offset] & 0xff) << 16) + | ((scanline[offset + 1] & 0xff) << 8) | ((scanline[offset + 2] & 0xff))); + } else { + throw new PngjUnsupportedException("Not supported " + line.getClass()); + } + } + + /** + * Returns pixel as integer packed [(A) R G B] with A=0xff + *

      + * This only makes sense for RGB images, with bitdepth=8 (this is not + * checked!) + **/ + public static int getPixelRGB8(IImageLine line, int column) { + if (line instanceof ImageLineInt) { + int offset = column * ((ImageLineInt) line).imgInfo.channels; + int[] scanline = ((ImageLineInt) line).getScanline(); + return (0xff << 24) | (scanline[offset] << 16) | (scanline[offset + 1] << 8) | (scanline[offset + 2]); + } else if (line instanceof ImageLineByte) { + int offset = column * ((ImageLineByte) line).imgInfo.channels; + byte[] scanline = ((ImageLineByte) line).getScanline(); + return (0xff << 24) | ((scanline[offset] & 0xff) << 16) | ((scanline[offset + 1] & 0xff) << 8) + | ((scanline[offset + 2] & 0xff)); + } else { + throw new PngjUnsupportedException("Not supported " + line.getClass()); + } + } + + public static void setPixelsRGB8(ImageLineInt line, int[] rgb) { + for (int i = 0, j = 0; i < line.imgInfo.cols; i++) { + line.scanline[j++] = ((rgb[i] >> 16) & 0xFF); + line.scanline[j++] = ((rgb[i] >> 8) & 0xFF); + line.scanline[j++] = ((rgb[i] & 0xFF)); + } + } + + public static void setPixelRGB8(ImageLineInt line, int col, int r, int g, int b) { + col *= line.imgInfo.channels; + line.scanline[col++] = r; + line.scanline[col++] = g; + line.scanline[col] = b; + } + + public static void setPixelRGB8(ImageLineInt line, int col, int rgb) { + setPixelRGB8(line, col, (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF); + } + + public static void setPixelsRGBA8(ImageLineInt line, int[] rgb) { + for (int i = 0, j = 0; i < line.imgInfo.cols; i++) { + line.scanline[j++] = ((rgb[i] >> 16) & 0xFF); + line.scanline[j++] = ((rgb[i] >> 8) & 0xFF); + line.scanline[j++] = ((rgb[i] & 0xFF)); + line.scanline[j++] = ((rgb[i] >> 24) & 0xFF); + } + } + + public static void setPixelRGBA8(ImageLineInt line, int col, int r, int g, int b, int a) { + col *= line.imgInfo.channels; + line.scanline[col++] = r; + line.scanline[col++] = g; + line.scanline[col++] = b; + line.scanline[col] = a; + } + + public static void setPixelRGBA8(ImageLineInt line, int col, int rgb) { + setPixelRGBA8(line, col, (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, (rgb >> 24) & 0xFF); + } + + public static void setValD(ImageLineInt line, int i, double d) { + line.scanline[i] = double2int(line, d); + } + + public static int interpol(int a, int b, int c, int d, double dx, double dy) { + // a b -> x (0-1) + // c d + double e = a * (1.0 - dx) + b * dx; + double f = c * (1.0 - dx) + d * dx; + return (int) (e * (1 - dy) + f * dy + 0.5); + } + + public static double int2double(ImageLineInt line, int p) { + return line.imgInfo.bitDepth == 16 ? p / 65535.0 : p / 255.0; + // TODO: replace my multiplication? check for other bitdepths + } + + public static double int2doubleClamped(ImageLineInt line, int p) { + // TODO: replace my multiplication? + double d = line.imgInfo.bitDepth == 16 ? p / 65535.0 : p / 255.0; + return d <= 0.0 ? 0 : (d >= 1.0 ? 1.0 : d); + } + + public static int double2int(ImageLineInt line, double d) { + d = d <= 0.0 ? 0 : (d >= 1.0 ? 1.0 : d); + return line.imgInfo.bitDepth == 16 ? (int) (d * 65535.0 + 0.5) : (int) (d * 255.0 + 0.5); // + } + + public static int double2intClamped(ImageLineInt line, double d) { + d = d <= 0.0 ? 0 : (d >= 1.0 ? 1.0 : d); + return line.imgInfo.bitDepth == 16 ? (int) (d * 65535.0 + 0.5) : (int) (d * 255.0 + 0.5); // + } + + public static int clampTo_0_255(int i) { + return i > 255 ? 255 : (i < 0 ? 0 : i); + } + + public static int clampTo_0_65535(int i) { + return i > 65535 ? 65535 : (i < 0 ? 0 : i); + } + + public static int clampTo_128_127(int x) { + return x > 127 ? 127 : (x < -128 ? -128 : x); + } + + public static int getMaskForPackedFormats(int bitDepth) { // Utility function for pack/unpack + if (bitDepth == 4) { + return 0xf0; + } else if (bitDepth == 2) { + return 0xc0; + } else { + return 0x80; // bitDepth == 1 + } + } + + public static int getMaskForPackedFormatsLs(int bitDepth) { // Utility function for pack/unpack + if (bitDepth == 4) { + return 0x0f; + } else if (bitDepth == 2) { + return 0x03; + } else { + return 0x01; // bitDepth == 1 + } + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineInt.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineInt.java new file mode 100644 index 0000000..87da68f --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineInt.java @@ -0,0 +1,199 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Represents an image line, integer format (one integer by sample). See + * {@link #scanline} to understand the format. + */ +public class ImageLineInt implements IImageLine, IImageLineArray { + public final ImageInfo imgInfo; + + /** + * The 'scanline' is an array of integers, corresponds to an image line + * (row). + *

      + * Each int is a "sample" (one for channel), (0-255 or 0-65535) + * in the corresponding PNG sequence: R G B R G B... or + * R G B A R G B A... or g g g ... or + * i i i (palette index) + *

      + * For bitdepth=1/2/4 the value is not scaled (hence, eg, if bitdepth=2 the + * range will be 0-4) + *

      + * To convert a indexed line to RGB values, see + * {@link ImageLineHelper#palette2rgb(ImageLineInt, org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkPLTE, int[])} + * (you can't do the reverse) + */ + protected final int[] scanline; + + /** + * number of elements in the scanline + */ + protected final int size; + + /** + * informational ; only filled by the reader. not meaningful for interlaced + */ + protected FilterType filterType = FilterType.FILTER_UNKNOWN; + + /** + * @param imgInfo Inmutable ImageInfo, basic parameters of the image we are + * reading or writing + */ + public ImageLineInt(ImageInfo imgInfo) { + this(imgInfo, null); + } + + /** + * @param imgInfo Inmutable ImageInfo, basic parameters of the image we are + * reading or writing + * @param sci prealocated buffer (can be null) + */ + public ImageLineInt(ImageInfo imgInfo, int[] sci) { + this.imgInfo = imgInfo; + filterType = FilterType.FILTER_UNKNOWN; + size = imgInfo.samplesPerRow; + scanline = sci != null && sci.length >= size ? sci : new int[size]; + } + + /** + * Helper method, returns a default factory for this object + */ + public static IImageLineFactory getFactory() { + return new IImageLineFactory() { + public ImageLineInt createImageLine(ImageInfo iminfo) { + return new ImageLineInt(iminfo); + } + }; + } + + public FilterType getFilterType() { + return filterType; + } + + /** + * This should rarely be used by client code. Only relevant if + * FilterPreserve==true + */ + public void setFilterType(FilterType ft) { + filterType = ft; + } + + /** + * Basic info + */ + public String toString() { + return " cols=" + imgInfo.cols + " bpc=" + imgInfo.bitDepth + " size=" + scanline.length; + } + + public void readFromPngRaw(byte[] raw, final int len, final int offset, final int step) { + setFilterType(FilterType.getByVal(raw[0])); + int len1 = len - 1; + int step1 = (step - 1) * imgInfo.channels; + if (imgInfo.bitDepth == 8) { + if (step == 1) {// 8bispp non-interlaced: most important case, should be optimized + for (int i = 0; i < size; i++) { + scanline[i] = (raw[i + 1] & 0xff); + } + } else {// 8bispp interlaced + for (int s = 1, c = 0, i = offset * imgInfo.channels; s <= len1; s++, i++) { + scanline[i] = (raw[s] & 0xff); + c++; + if (c == imgInfo.channels) { + c = 0; + i += step1; + } + } + } + } else if (imgInfo.bitDepth == 16) { + if (step == 1) {// 16bispp non-interlaced + for (int i = 0, s = 1; i < size; i++) { + scanline[i] = ((raw[s++] & 0xFF) << 8) | (raw[s++] & 0xFF); // 16 bitspc + } + } else { + for (int s = 1, c = 0, i = offset != 0 ? offset * imgInfo.channels : 0; s <= len1; s++, i++) { + scanline[i] = ((raw[s++] & 0xFF) << 8) | (raw[s] & 0xFF); // 16 bitspc + c++; + if (c == imgInfo.channels) { + c = 0; + i += step1; + } + } + } + } else { // packed formats + int mask0, mask, shi, bd; + bd = imgInfo.bitDepth; + mask0 = ImageLineHelper.getMaskForPackedFormats(bd); + for (int i = offset * imgInfo.channels, r = 1, c = 0; r < len; r++) { + mask = mask0; + shi = 8 - bd; + do { + scanline[i++] = (raw[r] & mask) >> shi; + mask >>= bd; + shi -= bd; + c++; + if (c == imgInfo.channels) { + c = 0; + i += step1; + } + } while (mask != 0 && i < size); + } + } + } + + public void writeToPngRaw(byte[] raw) { + raw[0] = (byte) filterType.val; + if (imgInfo.bitDepth == 8) { + for (int i = 0; i < size; i++) { + raw[i + 1] = (byte) scanline[i]; + } + } else if (imgInfo.bitDepth == 16) { + for (int i = 0, s = 1; i < size; i++) { + raw[s++] = (byte) (scanline[i] >> 8); + raw[s++] = (byte) (scanline[i] & 0xff); + } + } else { // packed formats + int shi, bd, v; + bd = imgInfo.bitDepth; + shi = 8 - bd; + v = 0; + for (int i = 0, r = 1; i < size; i++) { + v |= (scanline[i] << shi); + shi -= bd; + if (shi < 0 || i == size - 1) { + raw[r++] = (byte) v; + shi = 8 - bd; + v = 0; + } + } + } + } + + /** + * Does nothing in this implementation + */ + public void endReadFromPngRaw() { + + } + + /** + * @see #size + */ + public int getSize() { + return size; + } + + public int getElem(int i) { + return scanline[i]; + } + + /** + * @return see {@link #scanline} + */ + public int[] getScanline() { + return scanline; + } + + public ImageInfo getImageInfo() { + return imgInfo; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineSetDefault.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineSetDefault.java new file mode 100644 index 0000000..ba2e109 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/ImageLineSetDefault.java @@ -0,0 +1,167 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.util.ArrayList; +import java.util.List; + +/** + * Default implementation of {@link IImageLineSet}. + *

      + * Supports all modes: single cursor, full rows, or partial. This should not be + * used for + */ +public abstract class ImageLineSetDefault implements IImageLineSet { + + protected final ImageInfo imgInfo; + private final boolean singleCursor; + private final int nlines, offset, step; + protected List imageLines; // null if single cursor + protected T imageLine; // null unless single cursor + protected int currentRow = -1; // only relevant (and not much) for cursor + + public ImageLineSetDefault(ImageInfo imgInfo, final boolean singleCursor, final int nlinesx, final int noffsetx, + final int stepx) { + this.imgInfo = imgInfo; + this.singleCursor = singleCursor; + if (singleCursor) { + this.nlines = 1; // we store only one line, no matter how many will be read + offset = 0; + this.step = 1;// don't matter + } else { + this.nlines = nlinesx; // note that it can also be 1 + offset = noffsetx; + this.step = stepx;// don't matter + } + createImageLines(); + } + + private void createImageLines() { + if (singleCursor) { + imageLine = createImageLine(); + } else { + imageLines = new ArrayList(); + for (int i = 0; i < nlines; i++) { + imageLines.add(createImageLine()); + } + } + } + + protected abstract T createImageLine(); + + /** + * Retrieves the image line + *

      + * Warning: the argument is the row number in the original image + *

      + * If this is a cursor, no check is done, always the same row is returned + */ + public T getImageLine(int n) { + currentRow = n; + if (singleCursor) { + return imageLine; + } else { + int r = imageRowToMatrixRowStrict(n); + if (r < 0) { + throw new PngjException("Invalid row number"); + } + return imageLines.get(r); + } + } + + /** + * does not check for valid range + */ + public T getImageLineRawNum(int r) { + if (singleCursor) { + return imageLine; + } else { + return imageLines.get(r); + } + } + + /** + * True if the set contains this image line + *

      + * Warning: the argument is the row number in the original image + *

      + * If this works as cursor, this returns true only if that is the number of + * its "current" line + */ + public boolean hasImageLine(int n) { + return singleCursor ? currentRow == n : imageRowToMatrixRowStrict(n) >= 0; + } + + /** + * How many lines does this object contain? + */ + public int size() { + return nlines; + } + + /** + * Same as {@link #imageRowToMatrixRow(int)}, but returns negative if + * invalid + */ + public int imageRowToMatrixRowStrict(int imrow) { + imrow -= offset; + int mrow = imrow >= 0 && (step == 1 || imrow % step == 0) ? imrow / step : -1; + return mrow < nlines ? mrow : -1; + } + + /** + * Converts from matrix row number (0 : nRows-1) to image row number + * + * @param mrow Matrix row number + * @return Image row number. Returns trash if mrow is invalid + */ + public int matrixRowToImageRow(int mrow) { + return mrow * step + offset; + } + + /** + * Converts from real image row to this object row number. + *

      + * Warning: this always returns a valid matrix row (clamping on 0 : nrows-1, + * and rounding down) + *

      + * Eg: rowOffset=4,rowStep=2 imageRowToMatrixRow(17) returns 6 , + * imageRowToMatrixRow(1) returns 0 + */ + public int imageRowToMatrixRow(int imrow) { + int r = (imrow - offset) / step; + return r < 0 ? 0 : (r < nlines ? r : nlines - 1); + } + + /** + * utility function, given a factory for one line, returns a factory for a + * set + */ + public static IImageLineSetFactory createImageLineSetFactoryFromImageLineFactory( + final IImageLineFactory ifactory) { // ugly method must have ugly name. don't let this intimidate you + return new IImageLineSetFactory() { + public IImageLineSet create(final ImageInfo iminfo, boolean singleCursor, int nlines, int noffset, + int step) { + return new ImageLineSetDefault(iminfo, singleCursor, nlines, noffset, step) { + @Override + protected T createImageLine() { + return ifactory.createImageLine(iminfo); + } + }; + } + + }; + } + + /** + * utility function, returns default factory for {@link ImageLineInt} + */ + public static IImageLineSetFactory getFactoryInt() { + return createImageLineSetFactoryFromImageLineFactory(ImageLineInt.getFactory()); + } + + /** + * utility function, returns default factory for {@link ImageLineByte} + */ + public static IImageLineSetFactory getFactoryByte() { + return createImageLineSetFactoryFromImageLineFactory(ImageLineByte.getFactory()); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal.java new file mode 100644 index 0000000..939bd09 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal.java @@ -0,0 +1,248 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Some utility static methods for internal use. + * Client code should not normally use this class + */ +public final class PngHelperInternal { + + /** + * Default charset, used internally by PNG for several things + */ + public static Charset charsetLatin1 = StandardCharsets.ISO_8859_1; + /** + * UTF-8 is only used for some chunks + */ + public static Charset charsetUTF8 = StandardCharsets.UTF_8; + + /** + * PNG magic bytes + */ + public static byte[] getPngIdSignature() { + return new byte[]{-119, 80, 78, 71, 13, 10, 26, 10}; + } + + public static int doubleToInt100000(double d) { + return (int) (d * 100000.0 + 0.5); + } + + public static double intToDouble100000(int i) { + return i / 100000.0; + } + + public static int readByte(InputStream is) { + try { + return is.read(); + } catch (IOException e) { + throw new PngjInputException("error reading byte", e); + } + } + + /** + * -1 if eof + * PNG uses "network byte order" + */ + public static int readInt2(InputStream is) { + try { + int b1 = is.read(); + int b2 = is.read(); + if (b1 == -1 || b2 == -1) { + return -1; + } + return (b1 << 8) | b2; + } catch (IOException e) { + throw new PngjInputException("error reading Int2", e); + } + } + + /** + * -1 if eof + * PNG uses "network byte order" + */ + public static int readInt4(InputStream is) { + try { + int b1 = is.read(); + int b2 = is.read(); + int b3 = is.read(); + int b4 = is.read(); + if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1) { + return -1; + } + return (b1 << 24) | (b2 << 16) | (b3 << 8) + b4; + } catch (IOException e) { + throw new PngjInputException("error reading Int4", e); + } + } + + public static int readInt1fromByte(byte[] b, int offset) { + return (b[offset] & 0xff); + } + + public static int readInt2fromBytes(byte[] b, int offset) { + return ((b[offset] & 0xff) << 8) | ((b[offset + 1] & 0xff)); + } + + public static int readInt4fromBytes(byte[] b, int offset) { + return ((b[offset] & 0xff) << 24) | ((b[offset + 1] & 0xff) << 16) | ((b[offset + 2] & 0xff) << 8) + | (b[offset + 3] & 0xff); + } + + public static void writeByte(OutputStream os, byte b) { + try { + os.write(b); + } catch (IOException e) { + throw new PngjOutputException(e); + } + } + + public static void writeByte(OutputStream os, byte[] bs) { + try { + os.write(bs); + } catch (IOException e) { + throw new PngjOutputException(e); + } + } + + public static void writeInt2(OutputStream os, int n) { + byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}; + writeBytes(os, temp); + } + + public static void writeInt4(OutputStream os, int n) { + byte[] temp = new byte[4]; + writeInt4tobytes(n, temp, 0); + writeBytes(os, temp); + } + + public static void writeInt2tobytes(int n, byte[] b, int offset) { + b[offset] = (byte) ((n >> 8) & 0xff); + b[offset + 1] = (byte) (n & 0xff); + } + + public static void writeInt4tobytes(int n, byte[] b, int offset) { + b[offset] = (byte) ((n >> 24) & 0xff); + b[offset + 1] = (byte) ((n >> 16) & 0xff); + b[offset + 2] = (byte) ((n >> 8) & 0xff); + b[offset + 3] = (byte) (n & 0xff); + } + + /** + * guaranteed to read exactly len bytes. throws error if it can't + */ + public static void readBytes(InputStream is, byte[] b, int offset, int len) { + if (len == 0) { + return; + } + try { + int read = 0; + while (read < len) { + int n = is.read(b, offset + read, len - read); + if (n < 1) { + throw new PngjInputException("error reading bytes, " + n + " !=" + len); + } + read += n; + } + } catch (IOException e) { + throw new PngjInputException("error reading", e); + } + } + + public static void skipBytes(InputStream is, long len) { + try { + while (len > 0) { + long n1 = is.skip(len); + if (n1 > 0) { + len -= n1; + } else { // should we retry? lets read one byte + if (is.read() == -1) // EOF + { + break; + } else { + len--; + } + } + } + } catch (IOException e) { + throw new PngjInputException(e); + } + } + + public static void writeBytes(OutputStream os, byte[] b) { + try { + os.write(b); + } catch (IOException e) { + throw new PngjOutputException(e); + } + } + + public static void writeBytes(OutputStream os, byte[] b, int offset, int n) { + try { + os.write(b, offset, n); + } catch (IOException e) { + throw new PngjOutputException(e); + } + } + + public static int filterRowPaeth(int r, int left, int up, int upleft) { // a = left, b = above, c + // = upper left + return (r - filterPaethPredictor(left, up, upleft)) & 0xFF; + } + + static int filterPaethPredictor(final int a, final int b, final int c) { // a = left, b = + // above, c = upper + // left + // from + // http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html + + final int p = a + b - c;// ; initial estimate + final int pa = p >= a ? p - a : a - p; + final int pb = p >= b ? p - b : b - p; + final int pc = p >= c ? p - c : c - p; + // ; return nearest of a,b,c, + // ; breaking ties in order a,b,c. + if (pa <= pb && pa <= pc) { + return a; + } else if (pb <= pc) { + return b; + } else { + return c; + } + } + + public static InputStream istreamFromFile(File f) { + FileInputStream is; + try { + is = new FileInputStream(f); + } catch (Exception e) { + throw new PngjInputException("Could not open " + f, e); + } + return is; + } + + static OutputStream ostreamFromFile(File f, boolean allowoverwrite) { + // In old versions of GAE (Google App Engine) this could trigger + // issues because java.io.FileOutputStream was not whitelisted. + java.io.FileOutputStream os = null; + if (f.exists() && !allowoverwrite) { + throw new PngjOutputException("File already exists: " + f); + } + try { + os = new java.io.FileOutputStream(f); + } catch (Exception e) { + throw new PngjInputException("Could not open for write" + f, e); + } + return os; + } + + public static long getDigest(PngReader pngr) { + return pngr.getSimpleDigest(); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal2.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal2.java new file mode 100644 index 0000000..fddb9e5 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngHelperInternal2.java @@ -0,0 +1,36 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.File; +import java.io.OutputStream; + +/** + * For organization purposes, this class is the onlt that uses classes not in + * GAE (Google App Engine) white list + *

      + * You should not use this class in GAE + */ +final class PngHelperInternal2 { + + /** + * WARNING: this uses FileOutputStream which is not allowed in + * GoogleAppEngine + *

      + * In GAE, dont use this + * + * @param f + * @param allowoverwrite + * @return + */ + static OutputStream ostreamFromFile(File f, boolean allowoverwrite) { + java.io.FileOutputStream os = null; // this will fail in GAE! + if (f.exists() && !allowoverwrite) { + throw new PngjOutputException("File already exists: " + f); + } + try { + os = new java.io.FileOutputStream(f); + } catch (Exception e) { + throw new PngjInputException("Could not open for write" + f, e); + } + return os; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReader.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReader.java new file mode 100644 index 0000000..0944277 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReader.java @@ -0,0 +1,674 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.Closeable; +import java.io.File; +import java.io.InputStream; +import java.util.logging.Logger; +import java.util.zip.Adler32; +import java.util.zip.CRC32; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkLoadBehaviour; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksList; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkFCTL; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkFDAT; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIDAT; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngMetadata; + +/** + * Reads a PNG image (pixels and/or metadata) from a file or stream. + *

      + * Each row is read as an {@link ImageLineInt} object (one int per sample), but + * this can be changed by setting a different ImageLineFactory + *

      + * Internally, this wraps a {@link ChunkSeqReaderPng} with a + * {@link BufferedStreamFeeder} + *

      + * The reading sequence is as follows:
      + * 1. At construction time, the header and IHDR chunk are read (basic image + * info)
      + * 2. Afterwards you can set some additional global options. Eg. + * {@link #setCrcCheckDisabled()}.
      + * 3. Optional: If you call getMetadata() or getChunksLisk() before start + * reading the rows, all the chunks before IDAT are then loaded and available + *
      + * 4a. The rows are read in order by calling {@link #readRow()}. You can also + * call {@link #readRow(int)} to skip rows -but you can't go backwards, at least + * not with this implementation. This method returns a {@link IImageLine} object + * which can be casted to the concrete class. This class returns by default a + * {@link ImageLineInt}, but this can be changed.
      + * 4b. Alternatively, you can read all rows, or a subset, in a single call: + * {@link #readRows()}, {@link #readRows(int, int, int)} ,etc. In general this + * consumes more memory, but for interlaced images this is equally efficient, + * and more so if reading a small subset of rows.
      + * 5. Reading of the last row automatically loads the trailing chunks, and ends + * the reader.
      + * 6. end() also loads the trailing chunks, if not done, and finishes cleanly + * the reading and closes the stream. + *

      + * See also {@link PngReaderInt} (esentially the same as this, and slightly + * preferred) and {@link PngReaderByte} (uses byte instead of int to store the + * samples). + */ +public class PngReader implements Closeable { + private static final Logger LOGGER = Logger.getLogger(PngReader.class.getName()); + + // some performance/defensive limits + /** + * Defensive limit: refuse to read more than 900MB, can be changed with + * {@link #setMaxTotalBytesRead(long)} + */ + public static final long MAX_TOTAL_BYTES_READ_DEFAULT = 901001001L; // ~ 900MB + + /** + * Defensive limit: refuse to load more than 5MB of ancillary metadata, see + * {@link #setMaxBytesMetadata(long)} and also + * {@link #addChunkToSkip(String)} + */ + public static final long MAX_BYTES_METADATA_DEFAULT = 5024024; // for ancillary chunks + + /** + * Skip ancillary chunks greater than 2MB, see + * {@link #setSkipChunkMaxSize(long)} + */ + public static final long MAX_CHUNK_SIZE_SKIP = 2024024; // chunks exceeding this size will be skipped (nor even CRC + // checked) + + /** + * Basic image info - final and inmutable. + */ + public final ImageInfo imgInfo; // People always told me: be careful what you do, and don't go around declaring + // public + // fields... + /** + * flag: image was in interlaced format + */ + public final boolean interlaced; + + /** + * This object has most of the intelligence to parse the chunks and + * decompress the IDAT stream + */ + protected final ChunkSeqReaderPng chunkseq; + + /** + * Takes bytes from the InputStream and passes it to the ChunkSeqReaderPng. + * Never null. + */ + protected final BufferedStreamFeeder streamFeeder; + + /** + * @see #getMetadata() + */ + protected final PngMetadata metadata; // this a wrapper over chunks + + /** + * Current row number (reading or read), numbered from 0 + */ + protected int rowNum = -1; + + /** + * Represents the set of lines (rows) being read. Normally this works as a + * cursor, storing only one (the current) row. This stores several (perhaps + * all) rows only if calling {@link #readRows()} or for interlaced images + * (this later is transparent to the user) + */ + protected IImageLineSet imlinesSet; + + /** + * This factory decides the concrete type of the ImageLine that will be + * used. See {@link ImageLineSetDefault} for examples + */ + private IImageLineSetFactory imageLineSetFactory; + + CRC32 idatCrca;// for internal testing + Adler32 idatCrcb;// for internal testing + + protected ErrorBehaviour errorBehaviour = ErrorBehaviour.STRICT; + + /** + * Constructs a PngReader object from a stream, with default options. This + * reads the signature and the first IHDR chunk only. + *

      + * Warning: In case of exception the stream is NOT closed. + *

      + * Warning: By default the stream will be closed when this object is + * {@link #close()}d. See {@link #PngReader(InputStream, boolean)} or + * {@link #setShouldCloseStream(boolean)} + *

      + * + * @param inputStream PNG stream + */ + public PngReader(InputStream inputStream) { + this(inputStream, true); + } + + /** + * Same as {@link #PngReader(InputStream)} but allows to specify early if + * the stream must be closed + * + * @param inputStream + * @param shouldCloseStream The stream will be closed in case of exception (constructor + * included) or normal termination. + */ + public PngReader(InputStream inputStream, boolean shouldCloseStream) { + streamFeeder = new BufferedStreamFeeder(inputStream); + streamFeeder.setCloseStream(shouldCloseStream); + chunkseq = createChunkSeqReader(); + try { + if (streamFeeder.feedFixed(chunkseq, 36) != 36) // 8+13+12=36 PNG signature+IHDR chunk + { + throw new PngjInputException("Could not read first 36 bytes (PNG signature+IHDR chunk)"); + } + imgInfo = chunkseq.getImageInfo(); + interlaced = chunkseq.getDeinterlacer() != null; + setMaxBytesMetadata(MAX_BYTES_METADATA_DEFAULT); + setMaxTotalBytesRead(MAX_TOTAL_BYTES_READ_DEFAULT); + setSkipChunkMaxSize(MAX_CHUNK_SIZE_SKIP); + chunkseq.addChunkToSkip(PngChunkFDAT.ID);// default: skip fdAT chunks! + chunkseq.addChunkToSkip(PngChunkFCTL.ID);// default: skip fctl chunks! + this.metadata = new PngMetadata(chunkseq.chunksList); + // sets a default factory (with ImageLineInt), + // this can be overwriten by a extended constructor, or by a setter + setLineSetFactory(ImageLineSetDefault.getFactoryInt()); + rowNum = -1; + } catch (RuntimeException e) { + streamFeeder.close(); + chunkseq.close(); + throw e; + } + } + + /** + * Constructs a PngReader opening a file. Sets + * shouldCloseStream=true, so that the stream will be closed with + * this object. + * + * @param file PNG image file + */ + public PngReader(File file) { + this(PngHelperInternal.istreamFromFile(file), true); + } + + /** + * Reads chunks before first IDAT. Normally this is called automatically + *

      + * Position before: after IDHR (crc included) Position after: just after the + * first IDAT chunk id + *

      + * This can be called several times (tentatively), it does nothing if + * already run + *

      + * (Note: when should this be called? in the constructor? hardly, because we + * loose the opportunity to call setChunkLoadBehaviour() and perhaps other + * settings before reading the first row? but sometimes we want to access + * some metadata (plte, phys) before. Because of this, this method can be + * called explicitly but is also called implicititly in some methods + * (getMetatada(), getChunksList()) + */ + protected void readFirstChunks() { + while (chunkseq.currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT) { + if (streamFeeder.feed(chunkseq) <= 0) { + throw new PngjInputException("Premature ending reading first chunks"); + } + } + } + + /** + * Determines which ancillary chunks (metadata) are to be loaded and which + * skipped. + *

      + * Additional restrictions may apply. See also + * {@link #setChunksToSkip(String...)}, {@link #addChunkToSkip(String)}, + * {@link #setMaxBytesMetadata(long)}, {@link #setSkipChunkMaxSize(long)} + * + * @param chunkLoadBehaviour {@link ChunkLoadBehaviour} + */ + public void setChunkLoadBehaviour(ChunkLoadBehaviour chunkLoadBehaviour) { + this.chunkseq.setChunkLoadBehaviour(chunkLoadBehaviour); + } + + /** + * All loaded chunks (metada). If we have not yet end reading the image, + * this will include only the chunks before the pixels data (IDAT) + *

      + * Critical chunks are included, except that all IDAT chunks appearance are + * replaced by a single dummy-marker IDAT chunk. These might be copied to + * the PngWriter + *

      + * + * @see #getMetadata() + */ + public ChunksList getChunksList() { + return getChunksList(true); + } + + /** + * @param forceLoadingOfFirstChunks If true, chunks before IDAT will be load if needed + * @return list of currently read chunks + */ + public ChunksList getChunksList(boolean forceLoadingOfFirstChunks) { + if (forceLoadingOfFirstChunks && chunkseq.firstChunksNotYetRead()) { + readFirstChunks(); + } + return chunkseq.chunksList; + } + + /** + * From 0 to 6, see ChunksList CHUNK_GROUP_* + */ + int getCurrentChunkGroup() { + return chunkseq.currentChunkGroup; + } + + /** + * High level wrapper over chunksList + * + * @see #getChunksList() + */ + public PngMetadata getMetadata() { + if (chunkseq.firstChunksNotYetRead()) { + readFirstChunks(); + } + return metadata; + } + + /** + * Reads next row. + *

      + * The caller must know that there are more rows to read. + * + * @return Never null. Throws PngInputException if no more + */ + public IImageLine readRow() { + return readRow(rowNum + 1); + } + + /** + * True if last row has not yet been read + */ + public boolean hasMoreRows() { + return rowNum < getCurImgInfo().rows - 1; + } + + /** + * The row number is mostly meant as a check, the rows must be called in + * ascending order (not necessarily consecutive) + */ + public IImageLine readRow(int nrow) { + if (chunkseq.firstChunksNotYetRead()) { + readFirstChunks(); + } + if (!interlaced) { + if (imlinesSet == null) { + imlinesSet = createLineSet(true, -1, 0, 1); + } + IImageLine line = imlinesSet.getImageLine(nrow); + if (nrow == rowNum) { + return line; // already read?? + } else if (nrow < rowNum) { + throw new PngjInputException("rows must be read in increasing order: " + nrow); + } + while (rowNum < nrow) { + while (!chunkseq.getIdatSet().isRowReady()) { + if (streamFeeder.feed(chunkseq) < 1) { + throw new PngjInputException("premature ending"); + } + } + rowNum++; + chunkseq.getIdatSet().updateCrcs(idatCrca, idatCrcb); + if (rowNum == nrow) { + line.readFromPngRaw(chunkseq.getIdatSet().getUnfilteredRow(), getCurImgInfo().bytesPerRow + 1, 0, + 1); + line.endReadFromPngRaw(); + } + chunkseq.getIdatSet().advanceToNextRow(); + } + return line; + } else { // and now, for something completely different (interlaced!) + if (imlinesSet == null) { + imlinesSet = createLineSet(false, getCurImgInfo().rows, 0, 1); + loadAllInterlaced(getCurImgInfo().rows, 0, 1); + } + rowNum = nrow; + return imlinesSet.getImageLine(nrow); + } + + } + + /** + * Reads all rows in a ImageLineSet This is handy, but less memory-efficient + * (except for interlaced) + */ + public IImageLineSet readRows() { + return readRows(getCurImgInfo().rows, 0, 1); + } + + /** + * Reads a subset of rows. + *

      + * This method should called once, and not be mixed with {@link #readRow()} + * + * @param nRows how many rows to read (default: imageInfo.rows; negative: + * autocompute) + * @param rowOffset rows to skip (default:0) + * @param rowStep step between rows to load( default:1) + */ + public IImageLineSet readRows(int nRows, int rowOffset, int rowStep) { + if (chunkseq.firstChunksNotYetRead()) { + readFirstChunks(); + } + if (nRows < 0) { + nRows = (getCurImgInfo().rows - rowOffset) / rowStep; + } + if (rowStep < 1 || rowOffset < 0 || nRows == 0 || nRows * rowStep + rowOffset > getCurImgInfo().rows) { + throw new PngjInputException("bad args"); + } + if (rowNum >= rowOffset) { + throw new PngjInputException("readRows cannot be mixed with readRow"); + } + imlinesSet = createLineSet(false, nRows, rowOffset, rowStep); + if (!interlaced) { + int m = -1; // last row already read in + while (m < nRows - 1) { + while (!chunkseq.getIdatSet().isRowReady()) { + if (streamFeeder.feed(chunkseq) < 1) { + throw new PngjInputException("Premature ending"); + } + } + rowNum++; + chunkseq.getIdatSet().updateCrcs(idatCrca, idatCrcb); + m = (rowNum - rowOffset) / rowStep; + if (rowNum >= rowOffset && rowStep * m + rowOffset == rowNum) { + IImageLine line = imlinesSet.getImageLine(rowNum); + line.readFromPngRaw(chunkseq.getIdatSet().getUnfilteredRow(), getCurImgInfo().bytesPerRow + 1, 0, + 1); + line.endReadFromPngRaw(); + } + chunkseq.getIdatSet().advanceToNextRow(); + } + } else { // and now, for something completely different (interlaced) + loadAllInterlaced(nRows, rowOffset, rowStep); + } + chunkseq.getIdatSet().markAsDone(); + return imlinesSet; + } + + /** + * Sets the factory that creates the ImageLine. By default, this + * implementation uses ImageLineInt but this can be changed (at construction + * time or later) by calling this method. + *

      + * See also {@link #createLineSet(boolean, int, int, int)} + * + * @param factory + */ + public void setLineSetFactory(IImageLineSetFactory factory) { + imageLineSetFactory = factory; + } + + /** + * By default this uses the factory (which, by default creates + * ImageLineInt). You should rarely override this. + *

      + * See doc in + * {@link IImageLineSetFactory#create(ImageInfo, boolean, int, int, int)} + */ + protected IImageLineSet createLineSet(boolean singleCursor, int nlines, int noffset, + int step) { + return imageLineSetFactory.create(getCurImgInfo(), singleCursor, nlines, noffset, step); + } + + protected void loadAllInterlaced(int nRows, int rowOffset, int rowStep) { + IdatSet idat = chunkseq.getIdatSet(); + int nread = 0; + do { + while (!chunkseq.getIdatSet().isRowReady()) { + if (streamFeeder.feed(chunkseq) <= 0) { + break; + } + } + if (!chunkseq.getIdatSet().isRowReady()) { + throw new PngjInputException("Premature ending?"); + } + chunkseq.getIdatSet().updateCrcs(idatCrca, idatCrcb); + int rowNumreal = idat.rowinfo.rowNreal; + boolean inset = imlinesSet.hasImageLine(rowNumreal); + if (inset) { + imlinesSet.getImageLine(rowNumreal).readFromPngRaw(idat.getUnfilteredRow(), idat.rowinfo.buflen, + idat.rowinfo.oX, idat.rowinfo.dX); + nread++; + } + idat.advanceToNextRow(); + } while (nread < nRows || !idat.isDone()); + idat.markAsDone(); + for (int i = 0, j = rowOffset; i < nRows; i++, j += rowStep) { + imlinesSet.getImageLine(j).endReadFromPngRaw(); + } + } + + /** + * Reads all the (remaining) file, skipping the pixels data. This is much + * more efficient that calling {@link #readRow()}, specially for big files + * (about 10 times faster!), because it doesn't even decompress the IDAT + * stream and disables CRC check Use this if you are not interested in + * reading pixels,only metadata. + */ + public void readSkippingAllRows() { + setCrcCheckDisabled(); + chunkseq.addChunkToSkip(PngChunkIDAT.ID); + chunkseq.addChunkToSkip(PngChunkFDAT.ID); + if (chunkseq.firstChunksNotYetRead()) { + readFirstChunks(); + } + end(); + } + + /** + * Set total maximum bytes to read (0: unlimited; default: 200MB).
      + * These are the bytes read (not loaded) in the input stream. If exceeded, + * an exception will be thrown. + */ + public void setMaxTotalBytesRead(long maxTotalBytesToRead) { + chunkseq.setMaxTotalBytesRead(maxTotalBytesToRead); + } + + /** + * Set total maximum bytes to load from ancillary chunks (0: unlimited; + * default: 5Mb).
      + * If exceeded, some chunks will be skipped + */ + public void setMaxBytesMetadata(long maxBytesMetadata) { + chunkseq.setMaxBytesMetadata(maxBytesMetadata); + } + + /** + * Set maximum size in bytes for individual ancillary chunks (0: unlimited; + * default: 2MB).
      + * Chunks exceeding this length will be skipped (the CRC will not be + * checked) and the chunk will be saved as a PngChunkSkipped object. See + * also setSkipChunkIds + */ + public void setSkipChunkMaxSize(long skipChunkMaxSize) { + chunkseq.setSkipChunkMaxSize(skipChunkMaxSize); + } + + /** + * Chunks ids to be skipped.
      + * These chunks will be skipped (the CRC will not be checked) and the chunk + * will be saved as a PngChunkSkipped object. See also setSkipChunkMaxSize + */ + public void setChunksToSkip(String... chunksToSkip) { + chunkseq.setChunksToSkip(chunksToSkip); + } + + public void addChunkToSkip(String chunkToSkip) { + chunkseq.addChunkToSkip(chunkToSkip); + } + + public void dontSkipChunk(String chunkToSkip) { + chunkseq.dontSkipChunk(chunkToSkip); + } + + /** + * if true, input stream will be closed after ending read + *

      + * default=true + */ + public void setShouldCloseStream(boolean shouldCloseStream) { + streamFeeder.setCloseStream(shouldCloseStream); + } + + /** + * Reads till end of PNG stream and calls close() + *

      + * This should normally be called after reading the pixel data, to read the + * trailing chunks and close the stream. But it can be called at anytime. + * This will also read the first chunks if not still read, and skip pixels + * (IDAT) if still pending. + *

      + * If you want to read all metadata skipping pixels, + * readSkippingAllRows() is a little more efficient. + *

      + * If you want to abort immediately, call instead close() + */ + public void end() { + try { + if (chunkseq.firstChunksNotYetRead()) { + readFirstChunks(); + } + if (chunkseq.getIdatSet() != null && !chunkseq.getIdatSet().isDone()) { + chunkseq.getIdatSet().markAsDone(); // it will ignore data + } + while (!chunkseq.isDone()) { + if (streamFeeder.feed(chunkseq) <= 0) { + break; + } + } + } finally { + close(); + } + } + + /** + * Releases resources, and closes stream if corresponds. Idempotent, secure, + * no exceptions. + *

      + * This can be also called for abort. It is recommended to call this in case + * of exceptions + */ + public void close() { + try { + if (chunkseq != null) { + chunkseq.close(); + } + } catch (Exception e) { + LOGGER.warning("error closing chunk sequence:" + e.getMessage()); + } + if (streamFeeder != null) { + streamFeeder.close(); + } + } + + /** + * Interlaced PNG is accepted -though not welcomed- now... + */ + public boolean isInterlaced() { + return interlaced; + } + + /** + * Disables the CRC integrity check in IDAT chunks and ancillary chunks, + * this gives a slight increase in reading speed for big files + */ + public void setCrcCheckDisabled() { + chunkseq.setCheckCrc(false); + } + + /** + * Gets wrapped {@link ChunkSeqReaderPng} object + */ + public ChunkSeqReaderPng getChunkseq() { + return chunkseq; + } + + /** + * called on construction time. Override if you want an alternative class + */ + protected ChunkSeqReaderPng createChunkSeqReader() { + return new ChunkSeqReaderPng(false); + } + + /** + * Enables and prepare the simple digest computation. Must be called before + * reading the pixels. See {@link #getSimpleDigestHex()} + */ + public void prepareSimpleDigestComputation() { + if (idatCrca == null) { + idatCrca = new CRC32(); + } else { + idatCrca.reset(); + } + if (idatCrcb == null) { + idatCrcb = new Adler32(); + } else { + idatCrcb.reset(); + } + imgInfo.updateCrc(idatCrca); + idatCrcb.update((byte) imgInfo.rows); // not important + } + + long getSimpleDigest() { + if (idatCrca == null) { + return 0; + } else { + return (idatCrca.getValue() ^ (idatCrcb.getValue() << 31)); + } + } + + /** + * Pseudo 64-bits digest computed over the basic image properties and the + * raw pixels data: it should coincide for equivalent images encoded with + * different filters and compressors; but will not coincide for + * interlaced/non-interlaced; also, this does not take into account the + * palette info. This will be valid only if + * {@link #prepareSimpleDigestComputation()} has been called, and all rows + * have been read. Not fool-proof, not cryptografically secure, only for + * informal testing and duplicates detection. + * + * @return A 64-digest in hexadecimal + */ + public String getSimpleDigestHex() { + return String.format("%016X", getSimpleDigest()); + } + + /** + * Basic info, for debugging. + */ + public String toString() { // basic info + return imgInfo.toString() + " interlaced=" + interlaced; + } + + /** + * Basic info, in a compact format, apt for scripting + * COLSxROWS[dBITDEPTH][a][p][g][i] ( the default dBITDEPTH='d8' is ommited) + */ + public String toStringCompact() { + return imgInfo.toStringBrief() + (interlaced ? "i" : ""); + } + + public ImageInfo getImgInfo() { + return imgInfo; + } + + public ImageInfo getCurImgInfo() { + return chunkseq.getCurImgInfo(); + } + + public void setErrorBehaviour(ErrorBehaviour er) { + this.errorBehaviour = er; + chunkseq.setErrorBehaviour(er); + } + + public boolean isDone() { + return chunkseq.isDone(); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderApng.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderApng.java new file mode 100644 index 0000000..46d536e --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderApng.java @@ -0,0 +1,219 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.File; +import java.io.InputStream; +import java.util.List; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunk; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkACTL; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkFCTL; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkFDAT; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIDAT; + +/** + * + */ +public class PngReaderApng extends PngReaderByte { + + public PngReaderApng(File file) { + super(file); + dontSkipChunk(PngChunkFCTL.ID); + } + + public PngReaderApng(InputStream inputStream) { + super(inputStream); + dontSkipChunk(PngChunkFCTL.ID); + } + + private Boolean apngKind = null; + private boolean firsIdatApngFrame = false; + protected PngChunkACTL actlChunk; // null if not APNG + private PngChunkFCTL fctlChunk; // current (null for the pseudo still frame) + + /** + * Current frame number (reading or read). First animated frame is 0. Frame + * -1 represents the IDAT (default image) when it's not part of the + * animation + */ + protected int frameNum = -1; // incremented after each fctl finding + + public boolean isApng() { + if (apngKind == null) { + // this triggers the loading of first chunks; + actlChunk = (PngChunkACTL) getChunksList().getById1(PngChunkACTL.ID); // null if not apng + apngKind = actlChunk != null; + firsIdatApngFrame = fctlChunk != null; + + } + return apngKind.booleanValue(); + } + + public void advanceToFrame(int frame) { + if (frame < frameNum) { + throw new PngjInputException("Cannot go backwards"); + } + if (frame >= getApngNumFrames()) { + throw new PngjInputException("Frame out of range " + frame); + } + if (frame > frameNum) { + addChunkToSkip(PngChunkIDAT.ID); + addChunkToSkip(PngChunkFDAT.ID); + if (chunkseq.getIdatSet() != null && !chunkseq.getIdatSet().isDone()) { + chunkseq.getIdatSet().markAsDone(); // seems to be necessary sometimes (we should check this) + } + while (frameNum < frame & !chunkseq.isDone()) { + if (streamFeeder.feed(chunkseq) <= 0) { + break; + } + } + } + if (frame == frameNum) { // prepare to read rows. at this point we have a new + dontSkipChunk(PngChunkIDAT.ID); + dontSkipChunk(PngChunkFDAT.ID); + rowNum = -1; + imlinesSet = null;// force recreation (this is slightly dirty) + // seek the next IDAT/fDAT - TODO: set the expected sequence number + while (!chunkseq.isDone() && !chunkseq.getCurChunkReader().isFromDeflatedSet()) { + if (streamFeeder.feed(chunkseq) <= 0) { + break; + } + } + } else { + throw new PngjInputException("unexpected error seeking from frame " + frame); + } + } + + /** + * True if it has a default image (IDAT) that is not part of the animation. + * In that case, we consider it as a pseudo-frame (number -1) + */ + public boolean hasExtraStillImage() { + return isApng() && !firsIdatApngFrame; + } + + /** + * Only counts true animation frames. + */ + public int getApngNumFrames() { + if (isApng()) { + return actlChunk.getNumFrames(); + } else { + return 0; + } + } + + /** + * 0 if it's to been played infinitely. -1 if not APNG + */ + public int getApngNumPlays() { + if (isApng()) { + return actlChunk.getNumPlays(); + } else { + return -1; + } + } + + @Override + public IImageLine readRow() { + // TODO Auto-generated method stub + return super.readRow(); + } + + @Override + public boolean hasMoreRows() { + // TODO Auto-generated method stub + return super.hasMoreRows(); + } + + @Override + public IImageLine readRow(int nrow) { + // TODO Auto-generated method stub + return super.readRow(nrow); + } + + @Override + public IImageLineSet readRows() { + // TODO Auto-generated method stub + return super.readRows(); + } + + @Override + public IImageLineSet readRows(int nRows, int rowOffset, int rowStep) { + // TODO Auto-generated method stub + return super.readRows(nRows, rowOffset, rowStep); + } + + @Override + public void readSkippingAllRows() { + // TODO Auto-generated method stub + super.readSkippingAllRows(); + } + + @Override + protected ChunkSeqReaderPng createChunkSeqReader() { + ChunkSeqReaderPng cr = new ChunkSeqReaderPng(false) { + + @Override + public boolean shouldSkipContent(int len, String id) { + return super.shouldSkipContent(len, id); + } + + @Override + protected boolean isIdatKind(String id) { + return id.equals(PngChunkIDAT.ID) || id.equals(PngChunkFDAT.ID); + } + + @Override + protected DeflatedChunksSet createIdatSet(String id) { + IdatSet ids = new IdatSet(id, callbackMode, getCurImgInfo(), deinterlacer); + return ids; + } + + @Override + protected void startNewChunk(int len, String id, long offset) { + super.startNewChunk(len, id, offset); + } + + @Override + protected void postProcessChunk(ChunkReader chunkR) { + super.postProcessChunk(chunkR); + if (chunkR.getChunkRaw().id.equals(PngChunkFCTL.ID)) { + frameNum++; + List chunkslist = chunkseq.getChunks(); + fctlChunk = (PngChunkFCTL) chunkslist.get(chunkslist.size() - 1); + // as this is slightly dirty, we check + if (chunkR.getChunkRaw().getOffset() != fctlChunk.getRaw().getOffset()) { + throw new PngjInputException("something went wrong"); + } + ImageInfo frameInfo = fctlChunk.getEquivImageInfo(); + getChunkseq().updateCurImgInfo(frameInfo); + } + } + + @Override + protected boolean countChunkTypeAsAncillary(String id) { + // we don't count fdat as ancillary data + return super.countChunkTypeAsAncillary(id) && !id.equals(PngChunkFDAT.ID); + } + + }; + return cr; + } + + /** + * @see #frameNum + */ + public int getFrameNum() { + return frameNum; + } + + @Override + public void end() { + // TODO Auto-generated method stub + super.end(); + } + + public PngChunkFCTL getFctl() { + return fctlChunk; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderByte.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderByte.java new file mode 100644 index 0000000..f6614a3 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderByte.java @@ -0,0 +1,32 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.File; +import java.io.InputStream; + +/** + * Trivial extension of {@link PngReader} that uses {@link ImageLineByte} + *

      + * The factory is set at construction time. Remember that this could still be + * changed at runtime. + */ +public class PngReaderByte extends PngReader { + + public PngReaderByte(File file) { + super(file); + setLineSetFactory(ImageLineSetDefault.getFactoryByte()); + } + + public PngReaderByte(InputStream inputStream) { + super(inputStream); + setLineSetFactory(ImageLineSetDefault.getFactoryByte()); + } + + /** + * Utility method that casts {@link #readRow()} return to + * {@link ImageLineByte}. + */ + public ImageLineByte readRowByte() { + return (ImageLineByte) readRow(); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderFilter.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderFilter.java new file mode 100644 index 0000000..c824841 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderFilter.java @@ -0,0 +1,104 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunk; + +/** + * This class allows to use a simple PNG reader as an input filter, wrapping a + * ChunkSeqReaderPng in callback mode. + *

      + * In this sample implementation, all IDAT chunks are skipped and the rest are + * stored. An example of use, that lets us grab the Metadata and let the pixels + * go towards a BufferedImage: + * + * + *

      + * PngReaderFilter reader = new PngReaderFilter(new FileInputStream("image.png"));
      + * BufferedImage image1 = ImageIO.read(reader);
      + * reader.readUntilEndAndClose(); // in case ImageIO.read() does not read the traling chunks (it happens)
      + * 
      + */ +public class PngReaderFilter extends FilterInputStream { + + private final ChunkSeqReaderPng chunkseq; + + public PngReaderFilter(InputStream arg0) { + super(arg0); + chunkseq = createChunkSequenceReader(); + } + + protected ChunkSeqReaderPng createChunkSequenceReader() { + return new ChunkSeqReaderPng(true) { + @Override + public boolean shouldSkipContent(int len, String id) { + return super.shouldSkipContent(len, id) || id.equals("IDAT"); + } + + @Override + protected boolean shouldCheckCrc(int len, String id) { + return false; + } + + @Override + protected void postProcessChunk(ChunkReader chunkR) { + super.postProcessChunk(chunkR); + } + }; + } + + @Override + public void close() throws IOException { + super.close(); + chunkseq.close(); + } + + @Override + public int read() throws IOException { + int r = super.read(); + if (r > 0) { + chunkseq.feedAll(new byte[]{(byte) r}, 0, 1); + } + return r; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int res = super.read(b, off, len); + if (res > 0) { + chunkseq.feedAll(b, off, res); + } + return res; + } + + @Override + public int read(byte[] b) throws IOException { + int res = super.read(b); + if (res > 0) { + chunkseq.feedAll(b, 0, res); + } + return res; + } + + public void readUntilEndAndClose() throws IOException { + BufferedStreamFeeder br = new BufferedStreamFeeder(this.in); + while ((!chunkseq.isDone()) && br.hasPendingBytes()) { + if (br.feed(chunkseq) < 1) { + break; + } + } + br.close(); + close(); + } + + public List getChunksList() { + return chunkseq.getChunks(); + } + + public ChunkSeqReaderPng getChunkseq() { + return chunkseq; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderInt.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderInt.java new file mode 100644 index 0000000..5293427 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngReaderInt.java @@ -0,0 +1,38 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.File; +import java.io.InputStream; + +/** + * Trivial extension of {@link PngReader} that uses {@link ImageLineInt}. + *

      + * In the current implementation this is quite dummy/redundant, because (for + * backward compatibility) PngReader already uses a {@link ImageLineInt}. + *

      + * The factory is set at construction time. Remember that this could still be + * changed at runtime. + */ +public class PngReaderInt extends PngReader { + + public PngReaderInt(File file) { + super(file); // not necessary to set factory, PngReader already does that + } + + public PngReaderInt(InputStream inputStream) { + super(inputStream); + } + + /** + * Utility method that casts the IImageLine to a ImageLineInt + *

      + * This only make sense for this concrete class + */ + public ImageLineInt readRowInt() { + IImageLine line = readRow(); + if (line instanceof ImageLineInt) { + return (ImageLineInt) line; + } else { + throw new PngjException("This is not a ImageLineInt : " + line.getClass()); + } + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngWriter.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngWriter.java new file mode 100644 index 0000000..42ac918 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngWriter.java @@ -0,0 +1,468 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.Closeable; +import java.io.File; +import java.io.OutputStream; +import java.util.List; +import java.util.logging.Logger; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkCopyBehaviour; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunkPredicate; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksList; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.ChunksListForWrite; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunk; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIEND; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkIHDR; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngChunkPLTE; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngMetadata; +import org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriter; +import org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriterDefault; + +/** + * Writes a PNG image, line by line. + */ +public class PngWriter implements Closeable { + + public final ImageInfo imgInfo; + + /** + * last writen row number, starting from 0 + */ + protected int rowNum = -1; + + private final ChunksListForWrite chunksList; + + private final PngMetadata metadata; + + /** + * Current chunk grounp, (0-6) already written or currently writing (this is + * advanced when just starting to write the new group, not when finalizing + * the previous) + *

      + * see {@link ChunksList} + */ + protected int currentChunkGroup = -1; + + private final int passes = 1; // Some writes might require two passes (NOT USED STILL) + private int currentpass = 0; // numbered from 1 + + private boolean shouldCloseStream = true; + + private int idatMaxSize = 0; // 0=use default (PngIDatChunkOutputStream 64k) + // private PngIDatChunkOutputStream datStream; + + protected PixelsWriter pixelsWriter; + + private final OutputStream os; + + private ChunkPredicate copyFromPredicate = null; + private ChunksList copyFromList = null; + + protected StringBuilder debuginfo = new StringBuilder(); + + private static final Logger LOGGER = Logger.getLogger(PngWriter.class.getName()); + + /** + * Opens a file for writing. + *

      + * Sets shouldCloseStream=true. For more info see + * {@link #PngWriter(OutputStream, ImageInfo)} + * + * @param file + * @param imgInfo + * @param allowoverwrite If false and file exists, an {@link PngjOutputException} is + * thrown + */ + public PngWriter(File file, ImageInfo imgInfo, boolean allowoverwrite) { + this(PngHelperInternal.ostreamFromFile(file, allowoverwrite), imgInfo); + setShouldCloseStream(true); + } + + /** + * @see #PngWriter(File, ImageInfo, boolean) (overwrite=true) + */ + public PngWriter(File file, ImageInfo imgInfo) { + this(file, imgInfo, true); + } + + /** + * Constructs a new PngWriter from a output stream. After construction + * nothing is writen yet. You still can set some parameters (compression, + * filters) and queue chunks before start writing the pixels. + *

      + * + * @param outputStream Open stream for binary writing + * @param imgInfo Basic image parameters + */ + public PngWriter(OutputStream outputStream, ImageInfo imgInfo) { + this.os = outputStream; + this.imgInfo = imgInfo; + // prealloc + chunksList = new ChunksListForWrite(imgInfo); + metadata = new PngMetadata(chunksList); + pixelsWriter = createPixelsWriter(imgInfo); + setCompLevel(9); + } + + private void initIdat() { // this triggers the writing of first chunks + pixelsWriter.setOs(this.os); + pixelsWriter.setIdatMaxSize(idatMaxSize); + writeSignatureAndIHDR(); + writeFirstChunks(); + } + + private void writeEndChunk() { + currentChunkGroup = ChunksList.CHUNK_GROUP_6_END; + PngChunkIEND c = new PngChunkIEND(imgInfo); + c.createRawChunk().writeChunk(os); + chunksList.getChunks().add(c); + } + + private void writeFirstChunks() { + if (currentChunkGroup >= ChunksList.CHUNK_GROUP_4_IDAT) { + return; + } + int nw = 0; + currentChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR; + queueChunksFromOther(); + nw = chunksList.writeChunks(os, currentChunkGroup); + currentChunkGroup = ChunksList.CHUNK_GROUP_2_PLTE; + nw = chunksList.writeChunks(os, currentChunkGroup); + if (nw > 0 && imgInfo.greyscale) { + throw new PngjOutputException("cannot write palette for this format"); + } + if (nw == 0 && imgInfo.indexed) { + throw new PngjOutputException("missing palette"); + } + currentChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE; + nw = chunksList.writeChunks(os, currentChunkGroup); + } + + private void writeLastChunks() { // not including end + currentChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT; + queueChunksFromOther(); + chunksList.writeChunks(os, currentChunkGroup); + // should not be unwriten chunks + List pending = chunksList.getQueuedChunks(); + if (!pending.isEmpty()) { + throw new PngjOutputException( + pending.size() + " chunks were not written! Eg: " + pending.get(0).toString()); + } + } + + /** + * Write id signature and also "IHDR" chunk + */ + private void writeSignatureAndIHDR() { + PngHelperInternal.writeBytes(os, PngHelperInternal.getPngIdSignature()); // signature + currentChunkGroup = ChunksList.CHUNK_GROUP_0_IDHR; + PngChunkIHDR ihdr = new PngChunkIHDR(imgInfo); + // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html + ihdr.createRawChunk().writeChunk(os); + chunksList.getChunks().add(ihdr); + } + + private void queueChunksFromOther() { + if (copyFromList == null || copyFromPredicate == null) { + return; + } + boolean idatDone = currentChunkGroup >= ChunksList.CHUNK_GROUP_4_IDAT; // we assume this method is not either + // before + // or after the IDAT writing, not in the + // middle! + for (PngChunk chunk : copyFromList.getChunks()) { + if (chunk.getRaw().data == null) { + continue; // we cannot copy skipped chunks? + } + int groupOri = chunk.getChunkGroup(); + if (groupOri <= ChunksList.CHUNK_GROUP_4_IDAT && idatDone) { + continue; + } + if (groupOri >= ChunksList.CHUNK_GROUP_4_IDAT && !idatDone) { + continue; + } + if (chunk.crit && !chunk.id.equals(PngChunkPLTE.ID)) { + continue; // critical chunks (except perhaps PLTE) are never + } + // copied + boolean copy = copyFromPredicate.match(chunk); + if (copy) { + // but if the chunk is already queued or writen, it's ommited! + if (chunksList.getEquivalent(chunk).isEmpty() && chunksList.getQueuedEquivalent(chunk).isEmpty()) { + chunksList.queue(chunk); + } + } + } + } + + /** + * Queues an ancillary chunk for writing. + *

      + * If a "equivalent" chunk is already queued (see + * {@link ChunkHelper#equivalent(PngChunk, PngChunk)), this overwrites it. + *

      The chunk will be written as late as possible, unless the priority is + * set. + * + * @param chunk + */ + public void queueChunk(PngChunk chunk) { + for (PngChunk other : chunksList.getQueuedEquivalent(chunk)) { + getChunksList().removeChunk(other); + } + chunksList.queue(chunk); + } + + /** + * Sets an origin (typically from a {@link PngReader}) of Chunks to be + * copied. This should be called only once, before starting writing the + * rows. It doesn't matter the current state of the PngReader reading, this + * is a live object and what matters is that when the writer writes the + * pixels (IDAT) the reader has already read them, and that when the writer + * ends, the reader is already ended (all this is very natural). + *

      + * Apart from the copyMask, there is some addional heuristics: + *

      + * - The chunks will be queued, but will be written as late as possible + * (unless you explicitly set priority=true) + *

      + * - The chunk will not be queued if an "equivalent" chunk was already + * queued explicitly. And it will be overwriten another is queued + * explicitly. + * + * @param chunks + * @param copyMask Some bitmask from {@link ChunkCopyBehaviour} + * @see #copyChunksFrom(ChunksList, ChunkPredicate) + */ + public void copyChunksFrom(ChunksList chunks, int copyMask) { + copyChunksFrom(chunks, ChunkCopyBehaviour.createPredicate(copyMask, imgInfo)); + } + + /** + * Copy all chunks from origin. See {@link #copyChunksFrom(ChunksList, int)} + * for more info + */ + public void copyChunksFrom(ChunksList chunks) { + copyChunksFrom(chunks, ChunkCopyBehaviour.COPY_ALL); + } + + /** + * Copy chunks from origin depending on some {@link ChunkPredicate} + * + * @param chunks + * @param predicate The chunks (ancillary or PLTE) will be copied if and only if + * predicate matches + * @see #copyChunksFrom(ChunksList, int) for more info + */ + public void copyChunksFrom(ChunksList chunks, ChunkPredicate predicate) { + if (copyFromList != null && chunks != null) { + LOGGER.warning("copyChunksFrom should only be called once"); + } + if (predicate == null) { + throw new PngjOutputException("copyChunksFrom requires a predicate"); + } + this.copyFromList = chunks; + this.copyFromPredicate = predicate; + } + + /** + * Computes compressed size/raw size, approximate. + *

      + * Actually: compressed size = total size of IDAT data , raw size = + * uncompressed pixel bytes = rows * (bytesPerRow + 1). + *

      + * This must be called after pngw.end() + */ + public double computeCompressionRatio() { + if (currentChunkGroup < ChunksList.CHUNK_GROUP_5_AFTERIDAT) { + throw new PngjOutputException("must be called after end()"); + } + return pixelsWriter.getCompression(); + } + + /** + * Finalizes all the steps and closes the stream. This must be called after + * writing the lines. Idempotent + */ + public void end() { + if (rowNum != imgInfo.rows - 1 || !pixelsWriter.isDone()) { + throw new PngjOutputException("all rows have not been written"); + } + try { + if (pixelsWriter != null) { + pixelsWriter.close(); + } + if (currentChunkGroup < ChunksList.CHUNK_GROUP_5_AFTERIDAT) { + writeLastChunks(); + } + if (currentChunkGroup < ChunksList.CHUNK_GROUP_6_END) { + writeEndChunk(); + } + } finally { + close(); + } + } + + /** + * Closes and releases resources + *

      + * This is normally called internally from {@link #end()}, you should only + * call this for aborting the writing and release resources (close the + * stream). + *

      + * Idempotent and secure - never throws exceptions + */ + public void close() { + if (pixelsWriter != null) { + pixelsWriter.close(); + } + if (shouldCloseStream && os != null) { + try { + os.close(); + } catch (Exception e) { + LOGGER.warning("Error closing writer " + e.toString()); + } + } + } + + /** + * returns the chunks list (queued and writen chunks) + */ + public ChunksListForWrite getChunksList() { + return chunksList; + } + + /** + * Retruns a high level wrapper over for metadata handling + */ + public PngMetadata getMetadata() { + return metadata; + } + + /** + * Sets internal prediction filter type, or strategy to choose it. + *

      + * This must be called just after constructor, before starting writing. + *

      + */ + public void setFilterType(FilterType filterType) { + pixelsWriter.setFilterType(filterType); + } + + /** + * This is kept for backwards compatibility, now the PixelsWriter object + * should be used for setting compression/filtering options + * + * @param compLevel between 0 (no compression, max speed) and 9 (max compression) + * @see PixelsWriter#setCompressionFactor(double) + */ + public void setCompLevel(int complevel) { + pixelsWriter.setDeflaterCompLevel(complevel); + } + + /** + * + */ + public void setFilterPreserve(boolean filterPreserve) { + if (filterPreserve) { + pixelsWriter.setFilterType(FilterType.FILTER_PRESERVE); + } else if (pixelsWriter.getFilterType() == null) { + pixelsWriter.setFilterType(FilterType.FILTER_DEFAULT); + } + } + + /** + * Sets maximum size of IDAT fragments. Incrementing this from the default + * has very little effect on compression and increments memory usage. You + * should rarely change this. + *

      + * + * @param idatMaxSize default=0 : use defaultSize (32K) + */ + public void setIdatMaxSize(int idatMaxSize) { + this.idatMaxSize = idatMaxSize; + } + + /** + * If true, output stream will be closed after ending write + *

      + * default=true + */ + public void setShouldCloseStream(boolean shouldCloseStream) { + this.shouldCloseStream = shouldCloseStream; + } + + /** + * Writes next row, does not check row number. + * + * @param imgline + */ + public void writeRow(IImageLine imgline) { + writeRow(imgline, rowNum + 1); + } + + /** + * Writes the full set of row. The ImageLineSet should contain (allow to + * acces) imgInfo.rows + */ + public void writeRows(IImageLineSet imglines) { + for (int i = 0; i < imgInfo.rows; i++) { + writeRow(imglines.getImageLineRawNum(i)); + } + } + + public void writeRow(IImageLine imgline, int rownumber) { + rowNum++; + if (rowNum == imgInfo.rows) { + rowNum = 0; + } + if (rownumber == imgInfo.rows) { + rownumber = 0; + } + if (rownumber >= 0 && rowNum != rownumber) { + throw new PngjOutputException("rows must be written in order: expected:" + rowNum + " passed:" + rownumber); + } + if (rowNum == 0) { + currentpass++; + } + if (rownumber == 0 && currentpass == passes) { + initIdat(); + currentChunkGroup = ChunksList.CHUNK_GROUP_4_IDAT; // we just begin writing IDAT + } + byte[] rowb = pixelsWriter.getRowb(); + imgline.writeToPngRaw(rowb); + pixelsWriter.processRow(rowb); + + } + + /** + * Utility method, uses internaly a ImageLineInt + */ + public void writeRowInt(int[] buf) { + writeRow(new ImageLineInt(imgInfo, buf)); + } + + /** + * Factory method for pixels writer. This will be called once at the moment + * at start writing a set of IDAT chunks (typically once in a normal PNG) + *

      + * This should be overriden if custom filtering strategies are desired. + * Remember to release this with close() + * + * @param imginfo Might be different than that of this object (eg: APNG with + * subimages) + * @param os Output stream + * @return new PixelsWriter. Don't forget to call close() when discarding it + */ + protected PixelsWriter createPixelsWriter(ImageInfo imginfo) { + PixelsWriterDefault pw = new PixelsWriterDefault(imginfo); + return pw; + } + + public final PixelsWriter getPixelsWriter() { + return pixelsWriter; + } + + public String getDebuginfo() { + return debuginfo.toString(); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngWriterHc.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngWriterHc.java new file mode 100644 index 0000000..402af77 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngWriterHc.java @@ -0,0 +1,36 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +import java.io.File; +import java.io.OutputStream; +import org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriter; +import org.xbib.graphics.imageio.plugins.png.pngj.pixels.PixelsWriterMultiple; + +/** + * Pngwriter with High compression EXPERIMENTAL + */ +public class PngWriterHc extends PngWriter { + + public PngWriterHc(File file, ImageInfo imgInfo, boolean allowoverwrite) { + super(file, imgInfo, allowoverwrite); + setFilterType(FilterType.FILTER_SUPER_ADAPTIVE); + } + + public PngWriterHc(File file, ImageInfo imgInfo) { + super(file, imgInfo); + } + + public PngWriterHc(OutputStream outputStream, ImageInfo imgInfo) { + super(outputStream, imgInfo); + } + + @Override + protected PixelsWriter createPixelsWriter(ImageInfo imginfo) { + PixelsWriterMultiple pw = new PixelsWriterMultiple(imginfo); + return pw; + } + + public PixelsWriterMultiple getPixelWriterMultiple() { + return (PixelsWriterMultiple) pixelsWriter; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjBadCrcException.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjBadCrcException.java new file mode 100644 index 0000000..021cd79 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjBadCrcException.java @@ -0,0 +1,20 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Exception thrown by bad CRC check + */ +public class PngjBadCrcException extends PngjInputException { + private static final long serialVersionUID = 1L; + + public PngjBadCrcException(String message, Throwable cause) { + super(message, cause); + } + + public PngjBadCrcException(String message) { + super(message); + } + + public PngjBadCrcException(Throwable cause) { + super(cause); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjBadSignature.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjBadSignature.java new file mode 100644 index 0000000..aa0f4ee --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjBadSignature.java @@ -0,0 +1,20 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Exception thrown by bad signature (not a PNG file) + */ +public class PngjBadSignature extends PngjInputException { + private static final long serialVersionUID = 1L; + + public PngjBadSignature(String message, Throwable cause) { + super(message, cause); + } + + public PngjBadSignature(String message) { + super(message); + } + + public PngjBadSignature(Throwable cause) { + super(cause); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjException.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjException.java new file mode 100644 index 0000000..63513ed --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjException.java @@ -0,0 +1,20 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Generic exception for this library. It's a RuntimeException (unchecked) + */ +public class PngjException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public PngjException(String message, Throwable cause) { + super(message, cause); + } + + public PngjException(String message) { + super(message); + } + + public PngjException(Throwable cause) { + super(cause); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjExceptionInternal.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjExceptionInternal.java new file mode 100644 index 0000000..6775fee --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjExceptionInternal.java @@ -0,0 +1,23 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Exception for anomalous internal problems (sort of asserts) that point to + * some issue with the library + * + * @author Hernan J Gonzalez + */ +public class PngjExceptionInternal extends RuntimeException { + private static final long serialVersionUID = 1L; + + public PngjExceptionInternal(String message, Throwable cause) { + super(message, cause); + } + + public PngjExceptionInternal(String message) { + super(message); + } + + public PngjExceptionInternal(Throwable cause) { + super(cause); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjInputException.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjInputException.java new file mode 100644 index 0000000..e124e2b --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjInputException.java @@ -0,0 +1,20 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Exception thrown when reading a PNG. + */ +public class PngjInputException extends PngjException { + private static final long serialVersionUID = 1L; + + public PngjInputException(String message, Throwable cause) { + super(message, cause); + } + + public PngjInputException(String message) { + super(message); + } + + public PngjInputException(Throwable cause) { + super(cause); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjOutputException.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjOutputException.java new file mode 100644 index 0000000..8fc0a76 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjOutputException.java @@ -0,0 +1,20 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Exception thrown by writing process + */ +public class PngjOutputException extends PngjException { + private static final long serialVersionUID = 1L; + + public PngjOutputException(String message, Throwable cause) { + super(message, cause); + } + + public PngjOutputException(String message) { + super(message); + } + + public PngjOutputException(Throwable cause) { + super(cause); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjPrematureEnding.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjPrematureEnding.java new file mode 100644 index 0000000..a8e036c --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjPrematureEnding.java @@ -0,0 +1,10 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +@SuppressWarnings("serial") +public class PngjPrematureEnding extends PngjInputException { + + public PngjPrematureEnding(String message) { + super(message); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjUnsupportedException.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjUnsupportedException.java new file mode 100644 index 0000000..479f375 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/PngjUnsupportedException.java @@ -0,0 +1,21 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Exception thrown because of some valid feature of PNG standard that this + * library does not support. + */ +public class PngjUnsupportedException extends PngjException { + private static final long serialVersionUID = 1L; + + public PngjUnsupportedException(String message, Throwable cause) { + super(message, cause); + } + + public PngjUnsupportedException(String message) { + super(message); + } + + public PngjUnsupportedException(Throwable cause) { + super(cause); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/RowInfo.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/RowInfo.java new file mode 100644 index 0000000..799c716 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/RowInfo.java @@ -0,0 +1,54 @@ +package org.xbib.graphics.imageio.plugins.png.pngj; + +/** + * Packs information of current row. Only used internally + */ +class RowInfo { + public final ImageInfo imgInfo; + public final Deinterlacer deinterlacer; + public final boolean imode; // Interlaced + int dY, dX, oY, oX; // current step and offset (in pixels) + int rowNseq; // row number (from 0) in sequential read order + int rowNreal; // row number in the real image + int rowNsubImg; // current row in the virtual subsampled image; this increments (by 1) from 0 to rows/dy 7 times + int rowsSubImg, colsSubImg; // size of current subimage , in pixels + int bytesRow; + int pass; // 1-7 + byte[] buf; // non-deep copy + int buflen; // valid bytes in buffer (include filter byte) + + public RowInfo(ImageInfo imgInfo, Deinterlacer deinterlacer) { + this.imgInfo = imgInfo; + this.deinterlacer = deinterlacer; + this.imode = deinterlacer != null; + } + + void update(int rowseq) { + rowNseq = rowseq; + if (imode) { + pass = deinterlacer.getPass(); + dX = deinterlacer.dX; + dY = deinterlacer.dY; + oX = deinterlacer.oX; + oY = deinterlacer.oY; + rowNreal = deinterlacer.getCurrRowReal(); + rowNsubImg = deinterlacer.getCurrRowSubimg(); + rowsSubImg = deinterlacer.getRows(); + colsSubImg = deinterlacer.getCols(); + bytesRow = (imgInfo.bitspPixel * colsSubImg + 7) / 8; + } else { + pass = 1; + dX = dY = 1; + oX = oY = 0; + rowNreal = rowNsubImg = rowseq; + rowsSubImg = imgInfo.rows; + colsSubImg = imgInfo.cols; + bytesRow = imgInfo.bytesPerRow; + } + } + + void updateBuf(byte[] buf, int buflen) { + this.buf = buf; + this.buflen = buflen; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkCopyBehaviour.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkCopyBehaviour.java new file mode 100644 index 0000000..9f86230 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkCopyBehaviour.java @@ -0,0 +1,110 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngReader; +import org.xbib.graphics.imageio.plugins.png.pngj.PngWriter; + +/** + * Chunk copy policy to apply when copyng from a {@link PngReader} to a + * {@link PngWriter}. + *

      + * The constants are bit-masks, they can be OR-ed + *

      + * Reference: + * http://www.w3.org/TR/PNG/#14
      + */ +public class ChunkCopyBehaviour { + + /** + * Don't copy anything + */ + public static final int COPY_NONE = 0; + + /** + * copy the palette + */ + public static final int COPY_PALETTE = 1; + + /** + * copy all 'safe to copy' chunks + */ + public static final int COPY_ALL_SAFE = 1 << 2; + + /** + * copy all, including palette + */ + public static final int COPY_ALL = 1 << 3; // includes palette! + /** + * Copy PHYS chunk (physical resolution) + */ + public static final int COPY_PHYS = 1 << 4; // dpi + /** + * Copy al textual chunks. + */ + public static final int COPY_TEXTUAL = 1 << 5; // all textual types + /** + * Copy TRNS chunk + */ + public static final int COPY_TRANSPARENCY = 1 << 6; // + /** + * Copy unknown chunks (unknown by our factory) + */ + public static final int COPY_UNKNOWN = 1 << 7; // all unknown (by the factory!) + /** + * Copy almost all: excepts only HIST (histogram) TIME and TEXTUAL chunks + */ + public static final int COPY_ALMOSTALL = 1 << 8; + + private static boolean maskMatch(int v, int mask) { + return (v & mask) != 0; + } + + /** + * Creates a predicate equivalent to the copy mask + * Given a copy mask (see static fields) and the ImageInfo of the target + * PNG, returns a predicate that tells if a chunk should be copied. + * This is a handy helper method, you can also create and set your own + * predicate + */ + public static ChunkPredicate createPredicate(final int copyFromMask, final ImageInfo imgInfo) { + return new ChunkPredicate() { + public boolean match(PngChunk chunk) { + if (chunk.crit) { + if (chunk.id.equals(ChunkHelper.PLTE)) { + if (imgInfo.indexed && maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_PALETTE)) { + return true; + } + return !imgInfo.greyscale && maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_ALL); + } + } else { // ancillary + boolean text = (chunk instanceof PngChunkTextVar); + boolean safe = chunk.safe; + // notice that these if are not exclusive + if (maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_ALL)) { + return true; + } + if (safe && maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_ALL_SAFE)) { + return true; + } + if (chunk.id.equals(ChunkHelper.tRNS) + && maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_TRANSPARENCY)) { + return true; + } + if (chunk.id.equals(ChunkHelper.pHYs) && maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_PHYS)) { + return true; + } + if (text && maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_TEXTUAL)) { + return true; + } + if (maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_ALMOSTALL) && !(ChunkHelper.isUnknown(chunk) + || text || chunk.id.equals(ChunkHelper.hIST) || chunk.id.equals(ChunkHelper.tIME))) { + return true; + } + return maskMatch(copyFromMask, ChunkCopyBehaviour.COPY_UNKNOWN) && ChunkHelper.isUnknown(chunk); + } + return false; + } + + }; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkFactory.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkFactory.java new file mode 100644 index 0000000..5704df3 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkFactory.java @@ -0,0 +1,134 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.IChunkFactory; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * Default chunk factory. + *

      + * The user that wants to parse custom chunks can extend + * {@link #createEmptyChunkExtended(String, ImageInfo)} + */ +public class ChunkFactory implements IChunkFactory { + + boolean parse; + + public ChunkFactory() { + this(true); + } + + public ChunkFactory(boolean parse) { + this.parse = parse; + } + + public final PngChunk createChunk(ChunkRaw chunkRaw, ImageInfo imgInfo) { + PngChunk c = createEmptyChunkKnown(chunkRaw.id, imgInfo); + if (c == null) { + c = createEmptyChunkExtended(chunkRaw.id, imgInfo); + } + if (c == null) { + c = createEmptyChunkUnknown(chunkRaw.id, imgInfo); + } + c.setRaw(chunkRaw); + if (parse && chunkRaw.data != null) { + c.parseFromRaw(chunkRaw); + } + return c; + } + + protected final PngChunk createEmptyChunkKnown(String id, ImageInfo imgInfo) { + if (id.equals(ChunkHelper.IDAT)) { + return new PngChunkIDAT(imgInfo); + } + if (id.equals(ChunkHelper.IHDR)) { + return new PngChunkIHDR(imgInfo); + } + if (id.equals(ChunkHelper.PLTE)) { + return new PngChunkPLTE(imgInfo); + } + if (id.equals(ChunkHelper.IEND)) { + return new PngChunkIEND(imgInfo); + } + if (id.equals(ChunkHelper.tEXt)) { + return new PngChunkTEXT(imgInfo); + } + if (id.equals(ChunkHelper.iTXt)) { + return new PngChunkITXT(imgInfo); + } + if (id.equals(ChunkHelper.zTXt)) { + return new PngChunkZTXT(imgInfo); + } + if (id.equals(ChunkHelper.bKGD)) { + return new PngChunkBKGD(imgInfo); + } + if (id.equals(ChunkHelper.gAMA)) { + return new PngChunkGAMA(imgInfo); + } + if (id.equals(ChunkHelper.pHYs)) { + return new PngChunkPHYS(imgInfo); + } + if (id.equals(ChunkHelper.iCCP)) { + return new PngChunkICCP(imgInfo); + } + if (id.equals(ChunkHelper.tIME)) { + return new PngChunkTIME(imgInfo); + } + if (id.equals(ChunkHelper.tRNS)) { + return new PngChunkTRNS(imgInfo); + } + if (id.equals(ChunkHelper.cHRM)) { + return new PngChunkCHRM(imgInfo); + } + if (id.equals(ChunkHelper.sBIT)) { + return new PngChunkSBIT(imgInfo); + } + if (id.equals(ChunkHelper.sRGB)) { + return new PngChunkSRGB(imgInfo); + } + if (id.equals(ChunkHelper.hIST)) { + return new PngChunkHIST(imgInfo); + } + if (id.equals(ChunkHelper.sPLT)) { + return new PngChunkSPLT(imgInfo); + } + // apng + if (id.equals(PngChunkFDAT.ID)) { + return new PngChunkFDAT(imgInfo); + } + if (id.equals(PngChunkACTL.ID)) { + return new PngChunkACTL(imgInfo); + } + if (id.equals(PngChunkFCTL.ID)) { + return new PngChunkFCTL(imgInfo); + } + return null; + } + + /** + * This is used as last resort factory method. + *

      + * It creates a {@link PngChunkUNKNOWN} chunk. + */ + protected final PngChunk createEmptyChunkUnknown(String id, ImageInfo imgInfo) { + return new PngChunkUNKNOWN(id, imgInfo); + } + + /** + * Factory for chunks that are not in the original PNG standard. This can be + * overriden (but dont forget to call this also) + * + * @param id Chunk id , 4 letters + * @param imgInfo Usually not needed + * @return null if chunk id not recognized + */ + protected PngChunk createEmptyChunkExtended(String id, ImageInfo imgInfo) { + if (id.equals(PngChunkOFFS.ID)) { + return new PngChunkOFFS(imgInfo); + } + if (id.equals(PngChunkSTER.ID)) { + return new PngChunkSTER(imgInfo); + } + return null; // extend! + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkHelper.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkHelper.java new file mode 100644 index 0000000..1afbacc --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkHelper.java @@ -0,0 +1,287 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * Helper methods and constants related to Chunk processing. + * This should only be of interest to developers doing special chunk processing + * or extending the ChunkFactory + */ +public class ChunkHelper { + ChunkHelper() { + } + + public static final String IHDR = "IHDR"; + public static final String PLTE = "PLTE"; + public static final String IDAT = "IDAT"; + public static final String IEND = "IEND"; + public static final String cHRM = "cHRM"; + public static final String gAMA = "gAMA"; + public static final String iCCP = "iCCP"; + public static final String sBIT = "sBIT"; + public static final String sRGB = "sRGB"; + public static final String bKGD = "bKGD"; + public static final String hIST = "hIST"; + public static final String tRNS = "tRNS"; + public static final String pHYs = "pHYs"; + public static final String sPLT = "sPLT"; + public static final String tIME = "tIME"; + public static final String iTXt = "iTXt"; + public static final String tEXt = "tEXt"; + public static final String zTXt = "zTXt"; + + public static final byte[] b_IHDR = toBytesLatin1(IHDR); + public static final byte[] b_PLTE = toBytesLatin1(PLTE); + public static final byte[] b_IDAT = toBytesLatin1(IDAT); + public static final byte[] b_IEND = toBytesLatin1(IEND); + + /* + * static auxiliary buffer. any method that uses this should synchronize against + * this + */ + private static final byte[] tmpbuffer = new byte[4096]; + + /** + * Converts to bytes using Latin1 (ISO-8859-1) + */ + public static byte[] toBytesLatin1(String x) { + return x.getBytes(PngHelperInternal.charsetLatin1); + } + + /** + * Converts to String using Latin1 (ISO-8859-1) + */ + public static String toStringLatin1(byte[] x) { + return new String(x, PngHelperInternal.charsetLatin1); + } + + /** + * Converts to String using Latin1 (ISO-8859-1) + */ + public static String toStringLatin1(byte[] x, int offset, int len) { + return new String(x, offset, len, PngHelperInternal.charsetLatin1); + } + + /** + * Converts to bytes using UTF-8 + */ + public static byte[] toBytesUTF8(String x) { + return x.getBytes(PngHelperInternal.charsetUTF8); + } + + /** + * Converts to string using UTF-8 + */ + public static String toStringUTF8(byte[] x) { + return new String(x, PngHelperInternal.charsetUTF8); + } + + /** + * Converts to string using UTF-8 + */ + public static String toStringUTF8(byte[] x, int offset, int len) { + return new String(x, offset, len, PngHelperInternal.charsetUTF8); + } + + /** + * critical chunk : first letter is uppercase + */ + public static boolean isCritical(String id) { + return (Character.isUpperCase(id.charAt(0))); + } + + /** + * public chunk: second letter is uppercase + */ + public static boolean isPublic(String id) { // + return (Character.isUpperCase(id.charAt(1))); + } + + /** + * Safe to copy chunk: fourth letter is lower case + */ + public static boolean isSafeToCopy(String id) { + return (!Character.isUpperCase(id.charAt(3))); + } + + /** + * "Unknown" just means that our chunk factory (even when it has been + * augmented by client code) did not recognize its id + */ + public static boolean isUnknown(PngChunk c) { + return c instanceof PngChunkUNKNOWN; + } + + /** + * Finds position of null byte in array + * + * @param b + * @return -1 if not found + */ + public static int posNullByte(byte[] b) { + for (int i = 0; i < b.length; i++) { + if (b[i] == 0) { + return i; + } + } + return -1; + } + + /** + * Decides if a chunk should be loaded, according to a ChunkLoadBehaviour + * + * @param id + * @param behav + * @return true/false + */ + public static boolean shouldLoad(String id, ChunkLoadBehaviour behav) { + if (isCritical(id)) { + return true; + } + switch (behav) { + case LOAD_CHUNK_ALWAYS: + return true; + case LOAD_CHUNK_IF_SAFE: + return isSafeToCopy(id); + case LOAD_CHUNK_NEVER: + return false; + case LOAD_CHUNK_MOST_IMPORTANT: + return id.equals(PngChunkTRNS.ID); + } + return false; // should not reach here + } + + public final static byte[] compressBytes(byte[] ori, boolean compress) { + return compressBytes(ori, 0, ori.length, compress); + } + + public static byte[] compressBytes(byte[] ori, int offset, int len, boolean compress) { + try { + ByteArrayInputStream inb = new ByteArrayInputStream(ori, offset, len); + InputStream in = compress ? inb : new InflaterInputStream(inb); + ByteArrayOutputStream outb = new ByteArrayOutputStream(); + OutputStream out = compress ? new DeflaterOutputStream(outb) : outb; + shovelInToOut(in, out); + in.close(); + out.close(); + return outb.toByteArray(); + } catch (Exception e) { + throw new PngjException(e); + } + } + + /** + * Shovels all data from an input stream to an output stream. + */ + private static void shovelInToOut(InputStream in, OutputStream out) throws IOException { + synchronized (tmpbuffer) { + int len; + while ((len = in.read(tmpbuffer)) > 0) { + out.write(tmpbuffer, 0, len); + } + } + } + + /** + * Returns only the chunks that "match" the predicate + *

      + * See also trimList() + */ + public static List filterList(List target, ChunkPredicate predicateKeep) { + List result = new ArrayList(); + for (PngChunk element : target) { + if (predicateKeep.match(element)) { + result.add(element); + } + } + return result; + } + + /** + * Remove (in place) the chunks that "match" the predicate + *

      + * See also filterList + */ + public static int trimList(List target, ChunkPredicate predicateRemove) { + Iterator it = target.iterator(); + int cont = 0; + while (it.hasNext()) { + PngChunk c = it.next(); + if (predicateRemove.match(c)) { + it.remove(); + cont++; + } + } + return cont; + } + + /** + * Adhoc criteria: two ancillary chunks are "equivalent" ("practically same + * type") if they have same id and (perhaps, if multiple are allowed) if the + * match also in some "internal key" (eg: key for string values, palette for + * sPLT, etc) + *

      + * When we use this method, we implicitly assume that we don't allow/expect + * two "equivalent" chunks in a single PNG + *

      + * Notice that the use of this is optional, and that the PNG standard + * actually allows text chunks that have same key + * + * @return true if "equivalent" + */ + public static final boolean equivalent(PngChunk c1, PngChunk c2) { + if (c1 == c2) { + return true; + } + if (c1 == null || c2 == null || !c1.id.equals(c2.id)) { + return false; + } + if (c1.crit) { + return false; + } + // same id + if (c1.getClass() != c2.getClass()) { + return false; // should not happen + } + if (!c2.allowsMultiple()) { + return true; + } + if (c1 instanceof PngChunkTextVar) { + return ((PngChunkTextVar) c1).getKey().equals(((PngChunkTextVar) c2).getKey()); + } + if (c1 instanceof PngChunkSPLT) { + return ((PngChunkSPLT) c1).getPalName().equals(((PngChunkSPLT) c2).getPalName()); + } + // unknown chunks that allow multiple? consider they don't match + return false; + } + + public static boolean isText(PngChunk c) { + return c instanceof PngChunkTextVar; + } + + /** + * Convert four bytes to String (chunk id) + */ + public static String idFromBytes(byte[] buf, int offset) { + if (buf == null || buf.length < 4 + offset) { + return "?"; + } + return toStringLatin1(buf, offset, 4); + } + + public static Pattern CHUNK_ID_PAT = Pattern.compile("[a-zA-Z][a-zA-Z][A-Z][a-zA-Z]"); + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkLoadBehaviour.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkLoadBehaviour.java new file mode 100644 index 0000000..09bb9de --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkLoadBehaviour.java @@ -0,0 +1,25 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +/** + * What to do with ancillary (non-critical) chunks when reading. + */ +public enum ChunkLoadBehaviour { + /** + * All non-critical chunks are skipped + */ + LOAD_CHUNK_NEVER, + /** + * Load chunk if "safe to copy" + */ + LOAD_CHUNK_IF_SAFE, + /** + * Load only most important chunk: TRNS + */ + LOAD_CHUNK_MOST_IMPORTANT, + /** + * Load all chunks. + * Notice that other restrictions might apply, see + * PngReader.skipChunkMaxSize PngReader.skipChunkIds + */ + LOAD_CHUNK_ALWAYS +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkPredicate.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkPredicate.java new file mode 100644 index 0000000..f982b94 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkPredicate.java @@ -0,0 +1,14 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +/** + * Decides if another chunk "matches", according to some criterion. + */ +public interface ChunkPredicate { + /** + * The other chunk matches with this one. + * + * @param chunk chunk + * @return true if match + */ + boolean match(PngChunk chunk); +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkRaw.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkRaw.java new file mode 100644 index 0000000..a94464d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunkRaw.java @@ -0,0 +1,191 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.ByteArrayInputStream; +import java.io.OutputStream; +import java.util.logging.Logger; +import java.util.zip.CRC32; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjBadCrcException; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjOutputException; + +/** + * Raw (physical) chunk. + * Short lived object, to be created while serialing/deserializing Do not reuse + * it for different chunks. + * See http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html + */ +public class ChunkRaw { + private static final Logger LOGGER = Logger.getLogger(ChunkRaw.class.getName()); + /** + * The length counts only the data field, not itself, the chunk type code, + * or the CRC. Zero is a valid length. Although encoders and decoders should + * treat the length as unsigned, its value must not exceed 231-1 bytes. + */ + public final int len; + + /** + * A 4-byte chunk type code. uppercase and lowercase ASCII letters + */ + public final byte[] idbytes; + public final String id; + + /** + * The data bytes appropriate to the chunk type, if any. This field can be + * of zero length. Does not include crc. If it's null, it means that the + * data is ot available + */ + public byte[] data = null; + /** + * @see ChunkRaw#getOffset() + */ + private long offset = 0; + + /** + * A 4-byte CRC (Cyclic Redundancy Check) calculated on the preceding bytes + * in the chunk, including the chunk type code and chunk data fields, but + * not including the length field. + */ + public byte[] crcval = new byte[4]; + + private CRC32 crcengine; // lazily instantiated + + public ChunkRaw(int len, String id, boolean alloc) { + this.len = len; + this.id = id; + this.idbytes = ChunkHelper.toBytesLatin1(id); + for (int i = 0; i < 4; i++) { + if (idbytes[i] < 0x41 || idbytes[i] > 0x7a || (idbytes[i] > 0x5a && idbytes[i] < 0x61)) { + throw new PngjException("Bad id chunk: must be ascii letters " + id); + } + } + if (alloc) { + allocData(); + } + } + + public ChunkRaw(int len, byte[] idbytes, boolean alloc) { + this(len, ChunkHelper.toStringLatin1(idbytes), alloc); + } + + public void allocData() { // TODO: not public + if (data == null || data.length < len) { + data = new byte[len]; + } + } + + /** + * this is called after setting data, before writing to os + */ + private void computeCrcForWriting() { + crcengine = new CRC32(); + crcengine.update(idbytes, 0, 4); + if (len > 0) { + crcengine.update(data, 0, len); // + } + PngHelperInternal.writeInt4tobytes((int) crcengine.getValue(), crcval, 0); + } + + /** + * Computes the CRC and writes to the stream. If error, a + * PngjOutputException is thrown + *

      + * Note that this is only used for non idat chunks + */ + public void writeChunk(OutputStream os) { + writeChunkHeader(os); + if (len > 0) { + if (data == null) { + throw new PngjOutputException("cannot write chunk, raw chunk data is null [" + id + "]"); + } + PngHelperInternal.writeBytes(os, data, 0, len); + } + computeCrcForWriting(); + writeChunkCrc(os); + } + + public void writeChunkHeader(OutputStream os) { + if (idbytes.length != 4) { + throw new PngjOutputException("bad chunkid [" + id + "]"); + } + PngHelperInternal.writeInt4(os, len); + PngHelperInternal.writeBytes(os, idbytes); + } + + public void writeChunkCrc(OutputStream os) { + PngHelperInternal.writeBytes(os, crcval, 0, 4); + } + + public void checkCrc(boolean throwExcep) { + int crcComputed = (int) crcengine.getValue(); + int crcExpected = PngHelperInternal.readInt4fromBytes(crcval, 0); + if (crcComputed != crcExpected) { + String msg = String.format("Bad CRC in chunk: %s (offset:%d). Expected:%x Got:%x", id, offset, crcExpected, + crcComputed); + if (throwExcep) { + throw new PngjBadCrcException(msg); + } else { + LOGGER.warning(msg); + } + } + } + + public void updateCrc(byte[] buf, int off, int len) { + if (crcengine == null) { + crcengine = new CRC32(); + } + crcengine.update(buf, off, len); + } + + ByteArrayInputStream getAsByteStream() { // only the data + return new ByteArrayInputStream(data); + } + + /** + * offset in the full PNG stream, in bytes. only informational, for read + * chunks (0=NA) + */ + public long getOffset() { + return offset; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public String toString() { + return "chunkid=" + ChunkHelper.toStringLatin1(idbytes) + " len=" + len; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + (int) (offset ^ (offset >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ChunkRaw other = (ChunkRaw) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + return offset == other.offset; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksList.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksList.java new file mode 100644 index 0000000..b46da74 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksList.java @@ -0,0 +1,169 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.util.ArrayList; +import java.util.List; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * All chunks that form an image, read or to be written. + *

      + * chunks include all chunks, but IDAT is a single pseudo chunk without data + **/ +public class ChunksList { + // ref: http://www.w3.org/TR/PNG/#table53 + public static final int CHUNK_GROUP_0_IDHR = 0; // required - single + public static final int CHUNK_GROUP_1_AFTERIDHR = 1; // optional - multiple + public static final int CHUNK_GROUP_2_PLTE = 2; // optional - single + public static final int CHUNK_GROUP_3_AFTERPLTE = 3; // optional - multple + public static final int CHUNK_GROUP_4_IDAT = 4; // required (single pseudo chunk) + public static final int CHUNK_GROUP_5_AFTERIDAT = 5; // optional - multple + public static final int CHUNK_GROUP_6_END = 6; // only 1 chunk - requried + + /** + * All chunks, read (or written) + *

      + * But IDAT is a single pseudo chunk without data + */ + List chunks = new ArrayList(); + + final ImageInfo imageInfo; // only required for writing + + boolean withPlte = false; + + public ChunksList(ImageInfo imfinfo) { + this.imageInfo = imfinfo; + } + + /** + * WARNING: this does NOT return a copy, but the list itself. The called + * should not modify this directly! Don't use this to manipulate the chunks. + */ + public List getChunks() { + return chunks; + } + + protected static List getXById(final List list, final String id, final String innerid) { + if (innerid == null) { + return ChunkHelper.filterList(list, new ChunkPredicate() { + public boolean match(PngChunk c) { + return c.id.equals(id); + } + }); + } else { + return ChunkHelper.filterList(list, new ChunkPredicate() { + public boolean match(PngChunk c) { + if (!c.id.equals(id)) { + return false; + } + if (c instanceof PngChunkTextVar && !((PngChunkTextVar) c).getKey().equals(innerid)) { + return false; + } + return !(c instanceof PngChunkSPLT) || ((PngChunkSPLT) c).getPalName().equals(innerid); + } + }); + } + } + + /** + * Adds chunk in next position. This is used onyl by the pngReader + */ + public void appendReadChunk(PngChunk chunk, int chunkGroup) { + chunk.setChunkGroup(chunkGroup); + chunks.add(chunk); + if (chunk.id.equals(PngChunkPLTE.ID)) { + withPlte = true; + } + } + + /** + * All chunks with this ID + * + * @param id + * @return List, empty if none + */ + public List getById(final String id) { + return getById(id, null); + } + + /** + * If innerid!=null and the chunk is PngChunkTextVar or PngChunkSPLT, it's + * filtered by that id + * + * @param id + * @return List, empty if none + */ + public List getById(final String id, final String innerid) { + return getXById(chunks, id, innerid); + } + + /** + * Returns only one chunk + * + * @param id + * @return First chunk found, null if not found + */ + public PngChunk getById1(final String id) { + return getById1(id, false); + } + + /** + * Returns only one chunk or null if nothing found - does not include queued + *

      + * If more than one chunk is found, then an exception is thrown + * (failifMultiple=true or chunk is single) or the last one is returned + * (failifMultiple=false) + **/ + public PngChunk getById1(final String id, final boolean failIfMultiple) { + return getById1(id, null, failIfMultiple); + } + + /** + * Returns only one chunk or null if nothing found - does not include queued + *

      + * If more than one chunk (after filtering by inner id) is found, then an + * exception is thrown (failifMultiple=true or chunk is single) or the last + * one is returned (failifMultiple=false) + **/ + public PngChunk getById1(final String id, final String innerid, final boolean failIfMultiple) { + List list = getById(id, innerid); + if (list.isEmpty()) { + return null; + } + if (list.size() > 1 && (failIfMultiple || !list.get(0).allowsMultiple())) { + throw new PngjException("unexpected multiple chunks id=" + id); + } + return list.get(list.size() - 1); + } + + /** + * Finds all chunks "equivalent" to this one + * + * @param c2 + * @return Empty if nothing found + */ + public List getEquivalent(final PngChunk c2) { + return ChunkHelper.filterList(chunks, new ChunkPredicate() { + public boolean match(PngChunk c) { + return ChunkHelper.equivalent(c, c2); + } + }); + } + + public String toString() { + return "ChunkList: read: " + chunks.size(); + } + + /** + * for debugging + */ + public String toStringFull() { + StringBuilder sb = new StringBuilder(toString()); + sb.append("\n Read:\n"); + for (PngChunk chunk : chunks) { + sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n"); + } + return sb.toString(); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksListForWrite.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksListForWrite.java new file mode 100644 index 0000000..bd2cea9 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/ChunksListForWrite.java @@ -0,0 +1,198 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjOutputException; + +public class ChunksListForWrite extends ChunksList { + + /** + * chunks not yet writen - does not include IHDR, IDAT, END, perhaps yes + * PLTE + */ + private final List queuedChunks = new ArrayList(); + + // redundant, just for eficciency + private final HashMap alreadyWrittenKeys = new HashMap(); + + public ChunksListForWrite(ImageInfo imfinfo) { + super(imfinfo); + } + + /** + * Same as getById(), but looking in the queued chunks + */ + public List getQueuedById(final String id) { + return getQueuedById(id, null); + } + + /** + * Same as getById(), but looking in the queued chunks + */ + public List getQueuedById(final String id, final String innerid) { + return getXById(queuedChunks, id, innerid); + } + + /** + * Same as getById1(), but looking in the queued chunks + **/ + public PngChunk getQueuedById1(final String id, final String innerid, final boolean failIfMultiple) { + List list = getQueuedById(id, innerid); + if (list.isEmpty()) { + return null; + } + if (list.size() > 1 && (failIfMultiple || !list.get(0).allowsMultiple())) { + throw new PngjException("unexpected multiple chunks id=" + id); + } + return list.get(list.size() - 1); + } + + /** + * Same as getById1(), but looking in the queued chunks + **/ + public PngChunk getQueuedById1(final String id, final boolean failIfMultiple) { + return getQueuedById1(id, null, failIfMultiple); + } + + /** + * Same as getById1(), but looking in the queued chunks + **/ + public PngChunk getQueuedById1(final String id) { + return getQueuedById1(id, false); + } + + /** + * Finds all chunks "equivalent" to this one + * + * @param c2 + * @return Empty if nothing found + */ + public List getQueuedEquivalent(final PngChunk c2) { + return ChunkHelper.filterList(queuedChunks, new ChunkPredicate() { + public boolean match(PngChunk c) { + return ChunkHelper.equivalent(c, c2); + } + }); + } + + /** + * Remove Chunk: only from queued + *

      + * WARNING: this depends on c.equals() implementation, which is + * straightforward for SingleChunks. For MultipleChunks, it will normally + * check for reference equality! + */ + public boolean removeChunk(PngChunk c) { + if (c == null) { + return false; + } + return queuedChunks.remove(c); + } + + /** + * Adds chunk to queue + *

      + * If there + * + * @param c + */ + public boolean queue(PngChunk c) { + queuedChunks.add(c); + return true; + } + + /** + * this should be called only for ancillary chunks and PLTE (groups 1 - 3 - + * 5) + **/ + private static boolean shouldWrite(PngChunk c, int currentGroup) { + if (currentGroup == CHUNK_GROUP_2_PLTE) { + return c.id.equals(ChunkHelper.PLTE); + } + if (currentGroup % 2 == 0) { + throw new PngjOutputException("bad chunk group?"); + } + int minChunkGroup, maxChunkGroup; + if (c.getOrderingConstraint().mustGoBeforePLTE()) { + minChunkGroup = maxChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR; + } else if (c.getOrderingConstraint().mustGoBeforeIDAT()) { + maxChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE; + minChunkGroup = c.getOrderingConstraint().mustGoAfterPLTE() ? ChunksList.CHUNK_GROUP_3_AFTERPLTE + : ChunksList.CHUNK_GROUP_1_AFTERIDHR; + } else { + maxChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT; + minChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR; + } + + int preferred = maxChunkGroup; + if (c.hasPriority()) { + preferred = minChunkGroup; + } + if (ChunkHelper.isUnknown(c) && c.getChunkGroup() > 0) { + preferred = c.getChunkGroup(); + } + if (currentGroup == preferred) { + return true; + } + return currentGroup > preferred && currentGroup <= maxChunkGroup; + } + + public int writeChunks(OutputStream os, int currentGroup) { + int cont = 0; + Iterator it = queuedChunks.iterator(); + while (it.hasNext()) { + PngChunk c = it.next(); + if (!shouldWrite(c, currentGroup)) { + continue; + } + if (ChunkHelper.isCritical(c.id) && !c.id.equals(ChunkHelper.PLTE)) { + throw new PngjOutputException("bad chunk queued: " + c); + } + if (alreadyWrittenKeys.containsKey(c.id) && !c.allowsMultiple()) { + throw new PngjOutputException("duplicated chunk does not allow multiple: " + c); + } + c.write(os); + chunks.add(c); + alreadyWrittenKeys.put(c.id, alreadyWrittenKeys.containsKey(c.id) ? alreadyWrittenKeys.get(c.id) + 1 : 1); + c.setChunkGroup(currentGroup); + it.remove(); + cont++; + } + return cont; + } + + /** + * warning: this is NOT a copy, do not modify + */ + public List getQueuedChunks() { + return queuedChunks; + } + + public String toString() { + return "ChunkList: written: " + getChunks().size() + " queue: " + queuedChunks.size(); + } + + /** + * for debugging + */ + public String toStringFull() { + StringBuilder sb = new StringBuilder(toString()); + sb.append("\n Written:\n"); + for (PngChunk chunk : getChunks()) { + sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n"); + } + if (!queuedChunks.isEmpty()) { + sb.append(" Queued:\n"); + for (PngChunk chunk : queuedChunks) { + sb.append(chunk).append("\n"); + } + + } + return sb.toString(); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngBadCharsetException.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngBadCharsetException.java new file mode 100644 index 0000000..bd3369c --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngBadCharsetException.java @@ -0,0 +1,20 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +public class PngBadCharsetException extends PngjException { + private static final long serialVersionUID = 1L; + + public PngBadCharsetException(String message, Throwable cause) { + super(message, cause); + } + + public PngBadCharsetException(String message) { + super(message); + } + + public PngBadCharsetException(Throwable cause) { + super(cause); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunk.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunk.java new file mode 100644 index 0000000..5f18971 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunk.java @@ -0,0 +1,227 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.OutputStream; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjExceptionInternal; + +/** + * Represents a instance of a PNG chunk. + *

      + * See + * http://www + * .libpng.org/pub/png/spec/1.2/PNG-Chunks .html + *

      + * Concrete classes should extend {@link PngChunkSingle} or + * {@link PngChunkMultiple} + *

      + * Note that some methods/fields are type-specific (getOrderingConstraint(), + * allowsMultiple()),
      + * some are 'almost' type-specific (id,crit,pub,safe; the exception is + * PngUKNOWN),
      + * and the rest are instance-specific + */ +public abstract class PngChunk { + + /** + * Chunk-id: 4 letters + */ + public final String id; + /** + * Autocomputed at creation time + */ + public final boolean crit, pub, safe; + + protected final ImageInfo imgInfo; + + protected ChunkRaw raw; + + // For writing. Queued chunks with high priority will be written as soon as possible + private boolean priority = false; + + protected int chunkGroup = -1; // chunk group where it was read or writen + + /** + * Possible ordering constraint for a PngChunk type -only relevant for + * ancillary chunks. Theoretically, there could be more general constraints, + * but these cover the constraints for standard chunks. + */ + public enum ChunkOrderingConstraint { + /** + * no ordering constraint + */ + NONE, + /** + * Must go before PLTE (and hence, also before IDAT) + */ + BEFORE_PLTE_AND_IDAT, + /** + * Must go after PLTE (if exists) but before IDAT + */ + AFTER_PLTE_BEFORE_IDAT, + /** + * Must go after PLTE (and it must exist) but before IDAT + */ + AFTER_PLTE_BEFORE_IDAT_PLTE_REQUIRED, + /** + * Must before IDAT (before or after PLTE) + */ + BEFORE_IDAT, + /** + * After IDAT (this restriction does not apply to the standard PNG + * chunks) + */ + AFTER_IDAT, + /** + * Does not apply + */ + NA; + + public boolean mustGoBeforePLTE() { + return this == BEFORE_PLTE_AND_IDAT; + } + + public boolean mustGoBeforeIDAT() { + return this == BEFORE_IDAT || this == BEFORE_PLTE_AND_IDAT || this == AFTER_PLTE_BEFORE_IDAT; + } + + /** + * after pallete, if exists + */ + public boolean mustGoAfterPLTE() { + return this == AFTER_PLTE_BEFORE_IDAT || this == AFTER_PLTE_BEFORE_IDAT_PLTE_REQUIRED; + } + + public boolean mustGoAfterIDAT() { + return this == AFTER_IDAT; + } + + public boolean isOk(int currentChunkGroup, boolean hasplte) { + if (this == NONE) { + return true; + } else if (this == BEFORE_IDAT) { + return currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT; + } else if (this == BEFORE_PLTE_AND_IDAT) { + return currentChunkGroup < ChunksList.CHUNK_GROUP_2_PLTE; + } else if (this == AFTER_PLTE_BEFORE_IDAT) { + return hasplte ? currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT + : (currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT + && currentChunkGroup > ChunksList.CHUNK_GROUP_2_PLTE); + } else if (this == AFTER_IDAT) { + return currentChunkGroup > ChunksList.CHUNK_GROUP_4_IDAT; + } + return false; + } + } + + public PngChunk(String id, ImageInfo imgInfo) { + this.id = id; + this.imgInfo = imgInfo; + this.crit = ChunkHelper.isCritical(id); + this.pub = ChunkHelper.isPublic(id); + this.safe = ChunkHelper.isSafeToCopy(id); + } + + protected final ChunkRaw createEmptyChunk(int len, boolean alloc) { + ChunkRaw c = new ChunkRaw(len, ChunkHelper.toBytesLatin1(id), alloc); + return c; + } + + /** + * In which "chunkGroup" (see {@link ChunksList}for definition) this chunks + * instance was read or written. + *

      + * -1 if not read or written (eg, queued) + */ + final public int getChunkGroup() { + return chunkGroup; + } + + /** + * @see #getChunkGroup() + */ + final void setChunkGroup(int chunkGroup) { + this.chunkGroup = chunkGroup; + } + + public boolean hasPriority() { + return priority; + } + + public void setPriority(boolean priority) { + this.priority = priority; + } + + final void write(OutputStream os) { + if (raw == null || raw.data == null) { + raw = createRawChunk(); + } + if (raw == null) { + throw new PngjExceptionInternal("null chunk ! creation failed for " + this); + } + raw.writeChunk(os); + } + + /** + * Creates the physical chunk. This is used when writing (serialization). + * Each particular chunk class implements its own logic. + * + * @return A newly allocated and filled raw chunk + */ + public abstract ChunkRaw createRawChunk(); + + /** + * Parses raw chunk and fill inside data. This is used when reading + * (deserialization). Each particular chunk class implements its own logic. + */ + protected abstract void parseFromRaw(ChunkRaw c); + + /** + * See {@link PngChunkMultiple} and {@link PngChunkSingle} + * + * @return true if PNG accepts multiple chunks of this class + */ + protected abstract boolean allowsMultiple(); + + public ChunkRaw getRaw() { + return raw; + } + + void setRaw(ChunkRaw raw) { + this.raw = raw; + } + + /** + * @see ChunkRaw#len + */ + public int getLen() { + return raw != null ? raw.len : -1; + } + + /** + * @see ChunkRaw#getOffset() + */ + public long getOffset() { + return raw != null ? raw.getOffset() : -1; + } + + /** + * This signals that the raw chunk (serialized data) as invalid, so that + * it's regenerated on write. This should be called for the (infrequent) + * case of chunks that were copied from a PngReader and we want to manually + * modify it. + */ + public void invalidateRawData() { + raw = null; + } + + /** + * see {@link ChunkOrderingConstraint} + */ + public abstract ChunkOrderingConstraint getOrderingConstraint(); + + @Override + public String toString() { + return "chunk id= " + id + " (len=" + getLen() + " offset=" + getOffset() + ")"; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkACTL.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkACTL.java new file mode 100644 index 0000000..e652bb5 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkACTL.java @@ -0,0 +1,57 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; + +/** + * acTL chunk. For APGN, not PGN standard + *

      + * see + * https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk + *

      + */ +public class PngChunkACTL extends PngChunkSingle { + public final static String ID = "acTL"; + private int numFrames; + private int numPlays; + + public PngChunkACTL(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(8, true); + PngHelperInternal.writeInt4tobytes(numFrames, c.data, 0); + PngHelperInternal.writeInt4tobytes(numPlays, c.data, 4); + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + numFrames = PngHelperInternal.readInt4fromBytes(chunk.data, 0); + numPlays = PngHelperInternal.readInt4fromBytes(chunk.data, 4); + } + + public int getNumFrames() { + return numFrames; + } + + public void setNumFrames(int numFrames) { + this.numFrames = numFrames; + } + + public int getNumPlays() { + return numPlays; + } + + public void setNumPlays(int numPlays) { + this.numPlays = numPlays; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkBKGD.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkBKGD.java new file mode 100644 index 0000000..14908c6 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkBKGD.java @@ -0,0 +1,116 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * bKGD Chunk. + *

      + * see {@link http://www.w3.org/TR/PNG/#11bKGD} + *

      + * This chunk structure depends on the image type + */ +public class PngChunkBKGD extends PngChunkSingle { + public final static String ID = ChunkHelper.bKGD; + // only one of these is meaningful + private int gray; + private int red, green, blue; + private int paletteIndex; + + public PngChunkBKGD(ImageInfo info) { + super(ChunkHelper.bKGD, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = null; + if (imgInfo.greyscale) { + c = createEmptyChunk(2, true); + PngHelperInternal.writeInt2tobytes(gray, c.data, 0); + } else if (imgInfo.indexed) { + c = createEmptyChunk(1, true); + c.data[0] = (byte) paletteIndex; + } else { + c = createEmptyChunk(6, true); + PngHelperInternal.writeInt2tobytes(red, c.data, 0); + PngHelperInternal.writeInt2tobytes(green, c.data, 0); + PngHelperInternal.writeInt2tobytes(blue, c.data, 0); + } + return c; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + if (imgInfo.greyscale) { + gray = PngHelperInternal.readInt2fromBytes(c.data, 0); + } else if (imgInfo.indexed) { + paletteIndex = c.data[0] & 0xff; + } else { + red = PngHelperInternal.readInt2fromBytes(c.data, 0); + green = PngHelperInternal.readInt2fromBytes(c.data, 2); + blue = PngHelperInternal.readInt2fromBytes(c.data, 4); + } + } + + /** + * Set gray value (0-255 if bitdept=8) + * + * @param gray + */ + public void setGray(int gray) { + if (!imgInfo.greyscale) { + throw new PngjException("only gray images support this"); + } + this.gray = gray; + } + + public int getGray() { + if (!imgInfo.greyscale) { + throw new PngjException("only gray images support this"); + } + return gray; + } + + /** + * Set pallette index + */ + public void setPaletteIndex(int i) { + if (!imgInfo.indexed) { + throw new PngjException("only indexed (pallete) images support this"); + } + this.paletteIndex = i; + } + + public int getPaletteIndex() { + if (!imgInfo.indexed) { + throw new PngjException("only indexed (pallete) images support this"); + } + return paletteIndex; + } + + /** + * Set rgb values + */ + public void setRGB(int r, int g, int b) { + if (imgInfo.greyscale || imgInfo.indexed) { + throw new PngjException("only rgb or rgba images support this"); + } + red = r; + green = g; + blue = b; + } + + public int[] getRGB() { + if (imgInfo.greyscale || imgInfo.indexed) { + throw new PngjException("only rgb or rgba images support this"); + } + return new int[]{red, green, blue}; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkCHRM.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkCHRM.java new file mode 100644 index 0000000..079f294 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkCHRM.java @@ -0,0 +1,76 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * cHRM chunk. + *

      + * see http://www.w3.org/TR/PNG/#11cHRM + */ +public class PngChunkCHRM extends PngChunkSingle { + public final static String ID = ChunkHelper.cHRM; + + // http://www.w3.org/TR/PNG/#11cHRM + private double whitex, whitey; + private double redx, redy; + private double greenx, greeny; + private double bluex, bluey; + + public PngChunkCHRM(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = null; + c = createEmptyChunk(32, true); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(whitex), c.data, 0); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(whitey), c.data, 4); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(redx), c.data, 8); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(redy), c.data, 12); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(greenx), c.data, 16); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(greeny), c.data, 20); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(bluex), c.data, 24); + PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(bluey), c.data, 28); + return c; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + if (c.len != 32) { + throw new PngjException("bad chunk " + c); + } + whitex = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 0)); + whitey = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 4)); + redx = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 8)); + redy = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 12)); + greenx = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 16)); + greeny = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 20)); + bluex = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 24)); + bluey = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 28)); + } + + public void setChromaticities(double whitex, double whitey, double redx, double redy, double greenx, double greeny, + double bluex, double bluey) { + this.whitex = whitex; + this.redx = redx; + this.greenx = greenx; + this.bluex = bluex; + this.whitey = whitey; + this.redy = redy; + this.greeny = greeny; + this.bluey = bluey; + } + + public double[] getChromaticities() { + return new double[]{whitex, whitey, redx, redy, greenx, greeny, bluex, bluey}; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFCTL.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFCTL.java new file mode 100644 index 0000000..5bb56c0 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFCTL.java @@ -0,0 +1,158 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; + +/** + * fcTL chunk. For APGN, not PGN standard + *

      + * see + * https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk + *

      + */ +public class PngChunkFCTL extends PngChunkMultiple { + public final static String ID = "fcTL"; + + public final static byte APNG_DISPOSE_OP_NONE = 0; + public final static byte APNG_DISPOSE_OP_BACKGROUND = 1; + public final static byte APNG_DISPOSE_OP_PREVIOUS = 2; + public final static byte APNG_BLEND_OP_SOURCE = 0; + public final static byte APNG_BLEND_OP_OVER = 1; + + private int seqNum; + private int width, height, xOff, yOff; + private int delayNum, delayDen; + private byte disposeOp, blendOp; + + public PngChunkFCTL(ImageInfo info) { + super(ID, info); + } + + public ImageInfo getEquivImageInfo() { + return new ImageInfo(width, height, imgInfo.bitDepth, imgInfo.alpha, imgInfo.greyscale, imgInfo.indexed); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NONE; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(8, true); + int off = 0; + PngHelperInternal.writeInt4tobytes(seqNum, c.data, off); + off += 4; + PngHelperInternal.writeInt4tobytes(width, c.data, off); + off += 4; + PngHelperInternal.writeInt4tobytes(height, c.data, off); + off += 4; + PngHelperInternal.writeInt4tobytes(xOff, c.data, off); + off += 4; + PngHelperInternal.writeInt4tobytes(yOff, c.data, off); + off += 4; + PngHelperInternal.writeInt2tobytes(delayNum, c.data, off); + off += 2; + PngHelperInternal.writeInt2tobytes(delayDen, c.data, off); + off += 2; + c.data[off] = disposeOp; + off += 1; + c.data[off] = blendOp; + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + int off = 0; + seqNum = PngHelperInternal.readInt4fromBytes(chunk.data, off); + off += 4; + width = PngHelperInternal.readInt4fromBytes(chunk.data, off); + off += 4; + height = PngHelperInternal.readInt4fromBytes(chunk.data, off); + off += 4; + xOff = PngHelperInternal.readInt4fromBytes(chunk.data, off); + off += 4; + yOff = PngHelperInternal.readInt4fromBytes(chunk.data, off); + off += 4; + delayNum = PngHelperInternal.readInt2fromBytes(chunk.data, off); + off += 2; + delayDen = PngHelperInternal.readInt2fromBytes(chunk.data, off); + off += 2; + disposeOp = chunk.data[off]; + off += 1; + blendOp = chunk.data[off]; + } + + public int getSeqNum() { + return seqNum; + } + + public void setSeqNum(int seqNum) { + this.seqNum = seqNum; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getxOff() { + return xOff; + } + + public void setxOff(int xOff) { + this.xOff = xOff; + } + + public int getyOff() { + return yOff; + } + + public void setyOff(int yOff) { + this.yOff = yOff; + } + + public int getDelayNum() { + return delayNum; + } + + public void setDelayNum(int delayNum) { + this.delayNum = delayNum; + } + + public int getDelayDen() { + return delayDen; + } + + public void setDelayDen(int delayDen) { + this.delayDen = delayDen; + } + + public byte getDisposeOp() { + return disposeOp; + } + + public void setDisposeOp(byte disposeOp) { + this.disposeOp = disposeOp; + } + + public byte getBlendOp() { + return blendOp; + } + + public void setBlendOp(byte blendOp) { + this.blendOp = blendOp; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFDAT.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFDAT.java new file mode 100644 index 0000000..b396563 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkFDAT.java @@ -0,0 +1,72 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * fdAT chunk. For APGN, not PGN standard + *

      + * see + * https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk + *

      + * This implementation does not support buffering, this should be not managed + * similar to a IDAT chunk + */ +public class PngChunkFDAT extends PngChunkMultiple { + public final static String ID = "fdAT"; + private int seqNum; + private byte[] buffer; // normally not allocated - if so, it's the raw data, includes the 4bytes seqNum + int datalen; // length of idat data, excluding seqNUm (= chunk.len-4) + + public PngChunkFDAT(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.AFTER_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + if (buffer == null) { + throw new PngjException("not buffered"); + } + ChunkRaw c = createEmptyChunk(datalen + 4, false); + c.data = buffer; // shallow copy! + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + seqNum = PngHelperInternal.readInt4fromBytes(chunk.data, 0); + datalen = chunk.len - 4; + buffer = chunk.data; + } + + public int getSeqNum() { + return seqNum; + } + + public void setSeqNum(int seqNum) { + this.seqNum = seqNum; + } + + public byte[] getBuffer() { + return buffer; + } + + public void setBuffer(byte[] buffer) { + this.buffer = buffer; + } + + public int getDatalen() { + return datalen; + } + + public void setDatalen(int datalen) { + this.datalen = datalen; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkGAMA.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkGAMA.java new file mode 100644 index 0000000..1402ac0 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkGAMA.java @@ -0,0 +1,52 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * gAMA chunk. + *

      + * see http://www.w3.org/TR/PNG/#11gAMA + */ +public class PngChunkGAMA extends PngChunkSingle { + public final static String ID = ChunkHelper.gAMA; + + // http://www.w3.org/TR/PNG/#11gAMA + private double gamma; + + public PngChunkGAMA(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(4, true); + int g = (int) (gamma * 100000 + 0.5); + PngHelperInternal.writeInt4tobytes(g, c.data, 0); + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + if (chunk.len != 4) { + throw new PngjException("bad chunk " + chunk); + } + int g = PngHelperInternal.readInt4fromBytes(chunk.data, 0); + gamma = ((double) g) / 100000.0; + } + + public double getGamma() { + return gamma; + } + + public void setGamma(double gamma) { + this.gamma = gamma; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkHIST.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkHIST.java new file mode 100644 index 0000000..44ab3a7 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkHIST.java @@ -0,0 +1,60 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * hIST chunk. + *

      + * see http://www.w3.org/TR/PNG/#11hIST
      + * only for palette images + */ +public class PngChunkHIST extends PngChunkSingle { + public final static String ID = ChunkHelper.hIST; + + private int[] hist = new int[0]; // should have same lenght as palette + + public PngChunkHIST(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + if (!imgInfo.indexed) { + throw new PngjException("only indexed images accept a HIST chunk"); + } + int nentries = c.data.length / 2; + hist = new int[nentries]; + for (int i = 0; i < hist.length; i++) { + hist[i] = PngHelperInternal.readInt2fromBytes(c.data, i * 2); + } + } + + @Override + public ChunkRaw createRawChunk() { + if (!imgInfo.indexed) { + throw new PngjException("only indexed images accept a HIST chunk"); + } + ChunkRaw c = null; + c = createEmptyChunk(hist.length * 2, true); + for (int i = 0; i < hist.length; i++) { + PngHelperInternal.writeInt2tobytes(hist[i], c.data, i * 2); + } + return c; + } + + public int[] getHist() { + return hist; + } + + public void setHist(int[] hist) { + this.hist = hist; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkICCP.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkICCP.java new file mode 100644 index 0000000..24653b0 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkICCP.java @@ -0,0 +1,77 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * iCCP chunk. + *

      + * See {@link http://www.w3.org/TR/PNG/#11iCCP} + */ +public class PngChunkICCP extends PngChunkSingle { + public final static String ID = ChunkHelper.iCCP; + + // http://www.w3.org/TR/PNG/#11iCCP + private String profileName; + private byte[] compressedProfile; // copmression/decopmresion is done in getter/setter + + public PngChunkICCP(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(profileName.length() + compressedProfile.length + 2, true); + System.arraycopy(ChunkHelper.toBytesLatin1(profileName), 0, c.data, 0, profileName.length()); + c.data[profileName.length()] = 0; + c.data[profileName.length() + 1] = 0; + System.arraycopy(compressedProfile, 0, c.data, profileName.length() + 2, compressedProfile.length); + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + int pos0 = ChunkHelper.posNullByte(chunk.data); + profileName = ChunkHelper.toStringLatin1(chunk.data, 0, pos0); + int comp = (chunk.data[pos0 + 1] & 0xff); + if (comp != 0) { + throw new PngjException("bad compression for ChunkTypeICCP"); + } + int compdatasize = chunk.data.length - (pos0 + 2); + compressedProfile = new byte[compdatasize]; + System.arraycopy(chunk.data, pos0 + 2, compressedProfile, 0, compdatasize); + } + + /** + * The profile should be uncompressed bytes + */ + public void setProfileNameAndContent(String name, byte[] profile) { + profileName = name; + compressedProfile = ChunkHelper.compressBytes(profile, true); + } + + public void setProfileNameAndContent(String name, String profile) { + setProfileNameAndContent(name, ChunkHelper.toBytesLatin1(profile)); + } + + public String getProfileName() { + return profileName; + } + + /** + * uncompressed + **/ + public byte[] getProfile() { + return ChunkHelper.compressBytes(compressedProfile, false); + } + + public String getProfileAsString() { + return ChunkHelper.toStringLatin1(getProfile()); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIDAT.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIDAT.java new file mode 100644 index 0000000..b471b82 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIDAT.java @@ -0,0 +1,35 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * IDAT chunk. + *

      + * see http://www.w3.org/TR/PNG/#11IDAT + *

      + * This is dummy placeholder - we write/read this chunk (actually several) by + * special code. + */ +public class PngChunkIDAT extends PngChunkMultiple { + public final static String ID = ChunkHelper.IDAT; + + // http://www.w3.org/TR/PNG/#11IDAT + public PngChunkIDAT(ImageInfo i) { + super(ID, i); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NA; + } + + @Override + public ChunkRaw createRawChunk() {// does nothing + return null; + } + + @Override + public void parseFromRaw(ChunkRaw c) { // does nothing + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIEND.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIEND.java new file mode 100644 index 0000000..ed4655e --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIEND.java @@ -0,0 +1,35 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * IEND chunk. + *

      + * see http://www.w3.org/TR/PNG/#11IEND + */ +public class PngChunkIEND extends PngChunkSingle { + public final static String ID = ChunkHelper.IEND; + + // http://www.w3.org/TR/PNG/#11IEND + // this is a dummy placeholder + public PngChunkIEND(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NA; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = new ChunkRaw(0, ChunkHelper.b_IEND, false); + return c; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + // this is not used + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIHDR.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIHDR.java new file mode 100644 index 0000000..e5cbbec --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkIHDR.java @@ -0,0 +1,195 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.ByteArrayInputStream; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjInputException; + +/** + * IHDR chunk. + *

      + * see http://www.w3.org/TR/PNG/#11IHDR + *

      + * This is a special critical Chunk. + */ +public class PngChunkIHDR extends PngChunkSingle { + public final static String ID = ChunkHelper.IHDR; + + private int cols; + private int rows; + private int bitspc; + private int colormodel; + private int compmeth; + private int filmeth; + private int interlaced; + + // http://www.w3.org/TR/PNG/#11IHDR + // + public PngChunkIHDR(ImageInfo info) { // argument is normally null here, if not null is used to fill the fields + super(ID, info); + if (info != null) { + fillFromInfo(info); + } + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NA; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = new ChunkRaw(13, ChunkHelper.b_IHDR, true); + int offset = 0; + PngHelperInternal.writeInt4tobytes(cols, c.data, offset); + offset += 4; + PngHelperInternal.writeInt4tobytes(rows, c.data, offset); + offset += 4; + c.data[offset++] = (byte) bitspc; + c.data[offset++] = (byte) colormodel; + c.data[offset++] = (byte) compmeth; + c.data[offset++] = (byte) filmeth; + c.data[offset++] = (byte) interlaced; + return c; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + if (c.len != 13) { + throw new PngjException("Bad IDHR len " + c.len); + } + ByteArrayInputStream st = c.getAsByteStream(); + cols = PngHelperInternal.readInt4(st); + rows = PngHelperInternal.readInt4(st); + // bit depth: number of bits per channel + bitspc = PngHelperInternal.readByte(st); + colormodel = PngHelperInternal.readByte(st); + compmeth = PngHelperInternal.readByte(st); + filmeth = PngHelperInternal.readByte(st); + interlaced = PngHelperInternal.readByte(st); + } + + public int getCols() { + return cols; + } + + public void setCols(int cols) { + this.cols = cols; + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = rows; + } + + public int getBitspc() { + return bitspc; + } + + public void setBitspc(int bitspc) { + this.bitspc = bitspc; + } + + public int getColormodel() { + return colormodel; + } + + public void setColormodel(int colormodel) { + this.colormodel = colormodel; + } + + public int getCompmeth() { + return compmeth; + } + + public void setCompmeth(int compmeth) { + this.compmeth = compmeth; + } + + public int getFilmeth() { + return filmeth; + } + + public void setFilmeth(int filmeth) { + this.filmeth = filmeth; + } + + public int getInterlaced() { + return interlaced; + } + + public void setInterlaced(int interlaced) { + this.interlaced = interlaced; + } + + public boolean isInterlaced() { + return getInterlaced() == 1; + } + + public void fillFromInfo(ImageInfo info) { + setCols(imgInfo.cols); + setRows(imgInfo.rows); + setBitspc(imgInfo.bitDepth); + int colormodel = 0; + if (imgInfo.alpha) { + colormodel += 0x04; + } + if (imgInfo.indexed) { + colormodel += 0x01; + } + if (!imgInfo.greyscale) { + colormodel += 0x02; + } + setColormodel(colormodel); + setCompmeth(0); // compression method 0=deflate + setFilmeth(0); // filter method (0) + setInterlaced(0); // we never interlace + } + + /** + * throws PngInputException if unexpected values + */ + public ImageInfo createImageInfo() { + check(); + boolean alpha = (getColormodel() & 0x04) != 0; + boolean palette = (getColormodel() & 0x01) != 0; + boolean grayscale = (getColormodel() == 0 || getColormodel() == 4); + // creates ImgInfo and imgLine, and allocates buffers + return new ImageInfo(getCols(), getRows(), getBitspc(), alpha, grayscale, palette); + } + + public void check() { + if (cols < 1 || rows < 1 || compmeth != 0 || filmeth != 0) { + throw new PngjInputException("bad IHDR: col/row/compmethod/filmethod invalid"); + } + if (bitspc != 1 && bitspc != 2 && bitspc != 4 && bitspc != 8 && bitspc != 16) { + throw new PngjInputException("bad IHDR: bitdepth invalid"); + } + if (interlaced < 0 || interlaced > 1) { + throw new PngjInputException("bad IHDR: interlace invalid"); + } + switch (colormodel) { + case 0: + break; + case 3: + if (bitspc == 16) { + throw new PngjInputException("bad IHDR: bitdepth invalid"); + } + break; + case 2: + case 4: + case 6: + if (bitspc != 8 && bitspc != 16) { + throw new PngjInputException("bad IHDR: bitdepth invalid"); + } + break; + default: + throw new PngjInputException("bad IHDR: invalid colormodel"); + } + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkITXT.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkITXT.java new file mode 100644 index 0000000..fc48256 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkITXT.java @@ -0,0 +1,115 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * iTXt chunk. + *

      + * see http://www.w3.org/TR/PNG/#11iTXt + */ +public class PngChunkITXT extends PngChunkTextVar { + public final static String ID = ChunkHelper.iTXt; + + private boolean compressed = false; + private String langTag = ""; + private String translatedTag = ""; + + // http://www.w3.org/TR/PNG/#11iTXt + public PngChunkITXT(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkRaw createRawChunk() { + if (key == null || key.trim().length() == 0) { + throw new PngjException("Text chunk key must be non empty"); + } + try { + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + ba.write(ChunkHelper.toBytesLatin1(key)); + ba.write(0); // separator + ba.write(compressed ? 1 : 0); + ba.write(0); // compression method (always 0) + ba.write(ChunkHelper.toBytesLatin1(langTag)); + ba.write(0); // separator + ba.write(ChunkHelper.toBytesUTF8(translatedTag)); + ba.write(0); // separator + byte[] textbytes = ChunkHelper.toBytesUTF8(val); + if (compressed) { + textbytes = ChunkHelper.compressBytes(textbytes, true); + } + ba.write(textbytes); + byte[] b = ba.toByteArray(); + ChunkRaw chunk = createEmptyChunk(b.length, false); + chunk.data = b; + return chunk; + } catch (IOException e) { + throw new PngjException(e); + } + } + + @Override + public void parseFromRaw(ChunkRaw c) { + int nullsFound = 0; + int[] nullsIdx = new int[3]; + for (int i = 0; i < c.data.length; i++) { + if (c.data[i] != 0) { + continue; + } + nullsIdx[nullsFound] = i; + nullsFound++; + if (nullsFound == 1) { + i += 2; + } + if (nullsFound == 3) { + break; + } + } + if (nullsFound != 3) { + throw new PngjException("Bad formed PngChunkITXT chunk"); + } + key = ChunkHelper.toStringLatin1(c.data, 0, nullsIdx[0]); + int i = nullsIdx[0] + 1; + compressed = c.data[i] != 0; + i++; + if (compressed && c.data[i] != 0) { + throw new PngjException("Bad formed PngChunkITXT chunk - bad compression method "); + } + langTag = ChunkHelper.toStringLatin1(c.data, i, nullsIdx[1] - i); + translatedTag = ChunkHelper.toStringUTF8(c.data, nullsIdx[1] + 1, nullsIdx[2] - nullsIdx[1] - 1); + i = nullsIdx[2] + 1; + if (compressed) { + byte[] bytes = ChunkHelper.compressBytes(c.data, i, c.data.length - i, false); + val = ChunkHelper.toStringUTF8(bytes); + } else { + val = ChunkHelper.toStringUTF8(c.data, i, c.data.length - i); + } + } + + public boolean isCompressed() { + return compressed; + } + + public void setCompressed(boolean compressed) { + this.compressed = compressed; + } + + public String getLangtag() { + return langTag; + } + + public void setLangtag(String langtag) { + this.langTag = langtag; + } + + public String getTranslatedTag() { + return translatedTag; + } + + public void setTranslatedTag(String translatedTag) { + this.translatedTag = translatedTag; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkMultiple.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkMultiple.java new file mode 100644 index 0000000..5a797a0 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkMultiple.java @@ -0,0 +1,28 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * PNG chunk type (abstract) that allows multiple instances in same image. + */ +public abstract class PngChunkMultiple extends PngChunk { + + protected PngChunkMultiple(String id, ImageInfo imgInfo) { + super(id, imgInfo); + } + + @Override + public final boolean allowsMultiple() { + return true; + } + + /** + * NOTE: this chunk uses the default Object's equals() hashCode() + * implementation. + * + * This is the right thing to do, normally. + * + * This is important, eg see ChunkList.removeFromList() + */ + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkOFFS.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkOFFS.java new file mode 100644 index 0000000..cb40299 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkOFFS.java @@ -0,0 +1,84 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * oFFs chunk. + *

      + * see http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.oFFs + */ +public class PngChunkOFFS extends PngChunkSingle { + public final static String ID = "oFFs"; + + // http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.oFFs + private long posX; + private long posY; + private int units; // 0: pixel 1:micrometer + + public PngChunkOFFS(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(9, true); + PngHelperInternal.writeInt4tobytes((int) posX, c.data, 0); + PngHelperInternal.writeInt4tobytes((int) posY, c.data, 4); + c.data[8] = (byte) units; + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + if (chunk.len != 9) { + throw new PngjException("bad chunk length " + chunk); + } + posX = PngHelperInternal.readInt4fromBytes(chunk.data, 0); + if (posX < 0) { + posX += 0x100000000L; + } + posY = PngHelperInternal.readInt4fromBytes(chunk.data, 4); + if (posY < 0) { + posY += 0x100000000L; + } + units = PngHelperInternal.readInt1fromByte(chunk.data, 8); + } + + /** + * 0: pixel, 1:micrometer + */ + public int getUnits() { + return units; + } + + /** + * 0: pixel, 1:micrometer + */ + public void setUnits(int units) { + this.units = units; + } + + public long getPosX() { + return posX; + } + + public void setPosX(long posX) { + this.posX = posX; + } + + public long getPosY() { + return posY; + } + + public void setPosY(long posY) { + this.posY = posY; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPHYS.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPHYS.java new file mode 100644 index 0000000..edd591d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPHYS.java @@ -0,0 +1,112 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * pHYs chunk. + *

      + * see http://www.w3.org/TR/PNG/#11pHYs + */ +public class PngChunkPHYS extends PngChunkSingle { + public final static String ID = ChunkHelper.pHYs; + + // http://www.w3.org/TR/PNG/#11pHYs + private long pixelsxUnitX; + private long pixelsxUnitY; + private int units; // 0: unknown 1:metre + + public PngChunkPHYS(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(9, true); + PngHelperInternal.writeInt4tobytes((int) pixelsxUnitX, c.data, 0); + PngHelperInternal.writeInt4tobytes((int) pixelsxUnitY, c.data, 4); + c.data[8] = (byte) units; + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + if (chunk.len != 9) { + throw new PngjException("bad chunk length " + chunk); + } + pixelsxUnitX = PngHelperInternal.readInt4fromBytes(chunk.data, 0); + if (pixelsxUnitX < 0) { + pixelsxUnitX += 0x100000000L; + } + pixelsxUnitY = PngHelperInternal.readInt4fromBytes(chunk.data, 4); + if (pixelsxUnitY < 0) { + pixelsxUnitY += 0x100000000L; + } + units = PngHelperInternal.readInt1fromByte(chunk.data, 8); + } + + public long getPixelsxUnitX() { + return pixelsxUnitX; + } + + public void setPixelsxUnitX(long pixelsxUnitX) { + this.pixelsxUnitX = pixelsxUnitX; + } + + public long getPixelsxUnitY() { + return pixelsxUnitY; + } + + public void setPixelsxUnitY(long pixelsxUnitY) { + this.pixelsxUnitY = pixelsxUnitY; + } + + public int getUnits() { + return units; + } + + public void setUnits(int units) { + this.units = units; + } + + // special getters / setters + + /** + * returns -1 if the physicial unit is unknown, or X-Y are not equal + */ + public double getAsDpi() { + if (units != 1 || pixelsxUnitX != pixelsxUnitY) { + return -1; + } + return ((double) pixelsxUnitX) * 0.0254; + } + + /** + * returns -1 if the physicial unit is unknown + */ + public double[] getAsDpi2() { + if (units != 1) { + return new double[]{-1, -1}; + } + return new double[]{((double) pixelsxUnitX) * 0.0254, ((double) pixelsxUnitY) * 0.0254}; + } + + public void setAsDpi(double dpi) { + units = 1; + pixelsxUnitX = (long) (dpi / 0.0254 + 0.5); + pixelsxUnitY = pixelsxUnitX; + } + + public void setAsDpi2(double dpix, double dpiy) { + units = 1; + pixelsxUnitX = (long) (dpix / 0.0254 + 0.5); + pixelsxUnitY = (long) (dpiy / 0.0254 + 0.5); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPLTE.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPLTE.java new file mode 100644 index 0000000..e188b69 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkPLTE.java @@ -0,0 +1,99 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * PLTE chunk. + *

      + * see http://www.w3.org/TR/PNG/#11PLTE + *

      + * Critical chunk + */ +public class PngChunkPLTE extends PngChunkSingle { + public final static String ID = ChunkHelper.PLTE; + + // http://www.w3.org/TR/PNG/#11PLTE + private int nentries = 0; + /** + * RGB8 packed in one integer + */ + private int[] entries; + + public PngChunkPLTE(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NA; + } + + @Override + public ChunkRaw createRawChunk() { + int len = 3 * nentries; + int[] rgb = new int[3]; + ChunkRaw c = createEmptyChunk(len, true); + for (int n = 0, i = 0; n < nentries; n++) { + getEntryRgb(n, rgb); + c.data[i++] = (byte) rgb[0]; + c.data[i++] = (byte) rgb[1]; + c.data[i++] = (byte) rgb[2]; + } + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + setNentries(chunk.len / 3); + for (int n = 0, i = 0; n < nentries; n++) { + setEntry(n, chunk.data[i++] & 0xff, chunk.data[i++] & 0xff, chunk.data[i++] & 0xff); + } + } + + public void setNentries(int n) { + nentries = n; + if (nentries < 1 || nentries > 256) { + throw new PngjException("invalid pallette - nentries=" + nentries); + } + if (entries == null || entries.length != nentries) { // alloc + entries = new int[nentries]; + } + } + + public int getNentries() { + return nentries; + } + + public void setEntry(int n, int r, int g, int b) { + entries[n] = ((r << 16) | (g << 8) | b); + } + + public int getEntry(int n) { + return entries[n]; + } + + public void getEntryRgb(int n, int[] rgb) { + getEntryRgb(n, rgb, 0); + } + + public void getEntryRgb(int n, int[] rgb, int offset) { + int v = entries[n]; + rgb[offset + 0] = ((v & 0xff0000) >> 16); + rgb[offset + 1] = ((v & 0xff00) >> 8); + rgb[offset + 2] = (v & 0xff); + } + + public int minBitDepth() { + if (nentries <= 2) { + return 1; + } else if (nentries <= 4) { + return 2; + } else if (nentries <= 16) { + return 4; + } else { + return 8; + } + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSBIT.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSBIT.java new file mode 100644 index 0000000..1ce87da --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSBIT.java @@ -0,0 +1,125 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * sBIT chunk. + *

      + * see http://www.w3.org/TR/PNG/#11sBIT + *

      + * this chunk structure depends on the image type + */ +public class PngChunkSBIT extends PngChunkSingle { + public final static String ID = ChunkHelper.sBIT; + // http://www.w3.org/TR/PNG/#11sBIT + + // significant bits + private int graysb, alphasb; + private int redsb, greensb, bluesb; + + public PngChunkSBIT(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT; + } + + private int getCLen() { + int len = imgInfo.greyscale ? 1 : 3; + if (imgInfo.alpha) { + len += 1; + } + return len; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + if (c.len != getCLen()) { + throw new PngjException("bad chunk length " + c); + } + if (imgInfo.greyscale) { + graysb = PngHelperInternal.readInt1fromByte(c.data, 0); + if (imgInfo.alpha) { + alphasb = PngHelperInternal.readInt1fromByte(c.data, 1); + } + } else { + redsb = PngHelperInternal.readInt1fromByte(c.data, 0); + greensb = PngHelperInternal.readInt1fromByte(c.data, 1); + bluesb = PngHelperInternal.readInt1fromByte(c.data, 2); + if (imgInfo.alpha) { + alphasb = PngHelperInternal.readInt1fromByte(c.data, 3); + } + } + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = null; + c = createEmptyChunk(getCLen(), true); + if (imgInfo.greyscale) { + c.data[0] = (byte) graysb; + if (imgInfo.alpha) { + c.data[1] = (byte) alphasb; + } + } else { + c.data[0] = (byte) redsb; + c.data[1] = (byte) greensb; + c.data[2] = (byte) bluesb; + if (imgInfo.alpha) { + c.data[3] = (byte) alphasb; + } + } + return c; + } + + public void setGraysb(int gray) { + if (!imgInfo.greyscale) { + throw new PngjException("only greyscale images support this"); + } + graysb = gray; + } + + public int getGraysb() { + if (!imgInfo.greyscale) { + throw new PngjException("only greyscale images support this"); + } + return graysb; + } + + public void setAlphasb(int a) { + if (!imgInfo.alpha) { + throw new PngjException("only images with alpha support this"); + } + alphasb = a; + } + + public int getAlphasb() { + if (!imgInfo.alpha) { + throw new PngjException("only images with alpha support this"); + } + return alphasb; + } + + /** + * Set rgb values + */ + public void setRGB(int r, int g, int b) { + if (imgInfo.greyscale || imgInfo.indexed) { + throw new PngjException("only rgb or rgba images support this"); + } + redsb = r; + greensb = g; + bluesb = b; + } + + public int[] getRGB() { + if (imgInfo.greyscale || imgInfo.indexed) { + throw new PngjException("only rgb or rgba images support this"); + } + return new int[]{redsb, greensb, bluesb}; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSPLT.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSPLT.java new file mode 100644 index 0000000..b6496a1 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSPLT.java @@ -0,0 +1,132 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * sPLT chunk. + *

      + * see http://www.w3.org/TR/PNG/#11sPLT + */ +public class PngChunkSPLT extends PngChunkMultiple { + public final static String ID = ChunkHelper.sPLT; + + // http://www.w3.org/TR/PNG/#11sPLT + + private String palName; + private int sampledepth; // 8/16 + private int[] palette; // 5 elements per entry + + public PngChunkSPLT(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + try { + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + ba.write(ChunkHelper.toBytesLatin1(palName)); + ba.write(0); // separator + ba.write((byte) sampledepth); + int nentries = getNentries(); + for (int n = 0; n < nentries; n++) { + for (int i = 0; i < 4; i++) { + if (sampledepth == 8) { + PngHelperInternal.writeByte(ba, (byte) palette[n * 5 + i]); + } else { + PngHelperInternal.writeInt2(ba, palette[n * 5 + i]); + } + } + PngHelperInternal.writeInt2(ba, palette[n * 5 + 4]); + } + byte[] b = ba.toByteArray(); + ChunkRaw chunk = createEmptyChunk(b.length, false); + chunk.data = b; + return chunk; + } catch (IOException e) { + throw new PngjException(e); + } + } + + @Override + public void parseFromRaw(ChunkRaw c) { + int t = -1; + for (int i = 0; i < c.data.length; i++) { // look for first zero + if (c.data[i] == 0) { + t = i; + break; + } + } + if (t <= 0 || t > c.data.length - 2) { + throw new PngjException("bad sPLT chunk: no separator found"); + } + palName = ChunkHelper.toStringLatin1(c.data, 0, t); + sampledepth = PngHelperInternal.readInt1fromByte(c.data, t + 1); + t += 2; + int nentries = (c.data.length - t) / (sampledepth == 8 ? 6 : 10); + palette = new int[nentries * 5]; + int r, g, b, a, f, ne; + ne = 0; + for (int i = 0; i < nentries; i++) { + if (sampledepth == 8) { + r = PngHelperInternal.readInt1fromByte(c.data, t++); + g = PngHelperInternal.readInt1fromByte(c.data, t++); + b = PngHelperInternal.readInt1fromByte(c.data, t++); + a = PngHelperInternal.readInt1fromByte(c.data, t++); + } else { + r = PngHelperInternal.readInt2fromBytes(c.data, t); + t += 2; + g = PngHelperInternal.readInt2fromBytes(c.data, t); + t += 2; + b = PngHelperInternal.readInt2fromBytes(c.data, t); + t += 2; + a = PngHelperInternal.readInt2fromBytes(c.data, t); + t += 2; + } + f = PngHelperInternal.readInt2fromBytes(c.data, t); + t += 2; + palette[ne++] = r; + palette[ne++] = g; + palette[ne++] = b; + palette[ne++] = a; + palette[ne++] = f; + } + } + + public int getNentries() { + return palette.length / 5; + } + + public String getPalName() { + return palName; + } + + public void setPalName(String palName) { + this.palName = palName; + } + + public int getSampledepth() { + return sampledepth; + } + + public void setSampledepth(int sampledepth) { + this.sampledepth = sampledepth; + } + + public int[] getPalette() { + return palette; + } + + public void setPalette(int[] palette) { + this.palette = palette; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSRGB.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSRGB.java new file mode 100644 index 0000000..b5dadab --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSRGB.java @@ -0,0 +1,56 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * sRGB chunk. + *

      + * see http://www.w3.org/TR/PNG/#11sRGB + */ +public class PngChunkSRGB extends PngChunkSingle { + public final static String ID = ChunkHelper.sRGB; + + // http://www.w3.org/TR/PNG/#11sRGB + + public static final int RENDER_INTENT_Perceptual = 0; + public static final int RENDER_INTENT_Relative_colorimetric = 1; + public static final int RENDER_INTENT_Saturation = 2; + public static final int RENDER_INTENT_Absolute_colorimetric = 3; + + private int intent; + + public PngChunkSRGB(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + if (c.len != 1) { + throw new PngjException("bad chunk length " + c); + } + intent = PngHelperInternal.readInt1fromByte(c.data, 0); + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = null; + c = createEmptyChunk(1, true); + c.data[0] = (byte) intent; + return c; + } + + public int getIntent() { + return intent; + } + + public void setIntent(int intent) { + this.intent = intent; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSTER.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSTER.java new file mode 100644 index 0000000..d4ba65f --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSTER.java @@ -0,0 +1,55 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * sTER chunk. + *

      + * see http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.sTER + */ +public class PngChunkSTER extends PngChunkSingle { + public final static String ID = "sTER"; + + // http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.sTER + private byte mode; // 0: cross-fuse layout 1: diverging-fuse layout + + public PngChunkSTER(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(1, true); + c.data[0] = mode; + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + if (chunk.len != 1) { + throw new PngjException("bad chunk length " + chunk); + } + mode = chunk.data[0]; + } + + /** + * 0: cross-fuse layout 1: diverging-fuse layout + */ + public byte getMode() { + return mode; + } + + /** + * 0: cross-fuse layout 1: diverging-fuse layout + */ + public void setMode(byte mode) { + this.mode = mode; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSingle.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSingle.java new file mode 100644 index 0000000..9e0779e --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkSingle.java @@ -0,0 +1,44 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * PNG chunk type (abstract) that does not allow multiple instances in same + * image. + */ +public abstract class PngChunkSingle extends PngChunk { + + protected PngChunkSingle(String id, ImageInfo imgInfo) { + super(id, imgInfo); + } + + public final boolean allowsMultiple() { + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PngChunkSingle other = (PngChunkSingle) obj; + if (id == null) { + return other.id == null; + } else return id.equals(other.id); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTEXT.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTEXT.java new file mode 100644 index 0000000..f2b5cf2 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTEXT.java @@ -0,0 +1,47 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * tEXt chunk. + *

      + * see http://www.w3.org/TR/PNG/#11tEXt + */ +public class PngChunkTEXT extends PngChunkTextVar { + public final static String ID = ChunkHelper.tEXt; + + public PngChunkTEXT(ImageInfo info) { + super(ID, info); + } + + public PngChunkTEXT(ImageInfo info, String key, String val) { + super(ID, info); + setKeyVal(key, val); + } + + @Override + public ChunkRaw createRawChunk() { + if (key == null || key.trim().length() == 0) { + throw new PngjException("Text chunk key must be non empty"); + } + byte[] b = ChunkHelper.toBytesLatin1(key + "\0" + val); + ChunkRaw chunk = createEmptyChunk(b.length, false); + chunk.data = b; + return chunk; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + int i; + for (i = 0; i < c.data.length; i++) { + if (c.data[i] == 0) { + break; + } + } + key = ChunkHelper.toStringLatin1(c.data, 0, i); + i++; + val = i < c.data.length ? ChunkHelper.toStringLatin1(c.data, i, c.data.length - i) : ""; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTIME.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTIME.java new file mode 100644 index 0000000..c7825de --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTIME.java @@ -0,0 +1,84 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.util.Calendar; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * tIME chunk. + *

      + * see http://www.w3.org/TR/PNG/#11tIME + */ +public class PngChunkTIME extends PngChunkSingle { + public final static String ID = ChunkHelper.tIME; + + // http://www.w3.org/TR/PNG/#11tIME + private int year, mon, day, hour, min, sec; + + public PngChunkTIME(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NONE; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = createEmptyChunk(7, true); + PngHelperInternal.writeInt2tobytes(year, c.data, 0); + c.data[2] = (byte) mon; + c.data[3] = (byte) day; + c.data[4] = (byte) hour; + c.data[5] = (byte) min; + c.data[6] = (byte) sec; + return c; + } + + @Override + public void parseFromRaw(ChunkRaw chunk) { + if (chunk.len != 7) { + throw new PngjException("bad chunk " + chunk); + } + year = PngHelperInternal.readInt2fromBytes(chunk.data, 0); + mon = PngHelperInternal.readInt1fromByte(chunk.data, 2); + day = PngHelperInternal.readInt1fromByte(chunk.data, 3); + hour = PngHelperInternal.readInt1fromByte(chunk.data, 4); + min = PngHelperInternal.readInt1fromByte(chunk.data, 5); + sec = PngHelperInternal.readInt1fromByte(chunk.data, 6); + } + + public void setNow(int secsAgo) { + Calendar d = Calendar.getInstance(); + d.setTimeInMillis(System.currentTimeMillis() - 1000 * (long) secsAgo); + year = d.get(Calendar.YEAR); + mon = d.get(Calendar.MONTH) + 1; + day = d.get(Calendar.DAY_OF_MONTH); + hour = d.get(Calendar.HOUR_OF_DAY); + min = d.get(Calendar.MINUTE); + sec = d.get(Calendar.SECOND); + } + + public void setYMDHMS(int yearx, int monx, int dayx, int hourx, int minx, int secx) { + year = yearx; + mon = monx; + day = dayx; + hour = hourx; + min = minx; + sec = secx; + } + + public int[] getYMDHMS() { + return new int[]{year, mon, day, hour, min, sec}; + } + + /** + * format YYYY/MM/DD HH:mm:SS + */ + public String getAsString() { + return String.format("%04d/%02d/%02d %02d:%02d:%02d", year, mon, day, hour, min, sec); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTRNS.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTRNS.java new file mode 100644 index 0000000..68fc990 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTRNS.java @@ -0,0 +1,157 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * tRNS chunk. + *

      + * see http://www.w3.org/TR/PNG/#11tRNS + *

      + * this chunk structure depends on the image type + */ +public class PngChunkTRNS extends PngChunkSingle { + public final static String ID = ChunkHelper.tRNS; + + // http://www.w3.org/TR/PNG/#11tRNS + + // only one of these is meaningful, depending on the image type + private int gray; + private int red, green, blue; + private int[] paletteAlpha = new int[]{}; + + public PngChunkTRNS(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT; + } + + @Override + public ChunkRaw createRawChunk() { + ChunkRaw c = null; + if (imgInfo.greyscale) { + c = createEmptyChunk(2, true); + PngHelperInternal.writeInt2tobytes(gray, c.data, 0); + } else if (imgInfo.indexed) { + c = createEmptyChunk(paletteAlpha.length, true); + for (int n = 0; n < c.len; n++) { + c.data[n] = (byte) paletteAlpha[n]; + } + } else { + c = createEmptyChunk(6, true); + PngHelperInternal.writeInt2tobytes(red, c.data, 0); + PngHelperInternal.writeInt2tobytes(green, c.data, 0); + PngHelperInternal.writeInt2tobytes(blue, c.data, 0); + } + return c; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + if (imgInfo.greyscale) { + gray = PngHelperInternal.readInt2fromBytes(c.data, 0); + } else if (imgInfo.indexed) { + int nentries = c.data.length; + paletteAlpha = new int[nentries]; + for (int n = 0; n < nentries; n++) { + paletteAlpha[n] = c.data[n] & 0xff; + } + } else { + red = PngHelperInternal.readInt2fromBytes(c.data, 0); + green = PngHelperInternal.readInt2fromBytes(c.data, 2); + blue = PngHelperInternal.readInt2fromBytes(c.data, 4); + } + } + + /** + * Set rgb values + */ + public void setRGB(int r, int g, int b) { + if (imgInfo.greyscale || imgInfo.indexed) { + throw new PngjException("only rgb or rgba images support this"); + } + red = r; + green = g; + blue = b; + } + + public int[] getRGB() { + if (imgInfo.greyscale || imgInfo.indexed) { + throw new PngjException("only rgb or rgba images support this"); + } + return new int[]{red, green, blue}; + } + + public int getRGB888() { + if (imgInfo.greyscale || imgInfo.indexed) { + throw new PngjException("only rgb or rgba images support this"); + } + return (red << 16) | (green << 8) | blue; + } + + public void setGray(int g) { + if (!imgInfo.greyscale) { + throw new PngjException("only grayscale images support this"); + } + gray = g; + } + + public int getGray() { + if (!imgInfo.greyscale) { + throw new PngjException("only grayscale images support this"); + } + return gray; + } + + /** + * Sets the length of the palette alpha. This should be followed by + * #setNentriesPalAlpha + * + * @param idx index inside the table + * @param val alpha value (0-255) + */ + public void setEntryPalAlpha(int idx, int val) { + paletteAlpha[idx] = val; + } + + public void setNentriesPalAlpha(int len) { + paletteAlpha = new int[len]; + } + + /** + * WARNING: non deep copy. See also {@link #setNentriesPalAlpha(int)} + * {@link #setEntryPalAlpha(int, int)} + */ + public void setPalAlpha(int[] palAlpha) { + if (!imgInfo.indexed) { + throw new PngjException("only indexed images support this"); + } + paletteAlpha = palAlpha; + } + + /** + * WARNING: non deep copy + */ + public int[] getPalletteAlpha() { + return paletteAlpha; + } + + /** + * to use when only one pallete index is set as totally transparent + */ + public void setIndexEntryAsTransparent(int palAlphaIndex) { + if (!imgInfo.indexed) { + throw new PngjException("only indexed images support this"); + } + paletteAlpha = new int[]{palAlphaIndex + 1}; + for (int i = 0; i < palAlphaIndex; i++) { + paletteAlpha[i] = 255; + } + paletteAlpha[palAlphaIndex] = 0; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTextVar.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTextVar.java new file mode 100644 index 0000000..1dc572a --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkTextVar.java @@ -0,0 +1,59 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * Superclass (abstract) for three textual chunks (TEXT, ITXT, ZTXT) + */ +public abstract class PngChunkTextVar extends PngChunkMultiple { + protected String key; // key/val: only for tEXt. lazy computed + protected String val; + + // http://www.w3.org/TR/PNG/#11keywords + public final static String KEY_Title = "Title"; // Short (one line) title or caption for image + public final static String KEY_Author = "Author"; // Name of image's creator + public final static String KEY_Description = "Description"; // Description of image (possibly long) + public final static String KEY_Copyright = "Copyright"; // Copyright notice + public final static String KEY_Creation_Time = "Creation Time"; // Time of original image creation + public final static String KEY_Software = "Software"; // Software used to create the image + public final static String KEY_Disclaimer = "Disclaimer"; // Legal disclaimer + public final static String KEY_Warning = "Warning"; // Warning of nature of content + public final static String KEY_Source = "Source"; // Device used to create the image + public final static String KEY_Comment = "Comment"; // Miscellaneous comment + + protected PngChunkTextVar(String id, ImageInfo info) { + super(id, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NONE; + } + + public static class PngTxtInfo { + public String title; + public String author; + public String description; + public String creation_time;// = (new Date()).toString(); + public String software; + public String disclaimer; + public String warning; + public String source; + public String comment; + + } + + public String getKey() { + return key; + } + + public String getVal() { + return val; + } + + public void setKeyVal(String key, String val) { + this.key = key; + this.val = val; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkUNKNOWN.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkUNKNOWN.java new file mode 100644 index 0000000..ab7a919 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkUNKNOWN.java @@ -0,0 +1,40 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * Placeholder for UNKNOWN (custom or not) chunks. + *

      + * For PngReader, a chunk is unknown if it's not registered in the chunk factory + */ +public class PngChunkUNKNOWN extends PngChunkMultiple { // unkown, custom or not + + public PngChunkUNKNOWN(String id, ImageInfo info) { + super(id, info); + } + + @Override + public ChunkOrderingConstraint getOrderingConstraint() { + return ChunkOrderingConstraint.NONE; + } + + @Override + public ChunkRaw createRawChunk() { + return raw; + } + + @Override + public void parseFromRaw(ChunkRaw c) { + + } + + /* does not do deep copy! */ + public byte[] getData() { + return raw.data; + } + + /* does not do deep copy! */ + public void setData(byte[] data) { + raw.data = data; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkZTXT.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkZTXT.java new file mode 100644 index 0000000..87da89e --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngChunkZTXT.java @@ -0,0 +1,64 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * zTXt chunk. + *

      + * see http://www.w3.org/TR/PNG/#11zTXt + */ +public class PngChunkZTXT extends PngChunkTextVar { + public final static String ID = ChunkHelper.zTXt; + + // http://www.w3.org/TR/PNG/#11zTXt + public PngChunkZTXT(ImageInfo info) { + super(ID, info); + } + + @Override + public ChunkRaw createRawChunk() { + if (key == null || key.trim().length() == 0) { + throw new PngjException("Text chunk key must be non empty"); + } + try { + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + ba.write(ChunkHelper.toBytesLatin1(key)); + ba.write(0); // separator + ba.write(0); // compression method: 0 + byte[] textbytes = ChunkHelper.compressBytes(ChunkHelper.toBytesLatin1(val), true); + ba.write(textbytes); + byte[] b = ba.toByteArray(); + ChunkRaw chunk = createEmptyChunk(b.length, false); + chunk.data = b; + return chunk; + } catch (IOException e) { + throw new PngjException(e); + } + } + + @Override + public void parseFromRaw(ChunkRaw c) { + int nullsep = -1; + for (int i = 0; i < c.data.length; i++) { // look for first zero + if (c.data[i] != 0) { + continue; + } + nullsep = i; + break; + } + if (nullsep < 0 || nullsep > c.data.length - 2) { + throw new PngjException("bad zTXt chunk: no separator found"); + } + key = ChunkHelper.toStringLatin1(c.data, 0, nullsep); + int compmet = c.data[nullsep + 1]; + if (compmet != 0) { + throw new PngjException("bad zTXt chunk: unknown compression method"); + } + byte[] uncomp = ChunkHelper.compressBytes(c.data, nullsep + 2, c.data.length - nullsep - 2, false); // uncompress + val = ChunkHelper.toStringLatin1(uncomp); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngMetadata.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngMetadata.java new file mode 100644 index 0000000..f39a3c7 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/chunks/PngMetadata.java @@ -0,0 +1,237 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.chunks; + +import java.util.ArrayList; +import java.util.List; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjException; + +/** + * We consider "image metadata" every info inside the image except for the most + * basic image info (IHDR chunk - ImageInfo class) and the pixels values. + *

      + * This includes the palette (if present) and all the ancillary chunks + *

      + * This class provides a wrapper over the collection of chunks of a image (read + * or to write) and provides some high level methods to access them + */ +public class PngMetadata { + private final ChunksList chunkList; + private final boolean readonly; + + public PngMetadata(ChunksList chunks) { + this.chunkList = chunks; + this.readonly = !(chunks instanceof ChunksListForWrite); + } + + /** + * Queues the chunk at the writer + *

      + * lazyOverwrite: if true, checks if there is a queued "equivalent" chunk + * and if so, overwrites it. However if that not check for already written + * chunks. + */ + public void queueChunk(final PngChunk c, boolean lazyOverwrite) { + ChunksListForWrite cl = getChunkListW(); + if (readonly) { + throw new PngjException("cannot set chunk : readonly metadata"); + } + if (lazyOverwrite) { + ChunkHelper.trimList(cl.getQueuedChunks(), new ChunkPredicate() { + public boolean match(PngChunk c2) { + return ChunkHelper.equivalent(c, c2); + } + }); + } + cl.queue(c); + } + + public void queueChunk(final PngChunk c) { + queueChunk(c, true); + } + + private ChunksListForWrite getChunkListW() { + return (ChunksListForWrite) chunkList; + } + + // ///// high level utility methods follow //////////// + + // //////////// DPI + + /** + * returns -1 if not found or dimension unknown + */ + public double[] getDpi() { + PngChunk c = chunkList.getById1(ChunkHelper.pHYs, true); + if (c == null) { + return new double[]{-1, -1}; + } else { + return ((PngChunkPHYS) c).getAsDpi2(); + } + } + + public void setDpi(double x) { + setDpi(x, x); + } + + public void setDpi(double x, double y) { + PngChunkPHYS c = new PngChunkPHYS(chunkList.imageInfo); + c.setAsDpi2(x, y); + queueChunk(c); + } + + // //////////// TIME + + /** + * Creates a time chunk with current time, less secsAgo seconds + *

      + * + * @return Returns the created-queued chunk, just in case you want to + * examine or modify it + */ + public PngChunkTIME setTimeNow(int secsAgo) { + PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo); + c.setNow(secsAgo); + queueChunk(c); + return c; + } + + public PngChunkTIME setTimeNow() { + return setTimeNow(0); + } + + /** + * Creates a time chunk with diven date-time + *

      + * + * @return Returns the created-queued chunk, just in case you want to + * examine or modify it + */ + public PngChunkTIME setTimeYMDHMS(int yearx, int monx, int dayx, int hourx, int minx, int secx) { + PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo); + c.setYMDHMS(yearx, monx, dayx, hourx, minx, secx); + queueChunk(c, true); + return c; + } + + /** + * null if not found + */ + public PngChunkTIME getTime() { + return (PngChunkTIME) chunkList.getById1(ChunkHelper.tIME); + } + + public String getTimeAsString() { + PngChunkTIME c = getTime(); + return c == null ? "" : c.getAsString(); + } + + // //////////// TEXT + + /** + * Creates a text chunk and queue it. + *

      + * + * @param k : key (latin1) + * @param val (arbitrary, should be latin1 if useLatin1) + * @param useLatin1 + * @param compress + * @return Returns the created-queued chunks, just in case you want to + * examine, touch it + */ + public PngChunkTextVar setText(String k, String val, boolean useLatin1, boolean compress) { + if (compress && !useLatin1) { + throw new PngjException("cannot compress non latin text"); + } + PngChunkTextVar c; + if (useLatin1) { + if (compress) { + c = new PngChunkZTXT(chunkList.imageInfo); + } else { + c = new PngChunkTEXT(chunkList.imageInfo); + } + } else { + c = new PngChunkITXT(chunkList.imageInfo); + //((PngChunkITXT) c).setTranslatedTag(k); // we use the same orig tag (this is not quite right) + } + c.setKeyVal(k, val); + queueChunk(c, true); + return c; + } + + public PngChunkTextVar setText(String k, String val) { + return setText(k, val, false, false); + } + + /** + * gets all text chunks with a given key + *

      + * returns null if not found + *

      + * Warning: this does not check the "lang" key of iTxt + */ + @SuppressWarnings("unchecked") + public List getTxtsForKey(String k) { + @SuppressWarnings("rawtypes") + List c = new ArrayList(); + c.addAll(chunkList.getById(ChunkHelper.tEXt, k)); + c.addAll(chunkList.getById(ChunkHelper.zTXt, k)); + c.addAll(chunkList.getById(ChunkHelper.iTXt, k)); + return c; + } + + /** + * Returns empty if not found, concatenated (with newlines) if multiple! - + * and trimmed + *

      + * Use getTxtsForKey() if you don't want this behaviour + */ + public String getTxtForKey(String k) { + List li = getTxtsForKey(k); + if (li.isEmpty()) { + return ""; + } + StringBuilder t = new StringBuilder(); + for (PngChunkTextVar c : li) { + t.append(c.getVal()).append("\n"); + } + return t.toString().trim(); + } + + /** + * Returns the palette chunk, if present + * + * @return null if not present + */ + public PngChunkPLTE getPLTE() { + return (PngChunkPLTE) chunkList.getById1(PngChunkPLTE.ID); + } + + /** + * Creates a new empty palette chunk, queues it for write and return it to + * the caller, who should fill its entries + */ + public PngChunkPLTE createPLTEChunk() { + PngChunkPLTE plte = new PngChunkPLTE(chunkList.imageInfo); + queueChunk(plte); + return plte; + } + + /** + * Returns the TRNS chunk, if present + * + * @return null if not present + */ + public PngChunkTRNS getTRNS() { + return (PngChunkTRNS) chunkList.getById1(PngChunkTRNS.ID); + } + + /** + * Creates a new empty TRNS chunk, queues it for write and return it to the + * caller, who should fill its entries + */ + public PngChunkTRNS createTRNSChunk() { + PngChunkTRNS trns = new PngChunkTRNS(chunkList.imageInfo); + queueChunk(trns); + return trns; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStream.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStream.java new file mode 100644 index 0000000..890bf7d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStream.java @@ -0,0 +1,171 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.io.OutputStream; +import org.xbib.graphics.imageio.plugins.png.pngj.IdatChunkWriter; + +/** + * This is an OutputStream that compresses (via Deflater or a deflater-like + * object), and optionally passes the compressed stream to another output + * stream. + *

      + * It allows to compute in/out/ratio stats. + *

      + * It works as a stream (similar to DeflaterOutputStream), but it's peculiar in + * that it expects that each writes has a fixed length (other lenghts are + * accepted, but it's less efficient) and that the total amount of bytes is + * known (so it can close itself, but it can also be closed on demand) In PNGJ + * use, the block is typically a row (including filter byte). + *

      + * We use this to do the real compression (with Deflate) but also to compute + * tentative estimators + *

      + * If not closed, it can be recicled via reset() + */ +public abstract class CompressorStream extends OutputStream { + + protected IdatChunkWriter idatChunkWriter; + public final int blockLen; + public final long totalbytes; + + boolean closed = false; + protected boolean done = false; + protected long bytesIn = 0; + protected long bytesOut = 0; + protected int block = -1; + + /** + * optionally stores the first byte of each block (row) + */ + private byte[] firstBytes; + protected boolean storeFirstByte = false; + + /** + * @param idatCw Can be null (if we are only interested in compute compression + * ratio) + * @param blockLen Estimated maximum block length. If unknown, use -1. + * @param totalbytes Expected total bytes to be fed. If unknown, use -1. + */ + public CompressorStream(IdatChunkWriter idatCw, int blockLen, long totalbytes) { + this.idatChunkWriter = idatCw; + if (blockLen < 0) { + blockLen = 4096; + } + if (totalbytes < 0) { + totalbytes = Long.MAX_VALUE; + } + if (blockLen < 1 || totalbytes < 1) { + throw new RuntimeException(" maxBlockLen or totalLen invalid"); + } + this.blockLen = blockLen; + this.totalbytes = totalbytes; + } + + /** + * Releases resources. Idempotent. + */ + @Override + public void close() { + done(); + if (idatChunkWriter != null) { + idatChunkWriter.close(); + } + closed = true; + } + + /** + * Will be called automatically when the number of bytes reaches the total + * expected Can be also be called from outside. This should set the flag + * done=true + */ + public abstract void done(); + + @Override + public final void write(byte[] data) { + write(data, 0, data.length); + } + + @Override + public final void write(byte[] data, int off, int len) { + block++; + if (len <= blockLen) { // normal case + mywrite(data, off, len); + if (storeFirstByte && block < firstBytes.length) { + firstBytes[block] = data[off]; // only makes sense in this case + } + } else { + while (len > 0) { + mywrite(data, off, blockLen); + off += blockLen; + len -= blockLen; + } + } + if (bytesIn >= totalbytes) { + done(); + } + + } + + /** + * same as write, but guarantedd to not exceed blockLen The implementation + * should update bytesOut and bytesInt but not check for totalBytes + */ + public abstract void mywrite(byte[] data, int off, int len); + + /** + * compressed/raw. This should be called only when done + */ + public final double getCompressionRatio() { + return bytesOut == 0 ? 1.0 : bytesOut / (double) bytesIn; + } + + /** + * raw (input) bytes. This should be called only when done + */ + public final long getBytesRaw() { + return bytesIn; + } + + /** + * compressed (out) bytes. This should be called only when done + */ + public final long getBytesCompressed() { + return bytesOut; + } + + public boolean isClosed() { + return closed; + } + + public boolean isDone() { + return done; + } + + public byte[] getFirstBytes() { + return firstBytes; + } + + public void setStoreFirstByte(boolean storeFirstByte, int nblocks) { + this.storeFirstByte = storeFirstByte; + if (this.storeFirstByte) { + if (firstBytes == null || firstBytes.length < nblocks) { + firstBytes = new byte[nblocks]; + } + } else { + firstBytes = null; + } + } + + public void reset() { + done(); + bytesIn = 0; + bytesOut = 0; + block = -1; + done = false; + } + + @Override + public void write(int i) { // should not be used + write(new byte[]{(byte) i}); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamDeflater.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamDeflater.java new file mode 100644 index 0000000..bbb643a --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamDeflater.java @@ -0,0 +1,113 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.util.zip.Deflater; +import org.xbib.graphics.imageio.plugins.png.pngj.IdatChunkWriter; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjOutputException; + +/** + * CompressorStream backed by a Deflater. + *

      + * Note that the Deflater is not disposed after done, you should either recycle + * this with reset() or dispose it with close() + */ +public class CompressorStreamDeflater extends CompressorStream { + + protected Deflater deflater; + protected byte[] buf1; // temporary storage of compressed bytes: only used if idatWriter is null + protected boolean deflaterIsOwn = true; + + /** + * if a deflater is passed, it must be already reset. It will not be + * released on close + */ + public CompressorStreamDeflater(IdatChunkWriter idatCw, int maxBlockLen, long totalLen, Deflater def) { + super(idatCw, maxBlockLen, totalLen); + this.deflater = def == null ? new Deflater() : def; + this.deflaterIsOwn = def == null; + } + + public CompressorStreamDeflater(IdatChunkWriter idatCw, int maxBlockLen, long totalLen) { + this(idatCw, maxBlockLen, totalLen, null); + } + + public CompressorStreamDeflater(IdatChunkWriter idatCw, int maxBlockLen, long totalLen, int deflaterCompLevel, + int deflaterStrategy) { + this(idatCw, maxBlockLen, totalLen, new Deflater(deflaterCompLevel)); + this.deflaterIsOwn = true; + deflater.setStrategy(deflaterStrategy); + } + + @Override + public void mywrite(byte[] data, int off, final int len) { + if (deflater.finished() || done || closed) { + throw new PngjOutputException("write beyond end of stream"); + } + deflater.setInput(data, off, len); + bytesIn += len; + while (!deflater.needsInput()) { + deflate(); + } + } + + protected void deflate() { + byte[] buf; + int off, n; + if (idatChunkWriter != null) { + buf = idatChunkWriter.getBuf(); + off = idatChunkWriter.getOffset(); + n = idatChunkWriter.getAvailLen(); + } else { + if (buf1 == null) { + buf1 = new byte[4096]; + } + buf = buf1; + off = 0; + n = buf1.length; + } + int len = deflater.deflate(buf, off, n); + if (len > 0) { + if (idatChunkWriter != null) { + idatChunkWriter.incrementOffset(len); + } + bytesOut += len; + } + } + + /** + * automatically called when done + */ + @Override + public void done() { + if (done) { + return; + } + if (!deflater.finished()) { + deflater.finish(); + while (!deflater.finished()) { + deflate(); + } + } + done = true; + if (idatChunkWriter != null) { + idatChunkWriter.close(); + } + } + + public void close() { + done(); + try { + if (deflaterIsOwn) { + deflater.end(); + } + } catch (Exception e) { + } + super.close(); + } + + @Override + public void reset() { + deflater.reset(); + super.reset(); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamLz4.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamLz4.java new file mode 100644 index 0000000..f135dff --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/CompressorStreamLz4.java @@ -0,0 +1,99 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.util.zip.Deflater; +import org.xbib.graphics.imageio.plugins.png.pngj.IdatChunkWriter; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjOutputException; + +/** + * This class uses a quick compressor to get a rough estimate of deflate + * compression ratio. + *

      + * This just ignores the outputStream, and the deflater related parameters + */ +public class CompressorStreamLz4 extends CompressorStream { + + private final DeflaterEstimatorLz4 lz4; + + private byte[] buf; // lazily allocated, only if needed + private final int buffer_size; + // bufpos=bytes in buffer yet not compressed (bytesIn include this) + private int inbuf = 0; + + private static final int MAX_BUFFER_SIZE = 16000; + + public CompressorStreamLz4(IdatChunkWriter os, int maxBlockLen, long totalLen) { + super(os, maxBlockLen, totalLen); + lz4 = new DeflaterEstimatorLz4(); + buffer_size = (int) (totalLen > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : totalLen); + } + + public CompressorStreamLz4(IdatChunkWriter os, int maxBlockLen, long totalLen, Deflater def) { + this(os, maxBlockLen, totalLen);// edlfater ignored + } + + public CompressorStreamLz4(IdatChunkWriter os, int maxBlockLen, long totalLen, int deflaterCompLevel, + int deflaterStrategy) { + this(os, maxBlockLen, totalLen); // paramters ignored + } + + @Override + public void mywrite(byte[] b, int off, int len) { + if (len == 0) { + return; + } + if (done || closed) { + throw new PngjOutputException("write beyond end of stream"); + } + bytesIn += len; + while (len > 0) { + if (inbuf == 0 && (len >= MAX_BUFFER_SIZE || bytesIn == totalbytes)) { + // direct copy (buffer might be null or empty) + bytesOut += lz4.compressEstim(b, off, len); + len = 0; + } else { + if (buf == null) { + buf = new byte[buffer_size]; + } + int len1 = inbuf + len <= buffer_size ? len : buffer_size - inbuf; // to copy + if (len1 > 0) { + System.arraycopy(b, off, buf, inbuf, len1); + } + inbuf += len1; + len -= len1; + off += len1; + if (inbuf == buffer_size) { + compressFromBuffer(); + } + } + } + } + + void compressFromBuffer() { + if (inbuf > 0) { + bytesOut += lz4.compressEstim(buf, 0, inbuf); + inbuf = 0; + } + } + + @Override + public void done() { + if (!done) { + compressFromBuffer(); + done = true; + } + } + + @Override + public void close() { + done(); + if (!closed) { + super.close(); + buf = null; + } + } + + public void reset() { + super.reset(); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorHjg.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorHjg.java new file mode 100644 index 0000000..c0fe3c3 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorHjg.java @@ -0,0 +1,260 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +final class DeflaterEstimatorHjg { + + /** + * This object is stateless, it's thread safe and can be reused + */ + public DeflaterEstimatorHjg() { + } + + /** + * Estimates the length of the compressed bytes, as compressed by Lz4 + * WARNING: if larger than LZ4_64K_LIMIT it cuts it in fragments + *

      + * WARNING: if some part of the input is discarded, this should return the + * proportional (so that returnValue/srcLen=compressionRatio) + * + * @param src + * @param srcOff + * @param srcLen + * @return length of the compressed bytes + */ + public int compressEstim(byte[] src, int srcOff, final int srcLen) { + if (srcLen < 10) { + return srcLen; // too small + } + int stride = LZ4_64K_LIMIT - 1; + int segments = (srcLen + stride - 1) / stride; + stride = srcLen / segments; + if (stride >= LZ4_64K_LIMIT - 1 || stride * segments > srcLen || segments < 1 || stride < 1) { + throw new RuntimeException("?? " + srcLen); + } + int bytesIn = 0; + int bytesOut = 0; + int len = srcLen; + while (len > 0) { + if (len > stride) { + len = stride; + } + bytesOut += compress64k(src, srcOff, len); + srcOff += len; + bytesIn += len; + len = srcLen - bytesIn; + } + double ratio = bytesOut / (double) bytesIn; + return bytesIn == srcLen ? bytesOut : (int) (ratio * srcLen + 0.5); + } + + public int compressEstim(byte[] src) { + return compressEstim(src, 0, src.length); + } + + static final int MEMORY_USAGE = 14; + static final int NOT_COMPRESSIBLE_DETECTION_LEVEL = 6; // see SKIP_STRENGTH + + static final int MIN_MATCH = 4; + + static final int HASH_LOG = MEMORY_USAGE - 2; + static final int HASH_TABLE_SIZE = 1 << HASH_LOG; + + static final int SKIP_STRENGTH = Math.max(NOT_COMPRESSIBLE_DETECTION_LEVEL, 2); // 6 findMatchAttempts = 2^SKIP_STRENGTH+3 + static final int COPY_LENGTH = 8; + static final int LAST_LITERALS = 5; + static final int MF_LIMIT = COPY_LENGTH + MIN_MATCH; + static final int MIN_LENGTH = MF_LIMIT + 1; + + static final int MAX_DISTANCE = 1 << 16; + + static final int ML_BITS = 4; + static final int ML_MASK = (1 << ML_BITS) - 1; + static final int RUN_BITS = 8 - ML_BITS; + static final int RUN_MASK = (1 << RUN_BITS) - 1; + + static final int LZ4_64K_LIMIT = (1 << 16) + (MF_LIMIT - 1); + static final int HASH_LOG_64K = HASH_LOG + 1; + static final int HASH_TABLE_SIZE_64K = 1 << HASH_LOG_64K; + + static final int HASH_LOG_HC = 15; + static final int HASH_TABLE_SIZE_HC = 1 << HASH_LOG_HC; + static final int OPTIMAL_ML = ML_MASK - 1 + MIN_MATCH; + + static int compress64k(byte[] src, final int srcOff, final int srcLen) { + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = 0; + + int anchor = sOff; + + if (srcLen >= MIN_LENGTH) { + + final short[] hashTable = new short[HASH_TABLE_SIZE_64K]; + + ++sOff; + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int findMatchAttempts1 = (1 << SKIP_STRENGTH) + 3; // 64+3=67 + do { + sOff = forwardOff; + forwardOff += findMatchAttempts1++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; // ends all + } + + final int h = hash64k(readInt(src, sOff)); + ref = srcOff + readShort(hashTable, h); + writeShort(hashTable, h, sOff - srcOff); + } while (!readIntEquals(src, ref, sOff)); + + // catch up + final int excess = commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + // sequence == refsequence + final int runLen = sOff - anchor; + dOff++; + + if (runLen >= RUN_MASK) { + if (runLen > RUN_MASK) { + dOff += (runLen - RUN_MASK) / 0xFF; + } + dOff++; + } + dOff += runLen; + while (true) { + // encode offset + dOff += 2; + // count nb matches + sOff += MIN_MATCH; + ref += MIN_MATCH; + final int matchLen = commonBytes(src, ref, sOff, srcLimit); + sOff += matchLen; + // encode match len + if (matchLen >= ML_MASK) { + if (matchLen >= ML_MASK + 0xFF) { + dOff += (matchLen - ML_MASK) / 0xFF; + } + dOff++; + } + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + // fill table + writeShort(hashTable, hash64k(readInt(src, sOff - 2)), sOff - 2 - srcOff); + // test next position + final int h = hash64k(readInt(src, sOff)); + ref = srcOff + readShort(hashTable, h); + writeShort(hashTable, h, sOff - srcOff); + if (!readIntEquals(src, sOff, ref)) { + break; + } + dOff++; + } + // prepare next loop + anchor = sOff++; + } + } + int runLen = srcEnd - anchor; + if (runLen >= RUN_MASK + 0xFF) { + dOff += (runLen - RUN_MASK) / 0xFF; + } + dOff++; + dOff += runLen; + return dOff; + } + + static final int maxCompressedLength(int length) { + if (length < 0) { + throw new IllegalArgumentException("length must be >= 0, got " + length); + } + return length + length / 255 + 16; + } + + static int hash(int i) { + return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG); + } + + static int hash64k(int i) { + return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG_64K); + } + + static int readShortLittleEndian(byte[] buf, int i) { + return (buf[i] & 0xFF) | ((buf[i + 1] & 0xFF) << 8); + } + + static boolean readIntEquals(byte[] buf, int i, int j) { + return buf[i] == buf[j] && buf[i + 1] == buf[j + 1] && buf[i + 2] == buf[j + 2] && buf[i + 3] == buf[j + 3]; + } + + static int commonBytes(byte[] b, int o1, int o2, int limit) { + int count = 0; + while (o2 < limit && b[o1++] == b[o2++]) { + ++count; + } + return count; + } + + static int commonBytesBackward(byte[] b, int o1, int o2, int l1, int l2) { + int count = 0; + while (o1 > l1 && o2 > l2 && b[--o1] == b[--o2]) { + ++count; + } + return count; + } + + static int readShort(short[] buf, int off) { + return buf[off] & 0xFFFF; + } + + static byte readByte(byte[] buf, int i) { + return buf[i]; + } + + static void checkRange(byte[] buf, int off) { + if (off < 0 || off >= buf.length) { + throw new ArrayIndexOutOfBoundsException(off); + } + } + + static void checkRange(byte[] buf, int off, int len) { + checkLength(len); + if (len > 0) { + checkRange(buf, off); + checkRange(buf, off + len - 1); + } + } + + static void checkLength(int len) { + if (len < 0) { + throw new IllegalArgumentException("lengths must be >= 0"); + } + } + + static int readIntBE(byte[] buf, int i) { + return ((buf[i] & 0xFF) << 24) | ((buf[i + 1] & 0xFF) << 16) | ((buf[i + 2] & 0xFF) << 8) | (buf[i + 3] & 0xFF); + } + + static int readIntLE(byte[] buf, int i) { + return (buf[i] & 0xFF) | ((buf[i + 1] & 0xFF) << 8) | ((buf[i + 2] & 0xFF) << 16) | ((buf[i + 3] & 0xFF) << 24); + } + + static int readInt(byte[] buf, int i) { + return readIntBE(buf, i); + } + + static void writeShort(short[] buf, int off, int v) { + buf[off] = (short) v; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorLz4.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorLz4.java new file mode 100644 index 0000000..6ce0031 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/DeflaterEstimatorLz4.java @@ -0,0 +1,278 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.nio.ByteOrder; + +/** + * This estimator actually uses the LZ4 compression algorithm, and hopes that + * it's well correlated with Deflater. It's about 3 to 4 times faster than + * Deflater. + *

      + * This is a modified heavily trimmed version of the + * net.jpountz.lz4.LZ4JavaSafeCompressor class plus some methods from other + * classes from LZ4 Java library: https://github.com/jpountz/lz4-java , + * originally licensed under the Apache License 2.0 + */ +final public class DeflaterEstimatorLz4 { + + /** + * This object is stateless, it's thread safe and can be reused + */ + public DeflaterEstimatorLz4() { + } + + /** + * Estimates the length of the compressed bytes, as compressed by Lz4 + * WARNING: if larger than LZ4_64K_LIMIT it cuts it in fragments + *

      + * WARNING: if some part of the input is discarded, this should return the + * proportional (so that returnValue/srcLen=compressionRatio) + * + * @param src + * @param srcOff + * @param srcLen + * @return length of the compressed bytes + */ + public int compressEstim(byte[] src, int srcOff, final int srcLen) { + if (srcLen < 10) { + return srcLen; // too small + } + int stride = LZ4_64K_LIMIT - 1; + int segments = (srcLen + stride - 1) / stride; + stride = srcLen / segments; + if (stride >= LZ4_64K_LIMIT - 1 || stride * segments > srcLen || segments < 1 || stride < 1) { + throw new RuntimeException("?? " + srcLen); + } + int bytesIn = 0; + int bytesOut = 0; + int len = srcLen; + while (len > 0) { + if (len > stride) { + len = stride; + } + bytesOut += compress64k(src, srcOff, len); + srcOff += len; + bytesIn += len; + len = srcLen - bytesIn; + } + double ratio = bytesOut / (double) bytesIn; + return bytesIn == srcLen ? bytesOut : (int) (ratio * srcLen + 0.5); + } + + public int compressEstim(byte[] src) { + return compressEstim(src, 0, src.length); + } + + static final ByteOrder NATIVE_BYTE_ORDER = ByteOrder.nativeOrder(); + + static final int MEMORY_USAGE = 14; + static final int NOT_COMPRESSIBLE_DETECTION_LEVEL = 6; + + static final int MIN_MATCH = 4; + + static final int HASH_LOG = MEMORY_USAGE - 2; + static final int HASH_TABLE_SIZE = 1 << HASH_LOG; + + static final int SKIP_STRENGTH = Math.max(NOT_COMPRESSIBLE_DETECTION_LEVEL, 2); + static final int COPY_LENGTH = 8; + static final int LAST_LITERALS = 5; + static final int MF_LIMIT = COPY_LENGTH + MIN_MATCH; + static final int MIN_LENGTH = MF_LIMIT + 1; + + static final int MAX_DISTANCE = 1 << 16; + + static final int ML_BITS = 4; + static final int ML_MASK = (1 << ML_BITS) - 1; + static final int RUN_BITS = 8 - ML_BITS; + static final int RUN_MASK = (1 << RUN_BITS) - 1; + + static final int LZ4_64K_LIMIT = (1 << 16) + (MF_LIMIT - 1); + static final int HASH_LOG_64K = HASH_LOG + 1; + static final int HASH_TABLE_SIZE_64K = 1 << HASH_LOG_64K; + + static final int HASH_LOG_HC = 15; + static final int HASH_TABLE_SIZE_HC = 1 << HASH_LOG_HC; + static final int OPTIMAL_ML = ML_MASK - 1 + MIN_MATCH; + + static int compress64k(byte[] src, int srcOff, int srcLen) { + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = 0; + + int anchor = sOff; + + if (srcLen >= MIN_LENGTH) { + + final short[] hashTable = new short[HASH_TABLE_SIZE_64K]; + + ++sOff; + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int findMatchAttempts = (1 << SKIP_STRENGTH) + 3; + do { + sOff = forwardOff; + forwardOff += findMatchAttempts++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash64k(readInt(src, sOff)); + ref = srcOff + readShort(hashTable, h); + writeShort(hashTable, h, sOff - srcOff); + } while (!readIntEquals(src, ref, sOff)); + + // catch up + final int excess = commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + // sequence == refsequence + final int runLen = sOff - anchor; + dOff++; + + if (runLen >= RUN_MASK) { + if (runLen > RUN_MASK) { + dOff += (runLen - RUN_MASK) / 0xFF; + } + dOff++; + } + dOff += runLen; + while (true) { + // encode offset + dOff += 2; + // count nb matches + sOff += MIN_MATCH; + ref += MIN_MATCH; + final int matchLen = commonBytes(src, ref, sOff, srcLimit); + sOff += matchLen; + // encode match len + if (matchLen >= ML_MASK) { + if (matchLen >= ML_MASK + 0xFF) { + dOff += (matchLen - ML_MASK) / 0xFF; + } + dOff++; + } + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + // fill table + writeShort(hashTable, hash64k(readInt(src, sOff - 2)), sOff - 2 - srcOff); + // test next position + final int h = hash64k(readInt(src, sOff)); + ref = srcOff + readShort(hashTable, h); + writeShort(hashTable, h, sOff - srcOff); + if (!readIntEquals(src, sOff, ref)) { + break; + } + dOff++; + } + // prepare next loop + anchor = sOff++; + } + } + int runLen = srcEnd - anchor; + if (runLen >= RUN_MASK + 0xFF) { + dOff += (runLen - RUN_MASK) / 0xFF; + } + dOff++; + dOff += runLen; + return dOff; + } + + static final int maxCompressedLength(int length) { + if (length < 0) { + throw new IllegalArgumentException("length must be >= 0, got " + length); + } + return length + length / 255 + 16; + } + + static int hash(int i) { + return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG); + } + + static int hash64k(int i) { + return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG_64K); + } + + static int readShortLittleEndian(byte[] buf, int i) { + return (buf[i] & 0xFF) | ((buf[i + 1] & 0xFF) << 8); + } + + static boolean readIntEquals(byte[] buf, int i, int j) { + return buf[i] == buf[j] && buf[i + 1] == buf[j + 1] && buf[i + 2] == buf[j + 2] && buf[i + 3] == buf[j + 3]; + } + + static int commonBytes(byte[] b, int o1, int o2, int limit) { + int count = 0; + while (o2 < limit && b[o1++] == b[o2++]) { + ++count; + } + return count; + } + + static int commonBytesBackward(byte[] b, int o1, int o2, int l1, int l2) { + int count = 0; + while (o1 > l1 && o2 > l2 && b[--o1] == b[--o2]) { + ++count; + } + return count; + } + + static int readShort(short[] buf, int off) { + return buf[off] & 0xFFFF; + } + + static byte readByte(byte[] buf, int i) { + return buf[i]; + } + + static void checkRange(byte[] buf, int off) { + if (off < 0 || off >= buf.length) { + throw new ArrayIndexOutOfBoundsException(off); + } + } + + static void checkRange(byte[] buf, int off, int len) { + checkLength(len); + if (len > 0) { + checkRange(buf, off); + checkRange(buf, off + len - 1); + } + } + + static void checkLength(int len) { + if (len < 0) { + throw new IllegalArgumentException("lengths must be >= 0"); + } + } + + static int readIntBE(byte[] buf, int i) { + return ((buf[i] & 0xFF) << 24) | ((buf[i + 1] & 0xFF) << 16) | ((buf[i + 2] & 0xFF) << 8) | (buf[i + 3] & 0xFF); + } + + static int readIntLE(byte[] buf, int i) { + return (buf[i] & 0xFF) | ((buf[i + 1] & 0xFF) << 8) | ((buf[i + 2] & 0xFF) << 16) | ((buf[i + 3] & 0xFF) << 24); + } + + static int readInt(byte[] buf, int i) { + if (NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + return readIntBE(buf, i); + } else { + return readIntLE(buf, i); + } + } + + static void writeShort(short[] buf, int off, int v) { + buf[off] = (short) v; + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/FiltersPerformance.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/FiltersPerformance.java new file mode 100644 index 0000000..9b3ccab --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/FiltersPerformance.java @@ -0,0 +1,224 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.util.Arrays; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjExceptionInternal; + +/** + * for use in adaptative strategy + */ +public class FiltersPerformance { + + private final ImageInfo iminfo; + private double memoryA = 0.7; // empirical (not very critical: 0.72) + private int lastrow = -1; + private final double[] absum = new double[5];// depending on the strategy not all values might be computed for all + private final double[] entropy = new double[5]; + private final double[] cost = new double[5]; + private final int[] histog = new int[256]; // temporary, not normalized + private int lastprefered = -1; + private boolean initdone = false; + private double preferenceForNone = 1.0; // higher gives more preference to NONE + + // this values are empirical (montecarlo), for RGB8 images with entropy estimator for NONE and memory=0.7 + //DONT MODIFY THIS + // lower is better! + public static final double[] FILTER_WEIGHTS_DEFAULT = {0.73, 1.03, 0.97, 1.11, 1.22}; + + private final double[] filter_weights = new double[]{-1, -1, -1, -1, -1}; + + private final static double LOG2NI = -1.0 / Math.log(2.0); + + public FiltersPerformance(ImageInfo imgInfo) { + this.iminfo = imgInfo; + } + + private void init() { + if (filter_weights[0] < 0) {// has not been set from outside + System.arraycopy(FILTER_WEIGHTS_DEFAULT, 0, filter_weights, 0, 5); + double wNone = filter_weights[0]; + if (iminfo.bitDepth == 16) { + wNone = 1.2; + } else if (iminfo.alpha) { + wNone = 0.8; + } else if (iminfo.indexed || iminfo.bitDepth < 8) { + wNone = 0.4; // we prefer NONE strongly + } + wNone /= preferenceForNone; + filter_weights[0] = wNone; + } + Arrays.fill(cost, 1.0); + initdone = true; + } + + public void updateFromFiltered(FilterType ftype, byte[] rowff, int rown) { + updateFromRawOrFiltered(ftype, rowff, null, null, rown); + } + + /** + * alternative: computes statistic without filtering + */ + public void updateFromRaw(FilterType ftype, byte[] rowb, byte[] rowbprev, int rown) { + updateFromRawOrFiltered(ftype, null, rowb, rowbprev, rown); + } + + private void updateFromRawOrFiltered(FilterType ftype, byte[] rowff, byte[] rowb, byte[] rowbprev, int rown) { + if (!initdone) { + init(); + } + if (rown != lastrow) { + Arrays.fill(absum, Double.NaN); + Arrays.fill(entropy, Double.NaN); + } + lastrow = rown; + if (rowff != null) { + computeHistogram(rowff); + } else { + computeHistogramForFilter(ftype, rowb, rowbprev); + } + if (ftype == FilterType.FILTER_NONE) { + entropy[ftype.val] = computeEntropyFromHistogram(); + } else { + absum[ftype.val] = computeAbsFromHistogram(); + } + } + + /* WARNING: this is not idempotent, call it just once per cycle (sigh) */ + public FilterType getPreferred() { + int fi = 0; + double vali = Double.MAX_VALUE, val = 0; // lower wins + for (int i = 0; i < 5; i++) { + if (!Double.isNaN(absum[i])) { + val = absum[i]; + } else if (!Double.isNaN(entropy[i])) { + val = (Math.pow(2.0, entropy[i]) - 1.0) * 0.5; + } else { + continue; + } + val *= filter_weights[i]; + val = cost[i] * memoryA + (1 - memoryA) * val; + cost[i] = val; + if (val < vali) { + vali = val; + fi = i; + } + } + lastprefered = fi; + return FilterType.getByVal(lastprefered); + } + + public final void computeHistogramForFilter(FilterType filterType, byte[] rowb, byte[] rowbprev) { + Arrays.fill(histog, 0); + int i, j, imax = iminfo.bytesPerRow; + switch (filterType) { + case FILTER_NONE: + for (i = 1; i <= imax; i++) { + histog[rowb[i] & 0xFF]++; + } + break; + case FILTER_PAETH: + for (i = 1; i <= imax; i++) { + histog[PngHelperInternal.filterRowPaeth(rowb[i], 0, rowbprev[i] & 0xFF, 0)]++; + } + for (j = 1, i = iminfo.bytesPixel + 1; i <= imax; i++, j++) { + histog[PngHelperInternal.filterRowPaeth(rowb[i], rowb[j] & 0xFF, rowbprev[i] & 0xFF, + rowbprev[j] & 0xFF)]++; + } + break; + case FILTER_SUB: + for (i = 1; i <= iminfo.bytesPixel; i++) { + histog[rowb[i] & 0xFF]++; + } + for (j = 1, i = iminfo.bytesPixel + 1; i <= imax; i++, j++) { + histog[(rowb[i] - rowb[j]) & 0xFF]++; + } + break; + case FILTER_UP: + for (i = 1; i <= iminfo.bytesPerRow; i++) { + histog[(rowb[i] - rowbprev[i]) & 0xFF]++; + } + break; + case FILTER_AVERAGE: + for (i = 1; i <= iminfo.bytesPixel; i++) { + histog[((rowb[i] & 0xFF) - ((rowbprev[i] & 0xFF)) / 2) & 0xFF]++; + } + for (j = 1, i = iminfo.bytesPixel + 1; i <= imax; i++, j++) { + histog[((rowb[i] & 0xFF) - ((rowbprev[i] & 0xFF) + (rowb[j] & 0xFF)) / 2) & 0xFF]++; + } + break; + default: + throw new PngjExceptionInternal("Bad filter:" + filterType); + } + } + + public void computeHistogram(byte[] rowff) { + Arrays.fill(histog, 0); + for (int i = 1; i < iminfo.bytesPerRow; i++) { + histog[rowff[i] & 0xFF]++; + } + } + + public double computeAbsFromHistogram() { + int s = 0; + for (int i = 1; i < 128; i++) { + s += histog[i] * i; + } + for (int i = 128, j = 128; j > 0; i++, j--) { + s += histog[i] * j; + } + return s / (double) iminfo.bytesPerRow; + } + + public final double computeEntropyFromHistogram() { + double s = 1.0 / iminfo.bytesPerRow; + double ls = Math.log(s); + + double h = 0; + for (int x : histog) { + if (x > 0) { + h += (Math.log(x) + ls) * x; + } + } + h *= s * LOG2NI; + if (h < 0.0) { + h = 0.0; + } + return h; + } + + /** + * If larger than 1.0, NONE will be more prefered. This must be called + * before init + * + * @param preferenceForNone around 1.0 (default: 1.0) + */ + public void setPreferenceForNone(double preferenceForNone) { + this.preferenceForNone = preferenceForNone; + } + + /** + * Values greater than 1.0 (towards infinite) increase the memory towards 1. + * Values smaller than 1.0 (towards zero) decreases the memory . + */ + public void tuneMemory(double m) { + if (m == 0) { + memoryA = 0.0; + } else { + memoryA = Math.pow(memoryA, 1.0 / m); + } + } + + /** + * To set manually the filter weights. This is not recommended, unless you + * know what you are doing. Setting this ignores preferenceForNone and omits + * some heuristics + * + * @param weights Five doubles around 1.0, one for each filter type. Lower is + * preferered + */ + public void setFilterWeights(double[] weights) { + System.arraycopy(weights, 0, filter_weights, 0, 5); + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriter.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriter.java new file mode 100644 index 0000000..3cc9a4d --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriter.java @@ -0,0 +1,289 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.io.OutputStream; +import java.util.zip.Deflater; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import org.xbib.graphics.imageio.plugins.png.pngj.IdatChunkWriter; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngHelperInternal; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjOutputException; + +/** + * Encodes a set of rows (pixels) as a continuous deflated stream (does not know + * about IDAT chunk segmentation). + *

      + * This includes the filter selection strategy, plus the filtering itself and + * the deflating. Only supports fixed length rows (no interlaced writing). + *

      + * Typically an instance of this is hold by a PngWriter - but more instances + * could be used (for APGN) + */ +public abstract class PixelsWriter { + + private static final int IDAT_MAX_SIZE_DEFAULT = 32000; + + protected final ImageInfo imgInfo; + /** + * row buffer length, including filter byte (imgInfo.bytesPerRow + 1) + */ + protected final int buflen; + + protected final int bytesPixel; + protected final int bytesRow; + + private CompressorStream compressorStream; // to compress the idat stream + + protected int deflaterCompLevel = 6; + protected int deflaterStrategy = Deflater.DEFAULT_STRATEGY; + + protected boolean initdone = false; + + /** + * This is the globally configured filter type - it can be a concrete type + * or a pseudo type (hint or strategy) + */ + protected FilterType filterType; + + // counts the filters used - just for stats + private final int[] filtersUsed = new int[5]; + + // this is the raw underlying os (shared with the PngWriter) + private OutputStream os; + + private int idatMaxSize = IDAT_MAX_SIZE_DEFAULT; + + /** + * row being processed, couting from zero + */ + protected int currentRow; + + public PixelsWriter(ImageInfo imgInfo) { + this.imgInfo = imgInfo; + bytesRow = imgInfo.bytesPerRow; + buflen = bytesRow + 1; + bytesPixel = imgInfo.bytesPixel; + currentRow = -1; + filterType = FilterType.FILTER_DEFAULT; + } + + /** + * main internal point for external call. It does the lazy initializion if + * necessary, sets current row, and call {@link #filterAndWrite(byte[])} + */ + public final void processRow(final byte[] rowb) { + if (!initdone) { + init(); + } + currentRow++; + filterAndWrite(rowb); + } + + protected void sendToCompressedStream(byte[] rowf) { + compressorStream.write(rowf, 0, rowf.length); + filtersUsed[rowf[0]]++; + } + + /** + * This does the filtering and send to stream. Typically should decide the + * filtering, call + * {@link #filterRowWithFilterType(FilterType, byte[], byte[], byte[])} and + * and {@link #sendToCompressedStream(byte[])} + * + * @param rowb + */ + protected abstract void filterAndWrite(final byte[] rowb); + + /** + * Does the real filtering. This must be called with the real (standard) + * filterType. This should rarely be overriden. + *

      + * WARNING: look out the contract + * + * @param _filterType + * @param _rowb current row (the first byte might be modified) + * @param _rowbprev previous row (should be all zero the first time) + * @param _rowf tentative buffer to store the filtered bytes. might not be + * used! + * @return normally _rowf, but eventually _rowb. This MUST NOT BE MODIFIED + * nor reused by caller + */ + final protected byte[] filterRowWithFilterType(FilterType _filterType, byte[] _rowb, byte[] _rowbprev, + byte[] _rowf) { + // warning: some filters rely on: "previous row" (rowbprev) it must be initialized to 0 the first time + if (_filterType == FilterType.FILTER_NONE) { + _rowf = _rowb; + } + _rowf[0] = (byte) _filterType.val; + int i, j; + switch (_filterType) { + case FILTER_NONE: + // we return the same original (be careful!) + break; + case FILTER_PAETH: + for (i = 1; i <= bytesPixel; i++) { + _rowf[i] = (byte) PngHelperInternal.filterRowPaeth(_rowb[i], 0, _rowbprev[i] & 0xFF, 0); + } + for (j = 1, i = bytesPixel + 1; i <= bytesRow; i++, j++) { + _rowf[i] = (byte) PngHelperInternal.filterRowPaeth(_rowb[i], _rowb[j] & 0xFF, _rowbprev[i] & 0xFF, + _rowbprev[j] & 0xFF); + } + break; + case FILTER_SUB: + for (i = 1; i <= bytesPixel; i++) { + _rowf[i] = _rowb[i]; + } + for (j = 1, i = bytesPixel + 1; i <= bytesRow; i++, j++) { + _rowf[i] = (byte) (_rowb[i] - _rowb[j]); + } + break; + case FILTER_AVERAGE: + for (i = 1; i <= bytesPixel; i++) { + _rowf[i] = (byte) (_rowb[i] - (_rowbprev[i] & 0xFF) / 2); + } + for (j = 1, i = bytesPixel + 1; i <= bytesRow; i++, j++) { + _rowf[i] = (byte) (_rowb[i] - ((_rowbprev[i] & 0xFF) + (_rowb[j] & 0xFF)) / 2); + } + break; + case FILTER_UP: + for (i = 1; i <= bytesRow; i++) { + _rowf[i] = (byte) (_rowb[i] - _rowbprev[i]); + } + break; + default: + throw new PngjOutputException("Filter type not recognized: " + _filterType); + } + return _rowf; + } + + /** + * This will be called by the PngWrite to fill the raw pixels for each row. + * This can change from call to call. Warning: this can be called before the + * object is init, implementations should call init() to be sure + */ + public abstract byte[] getRowb(); + + /** + * This will be called lazily just before writing row 0. Idempotent. + */ + protected final void init() { + if (!initdone) { + initParams(); + initdone = true; + } + } + + /** + * called by init(); override (calling this first) to do additional + * initialization + */ + protected void initParams() { + IdatChunkWriter idatWriter = new IdatChunkWriter(os, idatMaxSize); + if (compressorStream == null) { // if not set, use the deflater + compressorStream = new CompressorStreamDeflater(idatWriter, buflen, imgInfo.getTotalRawBytes(), + deflaterCompLevel, deflaterStrategy); + } + } + + /** + * cleanup. This should be called explicitly. Idempotent and secure + */ + public void close() { + if (compressorStream != null) { + compressorStream.close(); + } + } + + /** + * Deflater (ZLIB) strategy. You should rarely change this from the default + * (Deflater.DEFAULT_STRATEGY) to Deflater.FILTERED (Deflater.HUFFMAN_ONLY + * is fast but compress poorly) + */ + public void setDeflaterStrategy(Integer deflaterStrategy) { + this.deflaterStrategy = deflaterStrategy; + } + + /** + * Deflater (ZLIB) compression level, between 0 (no compression) and 9 + */ + public void setDeflaterCompLevel(Integer deflaterCompLevel) { + this.deflaterCompLevel = deflaterCompLevel; + } + + public Integer getDeflaterCompLevel() { + return deflaterCompLevel; + } + + public final void setOs(OutputStream datStream) { + this.os = datStream; + } + + public OutputStream getOs() { + return os; + } + + /** + * @see #filterType + */ + final public FilterType getFilterType() { + return filterType; + } + + /** + * @see #filterType + */ + final public void setFilterType(FilterType filterType) { + this.filterType = filterType; + } + + /* out/in This should be called only after end() to get reliable results */ + public double getCompression() { + return compressorStream.isDone() ? compressorStream.getCompressionRatio() : 1.0; + } + + public void setCompressorStream(CompressorStream compressorStream) { + this.compressorStream = compressorStream; + } + + public long getTotalBytesToWrite() { + return imgInfo.getTotalRawBytes(); + } + + public boolean isDone() { + return currentRow == imgInfo.rows - 1; + } + + /** + * computed default fixed filter type to use, if specified DEFAULT; wilde + * guess based on image properties + * + * @return One of the five concrete filter types + */ + protected FilterType getDefaultFilter() { + if (imgInfo.indexed || imgInfo.bitDepth < 8) { + return FilterType.FILTER_NONE; + } else if (imgInfo.getTotalPixels() < 1024) { + return FilterType.FILTER_NONE; + } else if (imgInfo.rows == 1) { + return FilterType.FILTER_SUB; + } else if (imgInfo.cols == 1) { + return FilterType.FILTER_UP; + } else { + return FilterType.FILTER_PAETH; + } + } + + /** + * informational stats : filter used, in percentages + */ + final public String getFiltersUsed() { + return String.format("%d,%d,%d,%d,%d", (int) (filtersUsed[0] * 100.0 / imgInfo.rows + 0.5), + (int) (filtersUsed[1] * 100.0 / imgInfo.rows + 0.5), + (int) (filtersUsed[2] * 100.0 / imgInfo.rows + 0.5), + (int) (filtersUsed[3] * 100.0 / imgInfo.rows + 0.5), + (int) (filtersUsed[4] * 100.0 / imgInfo.rows + 0.5)); + } + + public void setIdatMaxSize(int idatMaxSize) { + this.idatMaxSize = idatMaxSize; + } +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterDefault.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterDefault.java new file mode 100644 index 0000000..2a20bc1 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterDefault.java @@ -0,0 +1,185 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.util.Arrays; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; +import org.xbib.graphics.imageio.plugins.png.pngj.PngjOutputException; + +/** + * Default implementation of PixelsWriter, with fixed filters and also adaptive + * strategies. + */ +public class PixelsWriterDefault extends PixelsWriter { + /** + * current raw row + */ + protected byte[] rowb; + /** + * previous raw row + */ + protected byte[] rowbprev; + /** + * buffer for filtered row + */ + protected byte[] rowbfilter; + + /** + * evaluates different filters, for adaptive strategy + */ + protected FiltersPerformance filtersPerformance; + + /** + * currently concrete selected filter type + */ + protected FilterType curfilterType; + + /** + * parameters for adaptive strategy + */ + protected int adaptMaxSkip; // set in initParams, does not change + protected int adaptSkipIncreaseSinceRow; // set in initParams, does not change + protected double adaptSkipIncreaseFactor; // set in initParams, does not change + protected int adaptNextRow = 0; + + public PixelsWriterDefault(ImageInfo imgInfo) { + super(imgInfo); + filtersPerformance = new FiltersPerformance(imgInfo); + } + + @Override + protected void initParams() { + super.initParams(); + + if (rowb == null || rowb.length < buflen) { + rowb = new byte[buflen]; + } + if (rowbfilter == null || rowbfilter.length < buflen) { + rowbfilter = new byte[buflen]; + } + if (rowbprev == null || rowbprev.length < buflen) { + rowbprev = new byte[buflen]; + } else { + Arrays.fill(rowbprev, (byte) 0); + } + + // if adaptative but too few rows or columns, use default + if (imgInfo.cols < 3 && !FilterType.isValidStandard(filterType)) { + filterType = FilterType.FILTER_DEFAULT; + } + if (imgInfo.rows < 3 && !FilterType.isValidStandard(filterType)) { + filterType = FilterType.FILTER_DEFAULT; + } + + if (imgInfo.getTotalPixels() <= 1024 && !FilterType.isValidStandard(filterType)) { + filterType = getDefaultFilter(); + } + + if (FilterType.isAdaptive(filterType)) { + // adaptCurSkip = 0; + adaptNextRow = 0; + if (filterType == FilterType.FILTER_ADAPTIVE_FAST) { + adaptMaxSkip = 200; + adaptSkipIncreaseSinceRow = 3; + adaptSkipIncreaseFactor = 1 / 4.0; // skip ~ row/3 + } else if (filterType == FilterType.FILTER_ADAPTIVE_MEDIUM) { + adaptMaxSkip = 8; + adaptSkipIncreaseSinceRow = 32; + adaptSkipIncreaseFactor = 1 / 80.0; + } else if (filterType == FilterType.FILTER_ADAPTIVE_FULL) { + adaptMaxSkip = 0; + adaptSkipIncreaseSinceRow = 128; + adaptSkipIncreaseFactor = 1 / 120.0; + } else { + throw new PngjOutputException("bad filter " + filterType); + } + } + } + + @Override + protected void filterAndWrite(final byte[] rowb) { + if (rowb != this.rowb) { + throw new RuntimeException("??"); // we rely on this + } + decideCurFilterType(); + byte[] filtered = filterRowWithFilterType(curfilterType, rowb, rowbprev, rowbfilter); + sendToCompressedStream(filtered); + // swap rowb <-> rowbprev + byte[] aux = this.rowb; + this.rowb = rowbprev; + rowbprev = aux; + } + + protected void decideCurFilterType() { + // decide the real filter and store in curfilterType + if (FilterType.isValidStandard(getFilterType())) { + curfilterType = getFilterType(); + } else if (getFilterType() == FilterType.FILTER_PRESERVE) { + curfilterType = FilterType.getByVal(rowb[0]); + } else if (getFilterType() == FilterType.FILTER_CYCLIC) { + curfilterType = FilterType.getByVal(currentRow % 5); + } else if (getFilterType() == FilterType.FILTER_DEFAULT) { + setFilterType(getDefaultFilter()); + curfilterType = getFilterType(); // this could be done once + } else if (FilterType.isAdaptive(getFilterType())) {// adaptive + if (currentRow == adaptNextRow) { + for (FilterType ftype : FilterType.getAllStandard()) { + filtersPerformance.updateFromRaw(ftype, rowb, rowbprev, currentRow); + } + curfilterType = filtersPerformance.getPreferred(); + int skip = (currentRow >= adaptSkipIncreaseSinceRow + ? (int) Math.round((currentRow - adaptSkipIncreaseSinceRow) * adaptSkipIncreaseFactor) + : 0); + if (skip > adaptMaxSkip) { + skip = adaptMaxSkip; + } + if (currentRow == 0) { + skip = 0; + } + adaptNextRow = currentRow + 1 + skip; + } + } else { + throw new PngjOutputException("not implemented filter: " + getFilterType()); + } + if (currentRow == 0 && curfilterType != FilterType.FILTER_NONE && curfilterType != FilterType.FILTER_SUB) { + curfilterType = FilterType.FILTER_SUB; // first row should always be none or sub + } + } + + @Override + public byte[] getRowb() { + if (!initdone) { + init(); + } + return rowb; + } + + @Override + public void close() { + super.close(); + } + + /** + * Only for adaptive strategies. See + * {@link FiltersPerformance#setPreferenceForNone(double)} + */ + public void setPreferenceForNone(double preferenceForNone) { + filtersPerformance.setPreferenceForNone(preferenceForNone); + } + + /** + * Only for adaptive strategies. See + * {@link FiltersPerformance#tuneMemory(double)} + */ + public void tuneMemory(double m) { + filtersPerformance.tuneMemory(m); + } + + /** + * Only for adaptive strategies. See + * {@link FiltersPerformance#setFilterWeights(double[])} + */ + public void setFilterWeights(double[] weights) { + filtersPerformance.setFilterWeights(weights); + } + +} diff --git a/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterMultiple.java b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterMultiple.java new file mode 100644 index 0000000..06485b3 --- /dev/null +++ b/png/src/main/java/org/xbib/graphics/imageio/plugins/png/pngj/pixels/PixelsWriterMultiple.java @@ -0,0 +1,254 @@ +package org.xbib.graphics.imageio.plugins.png.pngj.pixels; + +import java.util.LinkedList; +import java.util.zip.Deflater; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import org.xbib.graphics.imageio.plugins.png.pngj.ImageInfo; + +/** + * Special pixels writer for experimental super adaptive strategy + */ +public class PixelsWriterMultiple extends PixelsWriter { + /** + * unfiltered rowsperband elements, 0 is the current (rowb). This should + * include all rows of current band, plus one + */ + protected LinkedList rows; + + /** + * bank of compressor estimators, one for each filter and (perhaps) an + * adaptive strategy + */ + protected CompressorStream[] filterBank = new CompressorStream[6]; + /** + * stored filtered rows, one for each filter (0=none is not allocated but + * linked) + */ + protected byte[][] filteredRows = new byte[5][]; + protected byte[] filteredRowTmp; // + + protected FiltersPerformance filtersPerf; + protected int rowsPerBand = 0; // This is a 'nominal' size + protected int rowsPerBandCurrent = 0; // lastRowInThisBand-firstRowInThisBand +1 : might be smaller than rowsPerBand + protected int rowInBand = -1; + protected int bandNum = -1; + protected int firstRowInThisBand, lastRowInThisBand; + private boolean tryAdaptive = true; + + protected static final int HINT_MEMORY_DEFAULT_KB = 100; + // we will consume about (not more than) this memory (in buffers, not counting the compressors) + protected int hintMemoryKb = HINT_MEMORY_DEFAULT_KB; + + private int hintRowsPerBand = 1000; // default: very large number, can be changed + + private boolean useLz4 = true; + + public PixelsWriterMultiple(ImageInfo imgInfo) { + super(imgInfo); + filtersPerf = new FiltersPerformance(imgInfo); + rows = new LinkedList(); + for (int i = 0; i < 2; i++) { + rows.add(new byte[buflen]); // we preallocate 2 rows (rowb and rowbprev) + } + filteredRowTmp = new byte[buflen]; + } + + @Override + protected void filterAndWrite(byte[] rowb) { + if (!initdone) { + init(); + } + if (rowb != rows.get(0)) { + throw new RuntimeException("?"); + } + setBandFromNewRown(); + byte[] rowbprev = rows.get(1); + for (FilterType ftype : FilterType.getAllStandardNoneLast()) { + // this has a special behaviour for NONE: filteredRows[0] is null, and the returned value is rowb + if (currentRow == 0 && ftype != FilterType.FILTER_NONE && ftype != FilterType.FILTER_SUB) { + continue; + } + byte[] filtered = filterRowWithFilterType(ftype, rowb, rowbprev, filteredRows[ftype.val]); + filterBank[ftype.val].write(filtered); + if (currentRow == 0 && ftype == FilterType.FILTER_SUB) { // litle lie, only for first row + filterBank[FilterType.FILTER_PAETH.val].write(filtered); + filterBank[FilterType.FILTER_AVERAGE.val].write(filtered); + filterBank[FilterType.FILTER_UP.val].write(filtered); + } + // adptive: report each filterted + if (tryAdaptive) { + filtersPerf.updateFromFiltered(ftype, filtered, currentRow); + } + } + filteredRows[0] = rowb; + if (tryAdaptive) { + FilterType preferredAdaptive = filtersPerf.getPreferred(); + filterBank[5].write(filteredRows[preferredAdaptive.val]); + } + if (currentRow == lastRowInThisBand) { + int best = getBestCompressor(); + // PngHelperInternal.debug("won: " + best + " (rows: " + firstRowInThisBand +":" + lastRowInThisBand + ")"); + //if(currentRow>90&¤tRow<100) PngHelperInternal.debug(String.format("row=%d ft=%s",currentRow,FilterType.getByVal(best))); + byte[] filtersAdapt = filterBank[best].getFirstBytes(); + for (int r = firstRowInThisBand, i = 0, j = lastRowInThisBand + - firstRowInThisBand; r <= lastRowInThisBand; r++, j--, i++) { + int fti = filtersAdapt[i]; + byte[] filtered = null; + if (r != lastRowInThisBand) { + filtered = filterRowWithFilterType(FilterType.getByVal(fti), rows.get(j), rows.get(j + 1), + filteredRowTmp); + } else { // no need to do this filtering, we already have it + filtered = filteredRows[fti]; + } + sendToCompressedStream(filtered); + } + } + // rotate + if (rows.size() > rowsPerBandCurrent) { + rows.addFirst(rows.removeLast()); + } else { + rows.addFirst(new byte[buflen]); + } + } + + @Override + public byte[] getRowb() { + return rows.get(0); + } + + private void setBandFromNewRown() { + boolean newBand = currentRow == 0 || currentRow > lastRowInThisBand; + if (currentRow == 0) { + bandNum = -1; + } + if (newBand) { + bandNum++; + rowInBand = 0; + } else { + rowInBand++; + } + if (newBand) { + firstRowInThisBand = currentRow; + lastRowInThisBand = firstRowInThisBand + rowsPerBand - 1; + int lastRowInNextBand = firstRowInThisBand + 2 * rowsPerBand - 1; + if (lastRowInNextBand >= imgInfo.rows) // hack:make this band bigger, so we don't have a small last band + { + lastRowInThisBand = imgInfo.rows - 1; + } + rowsPerBandCurrent = 1 + lastRowInThisBand - firstRowInThisBand; + tryAdaptive = rowsPerBandCurrent > 3 && (rowsPerBandCurrent >= 10 || imgInfo.bytesPerRow >= 64); + // rebuild bank + rebuildFiltersBank(); + } + } + + private void rebuildFiltersBank() { + long bytesPerBandCurrent = rowsPerBandCurrent * (long) buflen; + final int DEFLATER_COMP_LEVEL = 4; + for (int i = 0; i <= 5; i++) {// one for each filter plus one adaptive + CompressorStream cp = filterBank[i]; + if (cp == null || cp.totalbytes != bytesPerBandCurrent) { + if (cp != null) { + cp.close(); + } + if (useLz4) { + cp = new CompressorStreamLz4(null, buflen, bytesPerBandCurrent); + } else { + cp = new CompressorStreamDeflater(null, buflen, bytesPerBandCurrent, DEFLATER_COMP_LEVEL, + Deflater.DEFAULT_STRATEGY); + } + filterBank[i] = cp; + } else { + cp.reset(); + } + cp.setStoreFirstByte(true, rowsPerBandCurrent); // TODO: only for adaptive? + } + } + + private int computeInitialRowsPerBand() { + // memory (only buffers) ~ (r+1+5) * bytesPerRow + int r = (int) ((hintMemoryKb * 1024.0) / (imgInfo.bytesPerRow + 1) - 5); + if (r < 1) { + r = 1; + } + if (hintRowsPerBand > 0 && r > hintRowsPerBand) { + r = hintRowsPerBand; + } + if (r > imgInfo.rows) { + r = imgInfo.rows; + } + if (r > 2 && r > imgInfo.rows / 8) { // redistribute more evenly + int k = (imgInfo.rows + (r - 1)) / r; + r = (imgInfo.rows + k / 2) / k; + } + // PngHelperInternal.debug("rows :" + r + "/" + imgInfo.rows); + return r; + } + + private int getBestCompressor() { + double bestcr = Double.MAX_VALUE; + int bestb = -1; + for (int i = tryAdaptive ? 5 : 4; i >= 0; i--) { + CompressorStream fb = filterBank[i]; + double cr = fb.getCompressionRatio(); + if (cr <= bestcr) { // dirty trick, here the equality gains for row 0, so that SUB is prefered over PAETH, UP, AVE... + bestb = i; + bestcr = cr; + } + } + return bestb; + } + + @Override + protected void initParams() { + super.initParams(); + // if adaptative but too few rows or columns, use default + if (imgInfo.cols < 3 && !FilterType.isValidStandard(filterType)) { + filterType = FilterType.FILTER_DEFAULT; + } + if (imgInfo.rows < 3 && !FilterType.isValidStandard(filterType)) { + filterType = FilterType.FILTER_DEFAULT; + } + for (int i = 1; i <= 4; i++) { // element 0 is not allocated + if (filteredRows[i] == null || filteredRows[i].length < buflen) { + filteredRows[i] = new byte[buflen]; + } + } + if (rowsPerBand == 0) { + rowsPerBand = computeInitialRowsPerBand(); + } + } + + @Override + public void close() { + super.close(); + rows.clear(); + for (CompressorStream f : filterBank) { + f.close(); + } + } + + public void setHintMemoryKb(int hintMemoryKb) { + this.hintMemoryKb = hintMemoryKb <= 0 ? HINT_MEMORY_DEFAULT_KB : (hintMemoryKb > 10000 ? 10000 : hintMemoryKb); + } + + public void setHintRowsPerBand(int hintRowsPerBand) { + this.hintRowsPerBand = hintRowsPerBand; + } + + public void setUseLz4(boolean lz4) { + this.useLz4 = lz4; + } + + /** + * for tuning memory or other parameters + */ + public FiltersPerformance getFiltersPerf() { + return filtersPerf; + } + + public void setTryAdaptive(boolean tryAdaptive) { + this.tryAdaptive = tryAdaptive; + } + +} diff --git a/png/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/png/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 0000000..883ad6e --- /dev/null +++ b/png/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1 @@ +o \ No newline at end of file diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/BufferedImageChildTest.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/BufferedImageChildTest.java new file mode 100644 index 0000000..38108cb --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/BufferedImageChildTest.java @@ -0,0 +1,62 @@ +package org.xbib.graphics.imageio.plugins.png; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import javax.imageio.ImageIO; + +public class BufferedImageChildTest { + + BufferedImage getSample() { + BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_4BYTE_ABGR); + Graphics graphics = bi.getGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, 25, 25); + graphics.setColor(Color.BLUE); + graphics.fillRect(25, 0, 25, 25); + graphics.setColor(Color.YELLOW); + graphics.fillRect(0, 25, 25, 25); + graphics.setColor(Color.RED); + graphics.fillRect(25, 25, 25, 25); + graphics.dispose(); + return bi; + } + + @Test + public void testSmallerSameOrigin() throws Exception { + testSubImage(0, 0, 25, 25); + } + + @Test + public void testSmallerTranslateX() throws Exception { + testSubImage(25, 0, 25, 25); + } + + @Test + public void testSmallerTranslateY() throws Exception { + testSubImage(0, 25, 25, 25); + } + + @Test + public void testSmallerTranslateXY() throws Exception { + testSubImage(25, 25, 25, 25); + } + + private void testSubImage(int x, int y, int w, int h) throws Exception { + BufferedImage bi = getSample(); + // ImageAssert.showImage("Original", 2000, bi); + BufferedImage subimage = bi.getSubimage(x, y, w, h); + // ImageAssert.showImage("Subimage", 2000, subimage); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + float quality = 4f / 9 - 1; + new PNGWriter().writePNG(subimage, bos, -quality, FilterType.FILTER_NONE); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + BufferedImage readBack = ImageIO.read(bis); + // ImageAssert.showImage("ReadBack", 2000, readBack); + ImageAssert.assertImagesEqual(subimage, readBack); + } +} diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/BufferedImageTypesTest.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/BufferedImageTypesTest.java new file mode 100644 index 0000000..3d4c062 --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/BufferedImageTypesTest.java @@ -0,0 +1,67 @@ +package org.xbib.graphics.imageio.plugins.png; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.imageio.ImageIO; + +public class BufferedImageTypesTest { + + static final int WIDTH = 1024; + + static final int HEIGTH = 1024; + + static final int STROKE_WIDTH = 30; + + static final int LINES = 200; + + BufferedImage image; + + String name; + + public BufferedImageTypesTest(String name, int imageType) { + this.name = name; + image = new BufferedImage(WIDTH, HEIGTH, imageType); + new SampleImagePainter().paintImage(image); + } + + //@Parameters(name = "{0}") + public static Collection parameters() throws Exception { + String[] types = new String[]{"4BYTE_ABGR", "INT_ARGB", "3BYTE_BGR", "INT_BGR", + "INT_RGB", "BYTE_INDEXED", "BYTE_GRAY"}; + List parameters = new ArrayList(); + for (String type : types) { + Field field = BufferedImage.class.getDeclaredField("TYPE_" + type); + int imageType = (Integer) field.get(null); + parameters.add(new Object[]{type.toLowerCase(), imageType}); + } + return parameters; + } + + @Test + public void compareImage() throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + float quality = 4f / 9 - 1; + new PNGWriter().writePNG(image, bos, -quality, FilterType.FILTER_NONE); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + BufferedImage readBack = ImageIO.read(bis); + + boolean success = false; + try { + ImageAssert.assertImagesEqual(image, readBack); + success = true; + } finally { + if (!success) { + ImageIO.write(image, "PNG", new File("./build/" + name + "_expected.png")); + ImageIO.write(readBack, "PNG", new File("./build/" + name + "_actual.png")); + } + } + } +} diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/CustomByteIndexImageTypesTest.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/CustomByteIndexImageTypesTest.java new file mode 100644 index 0000000..3d8ffdd --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/CustomByteIndexImageTypesTest.java @@ -0,0 +1,88 @@ +package org.xbib.graphics.imageio.plugins.png; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.imageio.ImageIO; + +public class CustomByteIndexImageTypesTest { + + private final int ncolors; + + private final int size; + + public CustomByteIndexImageTypesTest(int ncolors, int size) { + this.ncolors = ncolors; + this.size = size; + } + + //@Parameters(name = "colors{0}/size{1}") + public static Collection parameters() { + List result = new ArrayList(); + for (int ncolors : new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, + 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, + 197, 199, 211, 223, 227, 229, 233, 239, 241, 255, 256}) { + for (int size = 1; size <= 8; size++) { + result.add(new Object[]{ncolors, size}); + } + } + + return result; + } + + @Test + public void testCustomIndexedImage() throws Exception { + byte[] colors = new byte[ncolors]; + for (int i = 0; i < ncolors; i++) { + colors[i] = (byte) i; + } + int nbits; + if (ncolors <= 2) { + nbits = 1; + } else { + nbits = (int) Math.ceil(Math.log(ncolors) / Math.log(2)); + if ((nbits & (nbits - 1)) != 0) { + int nextPower = (int) (Math.floor(Math.log(nbits) / Math.log(2)) + 1); + nbits = (int) Math.pow(2, nextPower); + } + } + + IndexColorModel icm = new IndexColorModel(nbits, ncolors, colors, colors, colors); + SampleModel sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, size, size, nbits); + int pixelsPerByte = 8 / nbits; + int bytesPerRow = (int) Math.max(1, Math.ceil(1d * size / pixelsPerByte)); + int bytes = bytesPerRow * size; + DataBufferByte dataBuffer = new DataBufferByte(bytes); + WritableRaster wr = Raster.createWritableRaster(sm, dataBuffer, new Point(0, 0)); + BufferedImage bi = new BufferedImage(icm, wr, false, null); + Graphics2D graphics = bi.createGraphics(); + graphics.setColor(Color.BLACK); + graphics.fillRect(0, 0, 16, 32); + graphics.setColor(Color.WHITE); + graphics.fillRect(16, 0, 16, 32); + graphics.dispose(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + float quality = 5f / 9 - 1; + new PNGWriter().writePNG(bi, bos, -quality, FilterType.FILTER_NONE); + + BufferedImage read = ImageIO.read(new ByteArrayInputStream(bos.toByteArray())); + ImageAssert.assertImagesEqual(bi, read); + } +} diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/CustomUShortImageTypesTest.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/CustomUShortImageTypesTest.java new file mode 100644 index 0000000..b101298 --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/CustomUShortImageTypesTest.java @@ -0,0 +1,57 @@ +package org.xbib.graphics.imageio.plugins.png; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; + +public class CustomUShortImageTypesTest { + + private final int nbits; + private final int size; + + public CustomUShortImageTypesTest(int nbits, int size) { + this.nbits = nbits; + this.size = size; + } + + //@Parameters(name = "bits{0}/size{1}") + public static Collection parameters() { + List result = new ArrayList(); + for (int nbits : new int[]{1, 2, 4, 8, 16}) { + for (int size = 1; size <= 32; size++) { + result.add(new Object[]{nbits, size}); + } + } + + return result; + } + + @Test + public void testCustomUShortImage() throws Exception { + BufferedImage bi = ImageTypeSpecifier.createGrayscale(nbits, DataBuffer.TYPE_USHORT, false) + .createBufferedImage(size, size); + Graphics2D graphics = bi.createGraphics(); + graphics.setColor(Color.BLACK); + graphics.fillRect(0, 0, 16, 32); + graphics.setColor(Color.WHITE); + graphics.fillRect(16, 0, 16, 32); + graphics.dispose(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + float quality = 5f / 9 - 1; + new PNGWriter().writePNG(bi, bos, -quality, FilterType.FILTER_NONE); + + BufferedImage read = ImageIO.read(new ByteArrayInputStream(bos.toByteArray())); + ImageAssert.assertImagesEqual(bi, read); + } +} diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/ImageAssert.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/ImageAssert.java new file mode 100644 index 0000000..fd38bb8 --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/ImageAssert.java @@ -0,0 +1,84 @@ + +package org.xbib.graphics.imageio.plugins.png; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.HeadlessException; +import java.awt.Panel; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; + +public class ImageAssert { + + public static void assertImagesEqual(BufferedImage original, BufferedImage image) { + assertEquals(original.getWidth(), image.getWidth()); + assertEquals(original.getHeight(), image.getHeight()); + // these tests got disabled, as depending on the reader being used you can get a different + // structure back + // assertEquals(original.getSampleModel(), image.getSampleModel()); + // assertEquals(original.getColorModel(), image.getColorModel()); + + for (int x = 0; x < original.getWidth(); x++) { + for (int y = 0; y < original.getHeight(); y++) { + int rgbOriginal = original.getRGB(x, y); + int rgbActual = image.getRGB(x, y); + if (rgbOriginal != rgbActual) { + fail("Comparison failed at x:" + x + ", y: " + y + ", expected " + + colorToString(rgbOriginal) + ", got " + colorToString(rgbActual)); + } + } + } + } + + private static String colorToString(int rgb) { + Color c = new Color(rgb); + return "RGBA[" + c.getRed() + ", " + c.getGreen() + ", " + c.getBlue() + ", " + + c.getAlpha() + "]"; + } + + public static void showImage(String title, long timeOut, final BufferedImage image) + throws InterruptedException { + final String headless = System.getProperty("java.awt.headless", "false"); + if (!headless.equalsIgnoreCase("true")) { + try { + Frame frame = new Frame(title); + frame.addWindowListener(new WindowAdapter() { + + public void windowClosing(WindowEvent e) { + e.getWindow().dispose(); + } + }); + + Panel p = new Panel() { + + /** serialVersionUID field */ + private static final long serialVersionUID = 1L; + + { + setPreferredSize(new Dimension(image.getWidth(), image.getHeight())); + } + + public void paint(Graphics g) { + g.drawImage(image, 0, 0, this); + } + + }; + + frame.add(p); + frame.pack(); + frame.setVisible(true); + + Thread.sleep(timeOut); + frame.dispose(); + } catch (HeadlessException exception) { + // The test is running on a machine without X11 display. Ignore. + } + } + } + +} diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/PNGWriterTest.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/PNGWriterTest.java new file mode 100644 index 0000000..da02933 --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/PNGWriterTest.java @@ -0,0 +1,76 @@ +package org.xbib.graphics.imageio.plugins.png; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.Test; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import org.xbib.graphics.imageio.plugins.png.pngj.PngReader; +import org.xbib.graphics.imageio.plugins.png.pngj.chunks.PngMetadata; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import javax.imageio.ImageIO; + +public class PNGWriterTest { + + @Test + public void testWriter() throws Exception { + PNGWriter writer = new PNGWriter(); + OutputStream out = null; + try { + // read test image + BufferedImage read = ImageIO.read(new File("sample.jpeg")); + File pngOut = new File("build/test.png"); + out = new FileOutputStream(pngOut); + writer.writePNG(read, out, 1, FilterType.FILTER_NONE); + BufferedImage test = ImageIO.read(pngOut); + assertNotNull(test); + } finally { + if (out != null) { + out.close(); + } + } + } + + @Test + public void testTeXt() throws Exception { + PNGWriter writer = new PNGWriter(); + OutputStream out = null; + File pngOut = null; + final String title = "Title"; + final String description = "Sample Description"; + final String software = "ImageIO-Ext"; + final String author = "Me"; + try { + BufferedImage read = ImageIO.read(new File("sample.jpeg")); + pngOut = new File("build/test.png"); + out = new FileOutputStream(pngOut); + Map textMetadata = new HashMap(); + textMetadata.put("Title", title); + textMetadata.put("Author", author); + textMetadata.put("Software", software); + textMetadata.put("Description", description); + + writer.writePNG(read, out, 1, FilterType.FILTER_NONE, textMetadata); + } finally { + if (out != null) { + out.close(); + } + } + BufferedImage test = ImageIO.read(pngOut); + assertNotNull(test); + try (PngReader reader = new PngReader(pngOut)) { + reader.readSkippingAllRows(); + PngMetadata metadata = reader.getMetadata(); + assertNotNull(metadata); + assertEquals(title, metadata.getTxtForKey("Title")); + assertEquals(description, metadata.getTxtForKey("Description")); + assertEquals(author, metadata.getTxtForKey("Author")); + assertEquals(software, metadata.getTxtForKey("Software")); + } + } +} diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/PngSuiteImagesTest.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/PngSuiteImagesTest.java new file mode 100644 index 0000000..06bcbb4 --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/PngSuiteImagesTest.java @@ -0,0 +1,84 @@ + +package org.xbib.graphics.imageio.plugins.png; + +import org.junit.jupiter.api.Test; +import org.xbib.graphics.imageio.plugins.png.pngj.FilterType; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.imageio.ImageIO; + +public class PngSuiteImagesTest { + + private final File sourceFile; + + public PngSuiteImagesTest(File sourceFile) { + this.sourceFile = sourceFile; + } + + //@Parameters(name = "{0}") + public static Collection parameters() { + List result = new ArrayList(); + File source = new File("./src/test/resources/pngsuite"); + File[] files = source.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".png"); + } + }); + Arrays.sort(files); + for (File file : files) { + result.add(new Object[]{file}); + } + + return result; + } + + @Test + public void testRoundTripFilterNone() throws Exception { + BufferedImage input = ImageIO.read(sourceFile); + roundTripPNGJ(input); + } + + @Test + public void testRoundTripTiledImage() throws Exception { + BufferedImage input = ImageIO.read(sourceFile); + roundTripPNGJ(input); + } + + private void roundTripPNGJ(BufferedImage original) throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + float quality = 4f / 9 - 1; + new PNGWriter().writePNG(original, bos, -quality, FilterType.FILTER_NONE); + // write the output to file for eventual visual comparison + byte[] bytes = bos.toByteArray(); + writeToFile(new File("./build/roundTripNone", sourceFile.getName()), bytes); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + BufferedImage image = ImageIO.read(bis); + ImageAssert.assertImagesEqual(original, image); + } + + private void writeToFile(File file, byte[] bytes) throws IOException { + File parent = file.getParentFile(); + parent.mkdirs(); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + fos.write(bytes); + } finally { + if (fos != null) { + fos.close(); + } + } + } + +} diff --git a/png/src/test/java/org/xbib/graphics/imageio/plugins/png/SampleImagePainter.java b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/SampleImagePainter.java new file mode 100644 index 0000000..fc57873 --- /dev/null +++ b/png/src/test/java/org/xbib/graphics/imageio/plugins/png/SampleImagePainter.java @@ -0,0 +1,82 @@ +package org.xbib.graphics.imageio.plugins.png; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +/** + * Helper painting a random buffered image, to be used for tests + */ +class SampleImagePainter { + + int lines = 200; + + int labels = 50; + + int strokeWidth = 30; + + public void paintImage(BufferedImage image) { + Graphics2D g = image.createGraphics(); + + // setup some basic rendering hints, mimicks the output we'd get from GeoServer + final Map hintsMap = new HashMap(); + hintsMap.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + hintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + hintsMap.put(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + hintsMap.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + g.setRenderingHints(hintsMap); + + Random random = new Random(0); + for (int i = 0; i < lines; i++) { + int x1 = (int) (random.nextDouble() * image.getWidth()); + int y1 = (int) (random.nextDouble() * image.getHeight()); + int x2 = (int) (random.nextDouble() * image.getWidth()); + int y2 = (int) (random.nextDouble() * image.getHeight()); + int w = (int) (random.nextDouble() * (strokeWidth - 1) + 1); + g.setStroke(new BasicStroke(w)); + g.setColor(new Color((int) (random.nextDouble() * Integer.MAX_VALUE))); + g.drawLine(x1, y1, x2, y2); + } + + g.setColor(Color.BLACK); + g.setFont(new Font("Arial", Font.BOLD, 32)); + for (int i = 0; i < labels; i++) { + int x1 = (int) (random.nextDouble() * image.getWidth()); + int y1 = (int) (random.nextDouble() * image.getHeight()); + g.drawString("TestLabel", x1, y1); + } + + g.dispose(); + } + + public int getLines() { + return lines; + } + + public void setLines(int lines) { + this.lines = lines; + } + + public int getStrokeWidth() { + return strokeWidth; + } + + public void setStrokeWidth(int strokeWidth) { + this.strokeWidth = strokeWidth; + } + + public int getLabels() { + return labels; + } + + public void setLabels(int labels) { + this.labels = labels; + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..29bb9ff --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +include 'png' +include 'io-vector' +include 'chart' +include 'barcode' +include 'layout-pdfbox' \ No newline at end of file diff --git a/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterBackport.java b/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterBackport.java deleted file mode 100644 index 9e242a6..0000000 --- a/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterBackport.java +++ /dev/null @@ -1,1274 +0,0 @@ -/* - * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.sun.imageio.plugins.png; - -import javax.imageio.IIOException; -import javax.imageio.IIOImage; -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.spi.ImageWriterSpi; -import javax.imageio.stream.ImageOutputStream; -import javax.imageio.stream.ImageOutputStreamImpl; -import java.awt.Rectangle; -import java.awt.image.IndexColorModel; -import java.awt.image.Raster; -import java.awt.image.RenderedImage; -import java.awt.image.SampleModel; -import java.awt.image.WritableRaster; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Iterator; -import java.util.Locale; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -final class CRCBackport { - - private static final int[] crcTable = new int[256]; - - static { - // Initialize CRC table - for (int n = 0; n < 256; n++) { - int c = n; - for (int k = 0; k < 8; k++) { - if ((c & 1) == 1) { - c = 0xedb88320 ^ (c >>> 1); - } else { - c >>>= 1; - } - - crcTable[n] = c; - } - } - } - - private int crc = 0xffffffff; - - CRCBackport() { - } - - void reset() { - crc = 0xffffffff; - } - - void update(byte[] data, int off, int len) { - int c = crc; - for (int n = 0; n < len; n++) { - c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8); - } - crc = c; - } - - void update(int data) { - crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8); - } - - int getValue() { - return ~crc; - } -} - -final class ChunkStreamBackport extends ImageOutputStreamImpl { - - private final ImageOutputStream stream; - - private final long startPos; - - private final CRCBackport crc = new CRCBackport(); - - ChunkStreamBackport(int type, ImageOutputStream stream) throws IOException { - this.stream = stream; - this.startPos = stream.getStreamPosition(); - - stream.writeInt(-1); // length, will backpatch - writeInt(type); - } - - @Override - public int read() { - throw new UnsupportedOperationException("Method not available"); - } - - @Override - public int read(byte[] b, int off, int len) { - throw new UnsupportedOperationException("Method not available"); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - crc.update(b, off, len); - stream.write(b, off, len); - } - - @Override - public void write(int b) throws IOException { - crc.update(b); - stream.write(b); - } - - void finish() throws IOException { - // Write CRC - stream.writeInt(crc.getValue()); - - // Write length - long pos = stream.getStreamPosition(); - stream.seek(startPos); - stream.writeInt((int) (pos - startPos) - 12); - - // Return to end of chunk and flush to minimize buffering - stream.seek(pos); - stream.flushBefore(pos); - } - - @Override - protected void finalize() throws Throwable { - // Empty finalizer (for improved performance; no need to call - // super.finalize() in this case) - } -} - -/* - * Compress output and write as a series of 'IDAT' chunks of - * fixed length. - */ -final class IDATOutputStreamBackport extends ImageOutputStreamImpl { - - private static final byte[] chunkType = { - (byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T' - }; - - private final ImageOutputStream stream; - private final int chunkLength; - private final CRCBackport crc = new CRCBackport(); - private final Deflater def; - private final byte[] buf = new byte[512]; - // reused 1 byte[] array: - private final byte[] wbuf1 = new byte[1]; - private long startPos; - private int bytesRemaining; - - IDATOutputStreamBackport(ImageOutputStream stream, int chunkLength, - int deflaterLevel) throws IOException { - this.stream = stream; - this.chunkLength = chunkLength; - this.def = new Deflater(deflaterLevel); - - startChunk(); - } - - private void startChunk() throws IOException { - crc.reset(); - this.startPos = stream.getStreamPosition(); - stream.writeInt(-1); // length, will backpatch - - crc.update(chunkType, 0, 4); - stream.write(chunkType, 0, 4); - - this.bytesRemaining = chunkLength; - } - - private void finishChunk() throws IOException { - // Write CRC - stream.writeInt(crc.getValue()); - - // Write length - long pos = stream.getStreamPosition(); - stream.seek(startPos); - stream.writeInt((int) (pos - startPos) - 12); - - // Return to end of chunk and flush to minimize buffering - stream.seek(pos); - try { - stream.flushBefore(pos); - } catch (IOException e) { - /* - * If flushBefore() fails we try to access startPos in finally - * block of write_IDAT(). We should update startPos to avoid - * IndexOutOfBoundException while seek() is happening. - */ - this.startPos = stream.getStreamPosition(); - throw e; - } - } - - @Override - public int read() { - throw new UnsupportedOperationException("Method not available"); - } - - @Override - public int read(byte[] b, int off, int len) { - throw new UnsupportedOperationException("Method not available"); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (len == 0) { - return; - } - - if (!def.finished()) { - def.setInput(b, off, len); - while (!def.needsInput()) { - deflate(); - } - } - } - - private void deflate() throws IOException { - int len = def.deflate(buf, 0, buf.length); - int off = 0; - - while (len > 0) { - if (bytesRemaining == 0) { - finishChunk(); - startChunk(); - } - - int nbytes = Math.min(len, bytesRemaining); - crc.update(buf, off, nbytes); - stream.write(buf, off, nbytes); - - off += nbytes; - len -= nbytes; - bytesRemaining -= nbytes; - } - } - - @Override - public void write(int b) throws IOException { - wbuf1[0] = (byte) b; - write(wbuf1, 0, 1); - } - - void finish() throws IOException { - try { - if (!def.finished()) { - def.finish(); - while (!def.finished()) { - deflate(); - } - } - finishChunk(); - } finally { - def.end(); - } - } - - @Override - protected void finalize() throws Throwable { - // Empty finalizer (for improved performance; no need to call - // super.finalize() in this case) - } -} - - -final class PNGImageWriteParamBackport extends ImageWriteParam { - - /** - * Default quality level = 0.5 ie medium compression - */ - private static final float DEFAULT_QUALITY = 0.5f; - - private static final String[] compressionNames = {"Deflate"}; - private static final float[] qualityVals = {0.00F, 0.30F, 0.75F, 1.00F}; - private static final String[] qualityDescs = { - "High compression", // 0.00 -> 0.30 - "Medium compression", // 0.30 -> 0.75 - "Low compression" // 0.75 -> 1.00 - }; - - PNGImageWriteParamBackport(Locale locale) { - super(); - this.canWriteProgressive = true; - this.locale = locale; - this.canWriteCompressed = true; - this.compressionTypes = compressionNames; - this.compressionType = compressionTypes[0]; - this.compressionMode = MODE_DEFAULT; - this.compressionQuality = DEFAULT_QUALITY; - } - - /** - * Removes any previous compression quality setting. - * The default implementation resets the compression quality - * to 0.5F. - * - * @throws IllegalStateException if the compression mode is not - * MODE_EXPLICIT. - */ - @Override - public void unsetCompression() { - super.unsetCompression(); - this.compressionType = compressionTypes[0]; - this.compressionQuality = DEFAULT_QUALITY; - } - - /** - * Returns true since the PNG plug-in only supports - * lossless compression. - * - * @return true. - */ - @Override - public boolean isCompressionLossless() { - return true; - } - - @Override - public String[] getCompressionQualityDescriptions() { - super.getCompressionQualityDescriptions(); - return qualityDescs.clone(); - } - - @Override - public float[] getCompressionQualityValues() { - super.getCompressionQualityValues(); - return qualityVals.clone(); - } -} - -/** - */ -public final class PNGImageWriterBackport extends ImageWriter { - - /** - * Default compression level = 4 ie medium compression - */ - private static final int DEFAULT_COMPRESSION_LEVEL = 4; - - private ImageOutputStream stream = null; - - private PNGMetadata metadata = null; - - // Factors from the ImageWriteParam - private int sourceXOffset = 0; - private int sourceYOffset = 0; - private int sourceWidth = 0; - private int sourceHeight = 0; - private int[] sourceBands = null; - private int periodX = 1; - private int periodY = 1; - - private int numBands; - private int bpp; - - private RowFilter rowFilter = new RowFilter(); - - // Per-band scaling tables - // - // After the first call to initializeScaleTables, either scale and scale0 - // will be valid, or scaleh and scalel will be valid, but not both. - // - // The tables will be designed for use with a set of input but depths - // given by sampleSize, and an output bit depth given by scalingBitDepth. - // - private int[] sampleSize = null; // Sample size per band, in bits - private int scalingBitDepth = -1; // Output bit depth of the scaling tables - - // Tables for 1, 2, 4, or 8 bit output - private byte[][] scale = null; // 8 bit table - private byte[] scale0 = null; // equivalent to scale[0] - - // Tables for 16 bit output - private byte[][] scaleh = null; // High bytes of output - private byte[][] scalel = null; // Low bytes of output - - private int totalPixels; // Total number of pixels to be written by write_IDAT - private int pixelsDone; // Running count of pixels written by write_IDAT - - PNGImageWriterBackport(ImageWriterSpi originatingProvider) { - super(originatingProvider); - } - - private static int chunkType(String typeString) { - char c0 = typeString.charAt(0); - char c1 = typeString.charAt(1); - char c2 = typeString.charAt(2); - char c3 = typeString.charAt(3); - - return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; - } - - @Override - public void setOutput(Object output) { - super.setOutput(output); - if (output != null) { - if (!(output instanceof ImageOutputStream)) { - throw new IllegalArgumentException("output not an ImageOutputStream!"); - } - this.stream = (ImageOutputStream) output; - } else { - this.stream = null; - } - } - - @Override - public ImageWriteParam getDefaultWriteParam() { - return new PNGImageWriteParamBackport(getLocale()); - } - - @Override - public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { - return null; - } - - @Override - public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, - ImageWriteParam param) { - PNGMetadata m = new PNGMetadata(); - m.initialize(imageType, imageType.getSampleModel().getNumBands()); - return m; - } - - @Override - public IIOMetadata convertStreamMetadata(IIOMetadata inData, - ImageWriteParam param) { - return null; - } - - @Override - public IIOMetadata convertImageMetadata(IIOMetadata inData, - ImageTypeSpecifier imageType, - ImageWriteParam param) { - if (inData instanceof PNGMetadata) { - return (PNGMetadata) ((PNGMetadata) inData).clone(); - } else { - return new PNGMetadata(inData); - } - } - - private void write_magic() throws IOException { - // Write signature - byte[] magic = {(byte) 137, 80, 78, 71, 13, 10, 26, 10}; - stream.write(magic); - } - - private void write_IHDR() throws IOException { - // Write IHDR chunk - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.IHDR_TYPE, stream); - cs.writeInt(metadata.IHDR_width); - cs.writeInt(metadata.IHDR_height); - cs.writeByte(metadata.IHDR_bitDepth); - cs.writeByte(metadata.IHDR_colorType); - if (metadata.IHDR_compressionMethod != 0) { - throw new IIOException( - "Only compression method 0 is defined in PNG 1.1"); - } - cs.writeByte(metadata.IHDR_compressionMethod); - if (metadata.IHDR_filterMethod != 0) { - throw new IIOException( - "Only filter method 0 is defined in PNG 1.1"); - } - cs.writeByte(metadata.IHDR_filterMethod); - if (metadata.IHDR_interlaceMethod < 0 || - metadata.IHDR_interlaceMethod > 1) { - throw new IIOException( - "Only interlace methods 0 (node) and 1 (adam7) are defined in PNG 1.1"); - } - cs.writeByte(metadata.IHDR_interlaceMethod); - cs.finish(); - } - - private void write_cHRM() throws IOException { - if (metadata.cHRM_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.cHRM_TYPE, stream); - cs.writeInt(metadata.cHRM_whitePointX); - cs.writeInt(metadata.cHRM_whitePointY); - cs.writeInt(metadata.cHRM_redX); - cs.writeInt(metadata.cHRM_redY); - cs.writeInt(metadata.cHRM_greenX); - cs.writeInt(metadata.cHRM_greenY); - cs.writeInt(metadata.cHRM_blueX); - cs.writeInt(metadata.cHRM_blueY); - cs.finish(); - } - } - - private void write_gAMA() throws IOException { - if (metadata.gAMA_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.gAMA_TYPE, stream); - cs.writeInt(metadata.gAMA_gamma); - cs.finish(); - } - } - - private void write_iCCP() throws IOException { - if (metadata.iCCP_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.iCCP_TYPE, stream); - cs.writeBytes(metadata.iCCP_profileName); - cs.writeByte(0); // null terminator - - cs.writeByte(metadata.iCCP_compressionMethod); - cs.write(metadata.iCCP_compressedProfile); - cs.finish(); - } - } - - private void write_sBIT() throws IOException { - if (metadata.sBIT_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.sBIT_TYPE, stream); - int colorType = metadata.IHDR_colorType; - if (metadata.sBIT_colorType != colorType) { - processWarningOccurred(0, - "sBIT metadata has wrong color type.\n" + - "The chunk will not be written."); - return; - } - - if (colorType == PNGImageReader.PNG_COLOR_GRAY || - colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { - cs.writeByte(metadata.sBIT_grayBits); - } else if (colorType == PNGImageReader.PNG_COLOR_RGB || - colorType == PNGImageReader.PNG_COLOR_PALETTE || - colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { - cs.writeByte(metadata.sBIT_redBits); - cs.writeByte(metadata.sBIT_greenBits); - cs.writeByte(metadata.sBIT_blueBits); - } - - if (colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA || - colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { - cs.writeByte(metadata.sBIT_alphaBits); - } - cs.finish(); - } - } - - private void write_sRGB() throws IOException { - if (metadata.sRGB_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.sRGB_TYPE, stream); - cs.writeByte(metadata.sRGB_renderingIntent); - cs.finish(); - } - } - - private void write_PLTE() throws IOException { - if (metadata.PLTE_present) { - if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY || - metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { - // PLTE cannot occur in a gray image - - processWarningOccurred(0, - "A PLTE chunk may not appear in a gray or gray alpha image.\n" + - "The chunk will not be written"); - return; - } - - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.PLTE_TYPE, stream); - - int numEntries = metadata.PLTE_red.length; - byte[] palette = new byte[numEntries * 3]; - int index = 0; - for (int i = 0; i < numEntries; i++) { - palette[index++] = metadata.PLTE_red[i]; - palette[index++] = metadata.PLTE_green[i]; - palette[index++] = metadata.PLTE_blue[i]; - } - - cs.write(palette); - cs.finish(); - } - } - - private void write_hIST() throws IOException { - if (metadata.hIST_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.hIST_TYPE, stream); - - if (!metadata.PLTE_present) { - throw new IIOException("hIST chunk without PLTE chunk!"); - } - - cs.writeChars(metadata.hIST_histogram, - 0, metadata.hIST_histogram.length); - cs.finish(); - } - } - - private void write_tRNS() throws IOException { - if (metadata.tRNS_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.tRNS_TYPE, stream); - int colorType = metadata.IHDR_colorType; - int chunkType = metadata.tRNS_colorType; - - // Special case: image is RGB and chunk is Gray - // Promote chunk contents to RGB - int chunkRed = metadata.tRNS_red; - int chunkGreen = metadata.tRNS_green; - int chunkBlue = metadata.tRNS_blue; - if (colorType == PNGImageReader.PNG_COLOR_RGB && - chunkType == PNGImageReader.PNG_COLOR_GRAY) { - chunkType = colorType; - chunkRed = chunkGreen = chunkBlue = - metadata.tRNS_gray; - } - - if (chunkType != colorType) { - processWarningOccurred(0, - "tRNS metadata has incompatible color type.\n" + - "The chunk will not be written."); - return; - } - - if (colorType == PNGImageReader.PNG_COLOR_PALETTE) { - if (!metadata.PLTE_present) { - throw new IIOException("tRNS chunk without PLTE chunk!"); - } - cs.write(metadata.tRNS_alpha); - } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) { - cs.writeShort(metadata.tRNS_gray); - } else if (colorType == PNGImageReader.PNG_COLOR_RGB) { - cs.writeShort(chunkRed); - cs.writeShort(chunkGreen); - cs.writeShort(chunkBlue); - } else { - throw new IIOException("tRNS chunk for color type 4 or 6!"); - } - cs.finish(); - } - } - - private void write_bKGD() throws IOException { - if (metadata.bKGD_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.bKGD_TYPE, stream); - int colorType = metadata.IHDR_colorType & 0x3; - int chunkType = metadata.bKGD_colorType; - - // Special case: image is RGB(A) and chunk is Gray - // Promote chunk contents to RGB - int chunkRed = metadata.bKGD_red; - int chunkGreen = metadata.bKGD_red; - int chunkBlue = metadata.bKGD_red; - if (colorType == PNGImageReader.PNG_COLOR_RGB && - chunkType == PNGImageReader.PNG_COLOR_GRAY) { - // Make a gray bKGD chunk look like RGB - chunkType = colorType; - chunkRed = chunkGreen = chunkBlue = - metadata.bKGD_gray; - } - - // Ignore status of alpha in colorType - if (chunkType != colorType) { - processWarningOccurred(0, - "bKGD metadata has incompatible color type.\n" + - "The chunk will not be written."); - return; - } - - if (colorType == PNGImageReader.PNG_COLOR_PALETTE) { - cs.writeByte(metadata.bKGD_index); - } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) { - cs.writeShort(metadata.bKGD_gray); - } else { // colorType == PNGImageReader.PNG_COLOR_RGB || - // colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA - cs.writeShort(chunkRed); - cs.writeShort(chunkGreen); - cs.writeShort(chunkBlue); - } - cs.finish(); - } - } - - private void write_pHYs() throws IOException { - if (metadata.pHYs_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.pHYs_TYPE, stream); - cs.writeInt(metadata.pHYs_pixelsPerUnitXAxis); - cs.writeInt(metadata.pHYs_pixelsPerUnitYAxis); - cs.writeByte(metadata.pHYs_unitSpecifier); - cs.finish(); - } - } - - private void write_sPLT() throws IOException { - if (metadata.sPLT_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.sPLT_TYPE, stream); - - cs.writeBytes(metadata.sPLT_paletteName); - cs.writeByte(0); // null terminator - - cs.writeByte(metadata.sPLT_sampleDepth); - int numEntries = metadata.sPLT_red.length; - - if (metadata.sPLT_sampleDepth == 8) { - for (int i = 0; i < numEntries; i++) { - cs.writeByte(metadata.sPLT_red[i]); - cs.writeByte(metadata.sPLT_green[i]); - cs.writeByte(metadata.sPLT_blue[i]); - cs.writeByte(metadata.sPLT_alpha[i]); - cs.writeShort(metadata.sPLT_frequency[i]); - } - } else { // sampleDepth == 16 - for (int i = 0; i < numEntries; i++) { - cs.writeShort(metadata.sPLT_red[i]); - cs.writeShort(metadata.sPLT_green[i]); - cs.writeShort(metadata.sPLT_blue[i]); - cs.writeShort(metadata.sPLT_alpha[i]); - cs.writeShort(metadata.sPLT_frequency[i]); - } - } - cs.finish(); - } - } - - private void write_tIME() throws IOException { - if (metadata.tIME_present) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.tIME_TYPE, stream); - cs.writeShort(metadata.tIME_year); - cs.writeByte(metadata.tIME_month); - cs.writeByte(metadata.tIME_day); - cs.writeByte(metadata.tIME_hour); - cs.writeByte(metadata.tIME_minute); - cs.writeByte(metadata.tIME_second); - cs.finish(); - } - } - - private void write_tEXt() throws IOException { - Iterator keywordIter = metadata.tEXt_keyword.iterator(); - Iterator textIter = metadata.tEXt_text.iterator(); - - while (keywordIter.hasNext()) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.tEXt_TYPE, stream); - String keyword = keywordIter.next(); - cs.writeBytes(keyword); - cs.writeByte(0); - - String text = textIter.next(); - cs.writeBytes(text); - cs.finish(); - } - } - - private byte[] deflate(byte[] b) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DeflaterOutputStream dos = new DeflaterOutputStream(baos); - dos.write(b); - dos.close(); - return baos.toByteArray(); - } - - private void write_iTXt() throws IOException { - Iterator keywordIter = metadata.iTXt_keyword.iterator(); - Iterator flagIter = metadata.iTXt_compressionFlag.iterator(); - Iterator methodIter = metadata.iTXt_compressionMethod.iterator(); - Iterator languageIter = metadata.iTXt_languageTag.iterator(); - Iterator translatedKeywordIter = - metadata.iTXt_translatedKeyword.iterator(); - Iterator textIter = metadata.iTXt_text.iterator(); - - while (keywordIter.hasNext()) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.iTXt_TYPE, stream); - - cs.writeBytes(keywordIter.next()); - cs.writeByte(0); - - Boolean compressed = flagIter.next(); - cs.writeByte(compressed ? 1 : 0); - - cs.writeByte(methodIter.next()); - - cs.writeBytes(languageIter.next()); - cs.writeByte(0); - - - cs.write(translatedKeywordIter.next().getBytes("UTF8")); - cs.writeByte(0); - - String text = textIter.next(); - if (compressed) { - cs.write(deflate(text.getBytes("UTF8"))); - } else { - cs.write(text.getBytes("UTF8")); - } - cs.finish(); - } - } - - private void write_zTXt() throws IOException { - Iterator keywordIter = metadata.zTXt_keyword.iterator(); - Iterator methodIter = metadata.zTXt_compressionMethod.iterator(); - Iterator textIter = metadata.zTXt_text.iterator(); - - while (keywordIter.hasNext()) { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.zTXt_TYPE, stream); - String keyword = keywordIter.next(); - cs.writeBytes(keyword); - cs.writeByte(0); - - int compressionMethod = methodIter.next(); - cs.writeByte(compressionMethod); - - String text = textIter.next(); - cs.write(deflate(text.getBytes("ISO-8859-1"))); - cs.finish(); - } - } - - private void writeUnknownChunks() throws IOException { - Iterator typeIter = metadata.unknownChunkType.iterator(); - Iterator dataIter = metadata.unknownChunkData.iterator(); - - while (typeIter.hasNext() && dataIter.hasNext()) { - String type = typeIter.next(); - ChunkStreamBackport cs = new ChunkStreamBackport(chunkType(type), stream); - byte[] data = dataIter.next(); - cs.write(data); - cs.finish(); - } - } - - private void encodePass(ImageOutputStream os, - RenderedImage image, - int xOffset, int yOffset, - int xSkip, int ySkip) throws IOException { - int minX = sourceXOffset; - int minY = sourceYOffset; - int width = sourceWidth; - int height = sourceHeight; - - // Adjust offsets and skips based on source subsampling factors - xOffset *= periodX; - xSkip *= periodX; - yOffset *= periodY; - ySkip *= periodY; - - // Early exit if no data for this pass - int hpixels = (width - xOffset + xSkip - 1) / xSkip; - int vpixels = (height - yOffset + ySkip - 1) / ySkip; - if (hpixels == 0 || vpixels == 0) { - return; - } - - // Convert X offset and skip from pixels to samples - xOffset *= numBands; - xSkip *= numBands; - - // Create row buffers - int samplesPerByte = 8 / metadata.IHDR_bitDepth; - int numSamples = width * numBands; - int[] samples = new int[numSamples]; - - int bytesPerRow = hpixels * numBands; - if (metadata.IHDR_bitDepth < 8) { - bytesPerRow = (bytesPerRow + samplesPerByte - 1) / samplesPerByte; - } else if (metadata.IHDR_bitDepth == 16) { - bytesPerRow *= 2; - } - - IndexColorModel icm_gray_alpha = null; - if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA && - image.getColorModel() instanceof IndexColorModel) { - // reserve space for alpha samples - bytesPerRow *= 2; - - // will be used to calculate alpha value for the pixel - icm_gray_alpha = (IndexColorModel) image.getColorModel(); - } - - byte[] currRow = new byte[bytesPerRow + bpp]; - byte[] prevRow = new byte[bytesPerRow + bpp]; - byte[][] filteredRows = new byte[5][bytesPerRow + bpp]; - - int bitDepth = metadata.IHDR_bitDepth; - for (int row = minY + yOffset; row < minY + height; row += ySkip) { - Rectangle rect = new Rectangle(minX, row, width, 1); - Raster ras = image.getData(rect); - if (sourceBands != null) { - ras = ras.createChild(minX, row, width, 1, minX, row, - sourceBands); - } - - ras.getPixels(minX, row, width, 1, samples); - - if (image.getColorModel().isAlphaPremultiplied()) { - WritableRaster wr = ras.createCompatibleWritableRaster(); - wr.setPixels(wr.getMinX(), wr.getMinY(), - wr.getWidth(), wr.getHeight(), - samples); - - image.getColorModel().coerceData(wr, false); - wr.getPixels(wr.getMinX(), wr.getMinY(), - wr.getWidth(), wr.getHeight(), - samples); - } - - // Reorder palette data if necessary - int[] paletteOrder = metadata.PLTE_order; - if (paletteOrder != null) { - for (int i = 0; i < numSamples; i++) { - samples[i] = paletteOrder[samples[i]]; - } - } - - int count = bpp; // leave first 'bpp' bytes zero - int pos = 0; - int tmp = 0; - - switch (bitDepth) { - case 1: - case 2: - case 4: - // Image can only have a single band - - int mask = samplesPerByte - 1; - for (int s = xOffset; s < numSamples; s += xSkip) { - byte val = scale0[samples[s]]; - tmp = (tmp << bitDepth) | val; - - if ((pos++ & mask) == mask) { - currRow[count++] = (byte) tmp; - tmp = 0; - pos = 0; - } - } - - // Left shift the last byte - if ((pos & mask) != 0) { - tmp <<= ((8 / bitDepth) - pos) * bitDepth; - currRow[count] = (byte) tmp; - } - break; - - case 8: - if (numBands == 1) { - for (int s = xOffset; s < numSamples; s += xSkip) { - currRow[count++] = scale0[samples[s]]; - if (icm_gray_alpha != null) { - currRow[count++] = - scale0[icm_gray_alpha.getAlpha(0xff & samples[s])]; - } - } - } else { - for (int s = xOffset; s < numSamples; s += xSkip) { - for (int b = 0; b < numBands; b++) { - currRow[count++] = scale[b][samples[s + b]]; - } - } - } - break; - - case 16: - for (int s = xOffset; s < numSamples; s += xSkip) { - for (int b = 0; b < numBands; b++) { - currRow[count++] = scaleh[b][samples[s + b]]; - currRow[count++] = scalel[b][samples[s + b]]; - } - } - break; - } - - // Perform filtering - int filterType = rowFilter.filterRow(metadata.IHDR_colorType, - currRow, prevRow, - filteredRows, - bytesPerRow, bpp); - - os.write(filterType); - os.write(filteredRows[filterType], bpp, bytesPerRow); - - // Swap current and previous rows - byte[] swap = currRow; - currRow = prevRow; - prevRow = swap; - - pixelsDone += hpixels; - processImageProgress(100.0F * pixelsDone / totalPixels); - - // If write has been aborted, just return; - // processWriteAborted will be called later - if (abortRequested()) { - return; - } - } - } - - // Use sourceXOffset, etc. - private void write_IDAT(RenderedImage image, int deflaterLevel) - throws IOException { - IDATOutputStreamBackport ios = new IDATOutputStreamBackport(stream, 32768, - deflaterLevel); - try { - if (metadata.IHDR_interlaceMethod == 1) { - for (int i = 0; i < 7; i++) { - encodePass(ios, image, - PNGImageReader.adam7XOffset[i], - PNGImageReader.adam7YOffset[i], - PNGImageReader.adam7XSubsampling[i], - PNGImageReader.adam7YSubsampling[i]); - if (abortRequested()) { - break; - } - } - } else { - encodePass(ios, image, 0, 0, 1, 1); - } - } finally { - ios.finish(); - } - } - - private void writeIEND() throws IOException { - ChunkStreamBackport cs = new ChunkStreamBackport(PNGImageReader.IEND_TYPE, stream); - cs.finish(); - } - - // Check two int arrays for value equality, always returns false - // if either array is null - private boolean equals(int[] s0, int[] s1) { - if (s0 == null || s1 == null) { - return false; - } - if (s0.length != s1.length) { - return false; - } - for (int i = 0; i < s0.length; i++) { - if (s0[i] != s1[i]) { - return false; - } - } - return true; - } - - // Initialize the scale/scale0 or scaleh/scalel arrays to - // hold the results of scaling an input value to the desired - // output bit depth - private void initializeScaleTables(int[] sampleSize) { - int bitDepth = metadata.IHDR_bitDepth; - - // If the existing tables are still valid, just return - if (bitDepth == scalingBitDepth && - equals(sampleSize, this.sampleSize)) { - return; - } - - // Compute new tables - this.sampleSize = sampleSize; - this.scalingBitDepth = bitDepth; - int maxOutSample = (1 << bitDepth) - 1; - if (bitDepth <= 8) { - scale = new byte[numBands][]; - for (int b = 0; b < numBands; b++) { - int maxInSample = (1 << sampleSize[b]) - 1; - int halfMaxInSample = maxInSample / 2; - scale[b] = new byte[maxInSample + 1]; - for (int s = 0; s <= maxInSample; s++) { - scale[b][s] = - (byte) ((s * maxOutSample + halfMaxInSample) / maxInSample); - } - } - scale0 = scale[0]; - scaleh = scalel = null; - } else { // bitDepth == 16 - // Divide scaling table into high and low bytes - scaleh = new byte[numBands][]; - scalel = new byte[numBands][]; - - for (int b = 0; b < numBands; b++) { - int maxInSample = (1 << sampleSize[b]) - 1; - int halfMaxInSample = maxInSample / 2; - scaleh[b] = new byte[maxInSample + 1]; - scalel[b] = new byte[maxInSample + 1]; - for (int s = 0; s <= maxInSample; s++) { - int val = (s * maxOutSample + halfMaxInSample) / maxInSample; - scaleh[b][s] = (byte) (val >> 8); - scalel[b][s] = (byte) (val & 0xff); - } - } - scale = null; - scale0 = null; - } - } - - @Override - public void write(IIOMetadata streamMetadata, - IIOImage image, - ImageWriteParam param) throws IIOException { - if (stream == null) { - throw new IllegalStateException("output == null!"); - } - if (image == null) { - throw new IllegalArgumentException("image == null!"); - } - if (image.hasRaster()) { - throw new UnsupportedOperationException("image has a Raster!"); - } - - RenderedImage im = image.getRenderedImage(); - SampleModel sampleModel = im.getSampleModel(); - this.numBands = sampleModel.getNumBands(); - - // Set source region and subsampling to default values - this.sourceXOffset = im.getMinX(); - this.sourceYOffset = im.getMinY(); - this.sourceWidth = im.getWidth(); - this.sourceHeight = im.getHeight(); - this.sourceBands = null; - this.periodX = 1; - this.periodY = 1; - - if (param != null) { - // Get source region and subsampling factors - Rectangle sourceRegion = param.getSourceRegion(); - if (sourceRegion != null) { - Rectangle imageBounds = new Rectangle(im.getMinX(), - im.getMinY(), - im.getWidth(), - im.getHeight()); - // Clip to actual image bounds - sourceRegion = sourceRegion.intersection(imageBounds); - sourceXOffset = sourceRegion.x; - sourceYOffset = sourceRegion.y; - sourceWidth = sourceRegion.width; - sourceHeight = sourceRegion.height; - } - - // Adjust for subsampling offsets - int gridX = param.getSubsamplingXOffset(); - int gridY = param.getSubsamplingYOffset(); - sourceXOffset += gridX; - sourceYOffset += gridY; - sourceWidth -= gridX; - sourceHeight -= gridY; - - // Get subsampling factors - periodX = param.getSourceXSubsampling(); - periodY = param.getSourceYSubsampling(); - - int[] sBands = param.getSourceBands(); - if (sBands != null) { - sourceBands = sBands; - numBands = sourceBands.length; - } - } - - // Compute output dimensions - int destWidth = (sourceWidth + periodX - 1) / periodX; - int destHeight = (sourceHeight + periodY - 1) / periodY; - if (destWidth <= 0 || destHeight <= 0) { - throw new IllegalArgumentException("Empty source region!"); - } - - // Compute total number of pixels for progress notification - this.totalPixels = destWidth * destHeight; - this.pixelsDone = 0; - - // Create metadata - IIOMetadata imd = image.getMetadata(); - if (imd != null) { - metadata = (PNGMetadata) convertImageMetadata(imd, - ImageTypeSpecifier.createFromRenderedImage(im), - null); - } else { - metadata = new PNGMetadata(); - } - - // reset compression level to default: - int deflaterLevel = DEFAULT_COMPRESSION_LEVEL; - - if (param != null) { - switch (param.getCompressionMode()) { - case ImageWriteParam.MODE_DISABLED: - deflaterLevel = Deflater.NO_COMPRESSION; - break; - case ImageWriteParam.MODE_EXPLICIT: - float quality = param.getCompressionQuality(); - if (quality >= 0f && quality <= 1f) { - deflaterLevel = 9 - Math.round(9f * quality); - } - break; - default: - } - - // Use Adam7 interlacing if set in write param - switch (param.getProgressiveMode()) { - case ImageWriteParam.MODE_DEFAULT: - metadata.IHDR_interlaceMethod = 1; - break; - case ImageWriteParam.MODE_DISABLED: - metadata.IHDR_interlaceMethod = 0; - break; - // MODE_COPY_FROM_METADATA should already be taken care of - // MODE_EXPLICIT is not allowed - default: - } - } - - // Initialize bitDepth and colorType - metadata.initialize(new ImageTypeSpecifier(im), numBands); - - // Overwrite IHDR width and height values with values from image - metadata.IHDR_width = destWidth; - metadata.IHDR_height = destHeight; - - this.bpp = numBands * ((metadata.IHDR_bitDepth == 16) ? 2 : 1); - - // Initialize scaling tables for this image - initializeScaleTables(sampleModel.getSampleSize()); - - clearAbortRequest(); - - processImageStarted(0); - - try { - write_magic(); - write_IHDR(); - - write_cHRM(); - write_gAMA(); - write_iCCP(); - write_sBIT(); - write_sRGB(); - - write_PLTE(); - - write_hIST(); - write_tRNS(); - write_bKGD(); - - write_pHYs(); - write_sPLT(); - write_tIME(); - write_tEXt(); - write_iTXt(); - write_zTXt(); - - writeUnknownChunks(); - - write_IDAT(im, deflaterLevel); - - if (abortRequested()) { - processWriteAborted(); - } else { - // Finish up and inform the listeners we are done - writeIEND(); - processImageComplete(); - } - } catch (IOException e) { - throw new IIOException("I/O error writing PNG file!", e); - } - } -} diff --git a/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterSpiBackport.java b/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterSpiBackport.java deleted file mode 100644 index fc32bb8..0000000 --- a/src/main/java/com/sun/imageio/plugins/png/PNGImageWriterSpiBackport.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.sun.imageio.plugins.png; - -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.ImageWriter; -import javax.imageio.spi.ImageWriterSpi; -import javax.imageio.spi.ServiceRegistry; -import javax.imageio.stream.ImageOutputStream; -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; -import java.awt.image.SampleModel; -import java.util.Iterator; -import java.util.Locale; - -public class PNGImageWriterSpiBackport extends ImageWriterSpi { - - private static final String vendorName = "xbib"; - - private static final String version = "1.0"; - - private static final String[] names = {"png", "PNG"}; - - private static final String[] suffixes = {"png"}; - - private static final String[] MIMETypes = {"image/png", "image/x-png"}; - - private static final String writerClassName = - "com.sun.imageio.plugins.png.PNGImageWriterBackport"; - - private static final String[] readerSpiNames = { - "com.sun.imageio.plugins.png.PNGImageReaderSpi" - }; - - public PNGImageWriterSpiBackport() { - super(vendorName, - version, - names, - suffixes, - MIMETypes, - writerClassName, - new Class[]{ImageOutputStream.class}, - readerSpiNames, - false, - null, null, - null, null, - true, - PNGMetadata.nativeMetadataFormatName, - "com.sun.imageio.plugins.png.PNGMetadataFormat", - null, null - ); - } - - @Override - public boolean canEncodeImage(ImageTypeSpecifier type) { - SampleModel sampleModel = type.getSampleModel(); - ColorModel colorModel = type.getColorModel(); - - // Find the maximum bit depth across all channels - int[] sampleSize = sampleModel.getSampleSize(); - int bitDepth = sampleSize[0]; - for (int i = 1; i < sampleSize.length; i++) { - if (sampleSize[i] > bitDepth) { - bitDepth = sampleSize[i]; - } - } - - // Ensure bitDepth is between 1 and 16 - if (bitDepth < 1 || bitDepth > 16) { - return false; - } - - // Check number of bands, alpha - int numBands = sampleModel.getNumBands(); - if (numBands < 1 || numBands > 4) { - return false; - } - - boolean hasAlpha = colorModel.hasAlpha(); - // Fix 4464413: PNGTransparency reg-test was failing - // because for IndexColorModels that have alpha, - // numBands == 1 && hasAlpha == true, thus causing - // the check below to fail and return false. - if (colorModel instanceof IndexColorModel) { - return true; - } - if ((numBands == 1 || numBands == 3) && hasAlpha) { - return false; - } - if ((numBands == 2 || numBands == 4) && !hasAlpha) { - return false; - } - - return true; - } - - @Override - public String getDescription(Locale locale) { - return "JDK9 Backport PNG image writer"; - } - - @Override - public ImageWriter createWriterInstance(Object extension) { - return new PNGImageWriterBackport(this); - } - - @Override - public void onRegistration(ServiceRegistry registry, Class category) { - Iterator others = registry.getServiceProviders(ImageWriterSpi.class, false); - while (others.hasNext()) { - ImageWriterSpi other = others.next(); - if (other != this) { - for (String formatName : other.getFormatNames()) { - if ("png".equals(formatName)) { - registry.setOrdering(ImageWriterSpi.class, this, other); - break; - } - } - } - } - } -} diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi deleted file mode 100644 index 50a4998..0000000 --- a/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi +++ /dev/null @@ -1 +0,0 @@ -com.sun.imageio.plugins.png.PNGImageWriterSpiBackport \ No newline at end of file diff --git a/src/test/java/com/sun/imageio/plugins/png/PNGImageWriterBackportTest.java b/src/test/java/com/sun/imageio/plugins/png/PNGImageWriterBackportTest.java deleted file mode 100644 index 4427146..0000000 --- a/src/test/java/com/sun/imageio/plugins/png/PNGImageWriterBackportTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.sun.imageio.plugins.png; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; - -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Iterator; - -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.stream.ImageOutputStream; -import javax.imageio.stream.MemoryCacheImageOutputStream; - -import org.junit.Test; - -public class PNGImageWriterBackportTest { - - @Test - public void testPrecedence() { - Iterator it = ImageIO.getImageWritersByFormatName("png"); - assertTrue(it.next() instanceof PNGImageWriterBackport); - assertTrue(it.next() instanceof PNGImageWriter); - } - - @Test - public void testCompressionLevels() throws IOException { - - BufferedImage img = ImageIO.read(getClass().getResourceAsStream("placeholder-text.gif")); - - byte[] bd = toPng(img, null); // default - byte[] b00 = toPng(img, 0.0f); // highest compression, slowest - byte[] b01 = toPng(img, 0.1f); - byte[] b02 = toPng(img, 0.2f); - byte[] b03 = toPng(img, 0.3f); - byte[] b04 = toPng(img, 0.4f); - byte[] b05 = toPng(img, 0.5f); - byte[] b06 = toPng(img, 0.6f); - byte[] b07 = toPng(img, 0.7f); - byte[] b08 = toPng(img, 0.8f); - byte[] b09 = toPng(img, 0.9f); - byte[] b10 = toPng(img, 1.0f); // lowest compression, fastest - - assertArrayEquals(bd, b05); - assertArrayEquals(bd, b06); - - assertTrue(b00.length < b01.length); - assertTrue(b01.length < b02.length); - assertTrue(b02.length < b03.length); - assertTrue(b03.length < b04.length); - assertTrue(b04.length < b05.length); - assertTrue(b05.length == b06.length); - assertTrue(b06.length < b07.length); - assertTrue(b07.length < b08.length); - assertTrue(b08.length < b09.length); - assertTrue(b09.length < b10.length); - } - - private byte[] toPng(BufferedImage img, Float compressionQuality) throws IOException { - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Iterator imageWriters = ImageIO.getImageWritersByFormatName("png"); - while (imageWriters.hasNext()) { - ImageWriter writer = imageWriters.next(); - if (writer instanceof PNGImageWriterBackport) { - ImageWriteParam writeParam = writer.getDefaultWriteParam(); - if (compressionQuality != null) { - writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - writeParam.setCompressionQuality(compressionQuality); - } - try (ImageOutputStream stream = new MemoryCacheImageOutputStream(baos)) { - writer.setOutput(stream); - writer.write(null, new IIOImage(img, null, null), writeParam); - } finally { - writer.dispose(); - } - } - } - - return baos.toByteArray(); - } -} diff --git a/src/test/resources/com/sun/imageio/plugins/png/placeholder-text.gif b/src/test/resources/com/sun/imageio/plugins/png/placeholder-text.gif deleted file mode 100644 index d905891..0000000 Binary files a/src/test/resources/com/sun/imageio/plugins/png/placeholder-text.gif and /dev/null differ