]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Save initial work on private xform API.
authorMichael R Sweet <michael.r.sweet@gmail.com>
Tue, 15 May 2018 00:03:24 +0000 (17:03 -0700)
committerMichael R Sweet <michael.r.sweet@gmail.com>
Tue, 15 May 2018 00:03:24 +0000 (17:03 -0700)
cups/xform-dither.h [new file with mode: 0644]
cups/xform-private.h [new file with mode: 0644]
cups/xform.c [new file with mode: 0644]
filter/cupsxform.c [new file with mode: 0644]

diff --git a/cups/xform-dither.h b/cups/xform-dither.h
new file mode 100644 (file)
index 0000000..e723d4e
--- /dev/null
@@ -0,0 +1,68 @@
+/* Simple ordered dither threshold matrix */
+const unsigned char threshold[64][64] =
+{
+  {   0, 235, 138,  22,  45,  98, 217,   2,  45,  98,   7, 185,  45,  97,   6, 192, 110,  37, 110,  37,  97,  45,  18, 148, 109,  37,  97,  45,   7, 185,  45,  97,   6, 192, 109,  37,  57,  82, 165,  12,  26, 130, 190,   6, 119,  31,  97,  45, 179,   8, 109,  37,  97,  45, 140,  22, 109,  37,  57,  82,   9, 177,  24, 135 },
+  { 109,  37,  57,  82,  20, 143,  57,  82, 146,  19,  57,  82,  15, 158,  87,  53,  20, 144,  13, 162, 138,  22,  53,  87, 162,  13, 138,  22,  57,  82,  22, 140,  57,  82, 153,  16, 119,  31, 109,  37, 109,  37,  57,  81,  19, 147, 213,   2,  53,  87,  21, 142,  13, 164,  53,  87, 187,   6, 152,  17, 119,  32, 110,  37 },
+  {  24, 135,  12, 167,  97,  45,  17, 151,  45,  97,  32, 119,  26, 130, 119,  32,  81,  58,  30, 121,  81,  58, 181,   8, 119,  32,  97,  45,  17, 151,  45,  97, 161,  14,  45,  97, 213,   2, 135,  24,   7, 183, 138,  22, 109,  37,  58,  81, 121,  30, 109,  37,  58,  81,  30, 121,  58,  81, 127,  27,  58,  81, 165,  12 },
+  {  58,  81,  31, 119,   5, 197,  58,  81,   9, 175, 206,   3,  58,  81,  10, 174, 222,   1,  45,  97, 206,   3, 124,  29,  58,  81,   4, 200,  58,  81,   2, 217,  58,  81,  27, 127, 108,  37,  58,  81,  30, 121,  58,  81, 167,  12,  25, 132,  15, 158,   5, 197, 135,  24, 206,   3, 142,  21,  45,  97, 203,   3, 119,  32 },
+  {   6, 190, 108,  38,  97,  45, 143,  20, 108,  38,  97,  45, 148,  18, 108,  38,  58,  81, 148,  18, 119,  32,  97,  45,  16, 153,  97,  45, 143,  20,  46,  97,  32, 119,   6, 187,  25, 132,  13, 164,  46,  97,   1, 222, 119,  32, 108,  38, 108,  38, 108,  38, 108,  38, 108,  38,  58,  81,   8, 179, 123,  29,  97,  46 },
+  {  58,  81, 161,  14, 147,  19,  53,  87,  22, 138,  14, 159,  87,  53,  22, 138,  14, 159, 108,  38,  87,  53, 177,   9,  53,  87,  14, 161,  58,  81,  22, 137,  12, 167, 108,  38, 108,  38,  81,  58, 153,  16, 123,  29,  53,  87, 187,   6, 162,  13, 132,  25,  11, 168,  25, 132,  12, 165, 119,  32,  81,  58,  14, 161 },
+  { 140,  22, 108,  38,  81,  58,   8, 181, 108,  38,  97,  46,   8, 181,  58,  81,  31, 119,   7, 185, 147,  19,  58,  81, 127,  27,  97,  46,   8, 179, 108,  38,  58,  81,  21, 142,  13, 162,   5, 194, 108,  38,  58,  81,  15, 156,  87,  53,  30, 121, 108,  38, 108,  38,  58,  81, 127,  27,  97,  46,  16, 153,  97,  46 },
+  {  58,  81,   2, 217, 123,  29,  36, 111,   1, 227, 138,  22,  97,  46,   4, 203, 108,  38, 108,  38,  97,  46,  23, 137, 222,   1, 112,  36,  58,  81,   6, 192, 137,  23,  53,  87,  30, 121,  58,  80,  23, 137, 183,   7,  36, 111,  26, 130,  59,  80,   0, 235, 130,  26, 192,   6,  46,  97, 222,   1,  53,  87, 194,   5 },
+  {  17, 151,  46,  97, 172,  10,  59,  80,  27, 127,  59,  80,  18, 148,  59,  80, 172,  10,  24, 135,   4, 200,  80,  59,  36, 112, 167,  12, 137,  23, 108,  38,  97,  46,   0, 235,  46,  97, 144,  20,  46,  97, 127,  27,  53,  87,   3, 206, 147,  19, 108,  38, 108,  38,  80,  59, 151,  17,  80,  59,  27, 127,  96,  46 },
+  {  58,  81, 126,  27,  96,  46,  15, 156,  46,  97, 172,  10,  46,  96,  14, 159, 119,  32,  59,  80,  32, 119, 156,  15,  53,  87,  27, 127,  59,  80,  21, 142,  10, 172,  80,  59, 177,   9,  80,  59,   2, 213,  59,  80,  14, 161, 108,  38,  59,  79,  23, 137,  11, 168, 137,  23,  46,  96,  32, 119,  13, 164, 140,  22 },
+  {   6, 190, 152,  17, 112,  36,  59,  79,   6, 187,  59,  79,  30, 121, 108,  38,  96,  46,  15, 156,  46,  96,  29, 123, 183,   7,  46,  96, 209,   3,  46,  96,  32, 118,  26, 130,  96,  46,  15, 158,  46,  96,  16, 153,  46,  96,  32, 118,  10, 172, 108,  38, 108,  38,  59,  79, 179,   8, 141,  21,  59,  79,  32, 119 },
+  {  87,  53,  36, 112, 203,   4, 137,  23,  96,  46,  23, 137,   4, 200,  24, 135, 235,   0,  87,  53,   8, 179, 108,  38,  87,  53,  15, 156,  53,  87, 156,  15,  59,  79, 156,  15, 119,  32,  59,  79,  30, 121,  59,  79,   6, 190, 151,  17,  59,  79, 187,   6,  26, 130,   3, 206, 108,  38,  96,  46, 206,   3, 110,  37 },
+  { 142,  21,  59,  79,  28, 126,  59,  79, 144,  20,  59,  79, 119,  32,  60,  79,  32, 118,  27, 129,  87,  53,   3, 209, 118,  32,  60,  79, 126,  28,  96,  46,   5, 194,  46,  96, 197,   5, 134,  24,   7, 183, 137,  23,  46,  96,  36, 112,  28, 126, 108,  38, 108,  38,  60,  79, 148,  18, 118,  32,  60,  79, 167,  12 },
+  {  37, 109, 174,  10,  46,  95,  10, 174,  47,  95, 170,  11,  47,  95,   9, 177,  47,  95,  14, 159, 118,  32,  60,  79, 170,  11, 134,  24, 183,   7, 112,  36,  79,  60, 140,  22, 108,  38, 107,  38, 107,  39,  79,  60,  17, 149,  60,  79, 213,   2,  24, 135, 177,   9, 159,  14,  47,  95,   9, 175, 137,  23,  47,  95 },
+  {   0, 235,  60,  79,  18, 149,  87,  53, 217,   2,  53,  87, 156,  15,  86,  53,  16, 153,  54,  86, 167,  12, 137,  23, 107,  39,  60,  79,  36, 112, 172,  10, 147,  19,  47,  95,  19, 147,   9, 175, 132,  25,   1, 227,  36, 110,   9, 175, 112,  36,  60,  79,  30, 121,  60,  79, 126,  28, 112,  36,  60,  79,  19, 147 },
+  {  37, 109, 140,  22,  47,  95,  30, 121,  60,  78,  30, 121,  61,  78,  32, 118,  61,  78, 200,   4, 112,  36,  61,  78,  19, 147, 227,   1,  47,  95,  27, 129,  86,  54,   2, 217,  54,  86,  28, 126,  61,  78, 126,  28,  61,  78, 129,  26,  95,  47,  15, 158,  47,  95,  20, 144,   1, 222,  95,  47,   6, 190,  47,  95 },
+  {  13, 164,  61,  78,   4, 200, 162,  13,  24, 134, 183,   7,  24, 134, 213,   2, 142,  21, 123,  29,  95,  47, 192,   6, 107,  39,  86,  54, 153,  16,  54,  86,  30, 121,  61,  78,  30, 121,  95,  47, 170,  11,  47,  95,  10, 172,  47,  94, 179,   8,  61,  78,   4, 200, 107,  39,  61,  78,  22, 140,  61,  78,  23, 137 },
+  {  37, 109,  31, 120, 107,  39, 107,  39, 107,  39, 107,  39,  61,  78, 121,  30, 107,  39,  61,  78,  19, 146,  86,  54,  23, 137,  13, 164,  61,  77, 200,   4, 159,  14, 131,  25,  13, 164,   4, 200,  86,  54,  18, 148,  54,  86, 203,   4,  94,  47,  19, 146,  47,  94,  23, 137,  13, 161,  47,  94,  17, 151,  47,  94 },
+  {  26, 131,   6, 187, 158,  15,  26, 131,   4, 203,  26, 131, 177,   9,  47,  94,  14, 159,  10, 174,  94,  47, 151,  17,  61,  77,  32, 118,  27, 129, 118,  32, 107,  39, 107,  39,  86,  54, 126,  28, 118,  32,  61,  77,  31, 120,  77,  61, 126,  28,  86,  54,  14, 161, 107,  39,  77,  61,   6, 190,  61,  77,   3, 206 },
+  { 107,  39,  61,  77, 121,  30,  86,  54, 121,  30, 107,  39,  61,  77,  16, 153, 107,  39,  86,  54,   1, 222,  94,  47,   8, 179,  47,  94, 174,  10,  54,  86,  11, 168,   5, 194, 144,  20,  86,  54,  17, 152,   3, 209, 162,  13, 134,  24,  12, 167, 123,  29,  61,  77,   7, 185, 136,  23,  47,  94,  32, 118,  27, 127 },
+  { 164,  13, 144,  20,  94,  47, 174,  10,  61,  77,  11, 170, 123,  29,  37, 110,   5, 194, 123,  29,  86,  54,  20, 143,  61,  77,   3, 206,  61,  77,  18, 148, 118,  32,  85,  54,  32, 118, 177,   9,  85,  55,  28, 126, 106,  39, 106,  39,  61,  77,   0, 235, 118,  32, 106,  39,  61,  77,  19, 147, 177,   9,  81,  58 },
+  { 106,  39,  77,  61, 222,   1,  94,  47,  18, 149,  48,  93, 227,   1,  85,  55,  29, 123, 168,  11, 112,  36,  85,  55,  33, 117,  27, 129, 117,  33, 106,  39,  94,  47, 149,  18,  77,  61,  27, 129, 117,  33,  61,  77,  20, 144,   6, 190, 117,  33,  55,  85, 159,  14,  26, 131,   3, 209, 117,  32,  94,  47,  16, 153 },
+  {   5, 194, 123,  29,  55,  85,  19, 146,  62,  77, 126,  28,  37, 110, 156,  15,  62,  77,  36, 112,  17, 151, 194,   5, 151,  17,  77,  62,  17, 152,   2, 217, 175,   9,  93,  47, 227,   0,  48,  93,   6, 192, 168,  11, 117,  33,  77,  62, 161,  14, 140,  22, 106,  39, 106,  39, 106,  39,  62,  77,  28, 126,  81,  58 },
+  {  47,  94, 153,  16, 123,  29,  85,  55,   5, 197, 152,  17,  62,  77,  29, 123,  10, 174,  48,  93,  28, 126, 106,  39,  93,  48,   8, 181, 106,  39,  62,  77,  33, 117, 144,  20,  62,  77,  18, 148, 106,  39, 106,  39,  93,  48,  18, 147,  48,  93,  33, 117, 192,   6, 164,  13, 131,  26,  14, 158, 227,   1, 140,  22 },
+  {  13, 164,  85,  55,  10, 174, 117,  33, 106,  39,  93,  48,  10, 172, 106,  39,  62,  77,   2, 209,  62,  77, 161,  14, 142,  21,  62,  77,  33, 117, 143,  20, 106,  39,  93,  48,  22, 138,  48,  93, 158,  15, 131,  26,   1, 217,  85,  55, 200,   4, 106,  39, 105,  39, 105,  40, 105,  40, 105,  40, 105,  40,  87,  53 },
+  {  85,  55, 120,  31,  62,  77,   6, 187, 142,  21, 123,  29,  85,  55,   5, 194, 123,  29,  37, 110, 179,   8, 117,  33,  85,  55,  23, 136,   6, 192,  62,  77,  10, 174,   5, 197,  62,  77,   8, 179, 105,  40, 105,  40,  85,  55, 120,  31,  62,  77, 167,  12,  26, 131,  15, 158,   5, 194, 168,  11, 147,  19, 118,  32 },
+  { 135,  24,   3, 209, 136,  23, 105,  40,  62,  76, 209,   3, 123,  29,  63,  76,  15, 155,  63,  76,  27, 129,  93,  48,   0, 235, 105,  40,  93,  48,  17, 151,  48,  93,  33, 116,  16, 155,  37, 110,   4, 203, 162,  13,  24, 134,  11, 168, 136,  23, 105,  40, 105,  40, 105,  40,  63,  76, 126,  28,  85,  55,   6, 190 },
+  {  37, 109,  33, 117,  63,  76,  21, 142,  12, 165,  48,  93,  16, 155, 123,  29,  93,  48,  11, 170,  48,  93,  16, 155,  55,  85, 170,  11, 136,  23,  63,  76, 146,  19,  63,  76,  27, 129,  76,  63,  27, 129, 116,  33,  76,  63,  33, 117,  63,  76,  19, 147, 217,   2, 168,  11, 142,  21,  48,  93, 120,  31,  80,  59 },
+  { 175,   9,  93,  49,  10, 172, 105,  40,  63,  76, 125,  28,  85,  55,   8, 179, 117,  33,  84,  55, 197,   5,  55,  84,  27, 129, 116,  33,  93,  49, 213,   2,  49,  93,  11, 170,  49,  93, 172,  10, 105,  40,  93,  49,   5, 194,  49,  93, 192,   6, 116,  33,  63,  76, 120,  31,  63,  76,   3, 206, 162,  13,  26, 131 },
+  {  37, 109,  18, 149,  63,  76,   0, 235, 131,  26, 187,   6, 116,  33,  84,  55,   1, 222, 140,  22,  63,  76,  33, 116,   8, 179,  84,  55, 158,  15,  55,  84,  27, 129,  84,  55, 217,   2,  55,  84,  20, 144,  10, 174,  55,  84, 149,  18, 122,  29,  93,  49,  20, 143,  49,  93,  15, 158, 116,  33, 105,  40,  80,  59 },
+  { 138,  22,  93,  49,  22, 138, 105,  40, 105,  40,  63,  76, 159,  14, 136,  23,  63,  75,  33, 116, 164,  13, 144,  20,  93,  49,   5, 194,  63,  75,  33, 116, 185,   7, 116,  33,  75,  63,  31, 120, 104,  40,  75,  63,  31, 120, 104,  41,  75,  63, 181,   8,  84,  55,   6, 192, 104,  41,  84,  55,  21, 142, 217,   2 },
+  {  84,  55, 197,   5,  55,  84,  19, 146,  13, 164, 140,  22, 104,  41,  93,  49,  18, 149,  49,  93,  28, 125,  63,  75, 125,  28,  93,  49,  21, 141,  11, 168,  63,  75, 165,  12,  24, 134,  15, 158, 209,   3,  26, 131,  15, 158,   1, 227, 144,  20,  49,  93,  16, 153,  63,  75,  23, 136,  10, 172,  63,  75, 120,  30 },
+  {  14, 161,  75,  63, 177,   8, 116,  33,  75,  63,  33, 116,   2, 213, 175,   9,  63,  75, 190,   6,  92,  49,   2, 213, 152,  17, 111,  36,  63,  75,  36, 112,  27, 129, 111,  36, 104,  41, 104,  41, 104,  41, 104,  41, 104,  41, 104,  41,  75,  63, 125,  28,  92,  49,  14, 161, 104,  41,  92,  49,   7, 185,  49,  92 },
+  {  47,  94, 144,  20, 122,  30,  92,  49,   5, 197,  49,  92,  28, 125,  92,  49, 143,  20,  49,  92,  18, 149,  75,  63,  36, 112,  11, 168, 222,   1,  49,  92, 203,   4,  55,  84, 190,   6, 162,  13,  26, 130, 190,   6, 164,  13,  26, 130,  13, 162, 206,   3, 122,  30,  84,  55,   0, 235, 122,  30,  84,  55,  16, 152 },
+  {   6, 192, 116,  33,  75,  63,  16, 155,  63,  74,  15, 156,  64,  74,  20, 143,  84,  55,  22, 138,  92,  49, 143,  20,  49,  92,  27, 129,  55,  84, 156,  15,  56,  84,  16, 155, 116,  33, 104,  41, 104,  41, 104,  41, 104,  41, 104,  41, 103,  41,  64,  74, 181,   8, 115,  33,  64,  74, 152,  17, 122,  30,  80,  59 },
+  { 103,  41,  92,  49, 213,   2,  49,  92,  28, 125,  92,  49,   6, 185,  92,  49,   3, 206,  64,  74,   7, 185,  64,  74, 190,   6,  64,  74,  31, 120,  64,  74,  33, 117, 103,  41,  64,  74,  20, 144,   8, 181,  24, 134,  15, 158,   5, 197, 141,  21, 122,  30,  56,  84,  19, 146,  13, 164,  50,  91, 203,   4, 112,  36 },
+  {  24, 134,  12, 167,  56,  84,  34, 115, 172,  10, 136,  23,  64,  74,  18, 149,  92,  49,  18, 149,  91,  49,  15, 156,  50,  91,  20, 144,   7, 183,  24, 134, 183,   7,  24, 134, 235,   0,  50,  91,  31, 120,  64,  74, 125,  28, 115,  34,  64,  74, 161,  14, 115,  34, 103,  41,  64,  74, 125,  28,  37, 110, 165,  12 },
+  { 103,  41,  64,  74,  23, 136, 185,   7, 103,  41,  91,  50,  20, 143,  50,  91,  22, 138,  56,  84,  31, 120,  64,  74,  31, 120, 103,  41, 103,  41,  64,  74,  31, 120, 103,  41,  64,  74, 125,  28,  91,  50, 217,   2, 103,  41,  91,  50,  10, 174,  50,  91,  22, 140, 200,   4, 134,  24,   7, 183,  65,  74,  28, 126 },
+  { 227,   1, 146,  19, 103,  41,  65,  74,  21, 142, 227,   1,  65,  74,   6, 190,  65,  74,   9, 175,   0, 235,  24, 134, 206,   3, 162,  13, 130,  26, 213,   2,  50,  91,  13, 162, 131,  26, 185,   7, 152,  17,  65,  74, 170,  11, 144,  20,  56,  84,   1, 222, 103,  41, 103,  41, 103,  41,  91,  50, 175,   9,  50,  91 },
+  {  49,  93,  33, 116,   6, 187, 136,  23,  50,  91,  33, 116,  18, 149,  50,  91,  19, 147, 103,  41, 103,  41, 102,  41, 102,  42, 102,  42, 102,  42,  65,  74,  21, 141, 102,  42, 102,  42,  65,  74, 125,  28, 115,  34, 102,  42,  65,  74,  31, 119,  65,  74, 167,  12,  26, 130,  12, 165, 115,  34,  84,  56, 151,  17 },
+  { 148,  18, 102,  42, 102,  42,  84,  56, 174,  10, 102,  42,  65,  74,  22, 140,  65,  74, 159,  14, 131,  26,  11, 168,  26, 131,  15, 158,   7, 185, 159,  14,  50,  91,   5, 197, 168,  11, 141,  21,  50,  91, 179,   8,  24, 134,   4, 203, 162,  13, 142,  21, 102,  42, 102,  42,  65,  74,   2, 217, 140,  22,  80,  59 },
+  {  49,  92,   9, 175,  24, 133,   3, 209,  65,  74,  19, 146, 185,   7,  90,  50,   3, 209, 102,  42,  65,  74,  31, 120, 102,  42,  65,  74,  31, 120,  65,  73,  34, 115,  27, 129, 115,  34,  56,  84, 203,   4, 111,  36,  65,  73, 125,  28, 102,  42,  65,  73,  23, 136, 192,   6, 136,  23, 102,  42,  90,  50,   6, 187 },
+  {  31, 119, 102,  42, 102,  42,  90,  50, 138,  22, 102,  42,  65,  73, 125,  28,  65,  73,  21, 141, 194,   5,  50,  90, 192,   6, 141,  21,  50,  90,  21, 141,   6, 187, 102,  42,  65,  73,  16, 155, 122,  30,  90,  50,  14, 161,  50,  90,  34, 115, 200,   4, 102,  42, 102,  42,  84,  56,  18, 149, 115,  34,  79,  59 },
+  { 209,   3,  25, 133,   6, 187, 146,  19,  65,  73, 203,   4, 134,  24,  11, 168, 111,  36, 102,  42,  65,  73,  19, 147, 102,  42,  65,  73,   1, 227, 102,  42,  65,  73,  19, 146, 217,   2, 115,  34,  56,  84,  17, 152,  73,  65, 177,   9, 151,  17,  73,  65, 165,  12, 131,  26, 175,   9,  73,  65,   9, 174,  24, 134 },
+  {  33, 117, 102,  42,  65,  73,  34, 115,  11, 170, 115,  34,  65,  73,  36, 111, 181,   7, 168,  11, 136,  23,  50,  90, 161,  14, 146,  19,  50,  90, 159,  14, 136,  23, 102,  42, 102,  42,  73,  65,   7, 183,  90,  50,   1, 227,  50,  90,  36, 111,  27, 129, 115,  34,  72,  65,  34, 115,  27, 127, 111,  36,  79,  59 },
+  {  50,  90, 164,  13, 141,  21, 102,  42, 102,  42,  90,  50,  15, 156,  50,  90,  27, 127, 115,  34,  66,  72,   2, 217, 102,  42,  66,  72,  22, 138, 102,  42,  66,  72, 167,  12, 133,  25,  12, 165,  37, 110,  28, 125,  72,  66, 143,  20,  72,  66, 179,   8,  90,  50, 235,   0,  51,  90, 203,   4,  90,  50,   7, 183 },
+  {  17, 152, 101,  42,  66,  72, 235,   0,  25, 133,   8, 181,  66,  72, 222,   1, 101,  43,  90,  50,  17, 152,  51,  90,  23, 136, 185,   7,  51,  90,   9, 175, 203,   4, 114,  34,  72,  66, 125,  28,  72,  66, 172,  10, 146,  19,  90,  51, 209,   3,  51,  90, 153,  16,  56,  84,  16, 155,  56,  84,  16, 155,  87,  53 },
+  {  84,  56,   6, 192, 151,  17, 114,  35,  72,  66,  35, 114,  27, 127,  37, 110,  17, 152, 197,   5,  84,  56,  14, 161, 101,  43,  72,  66,  18, 149,  72,  66, 125,  28,  90,  51, 190,   6,  51,  90,   3, 206,  51,  90,  35, 114,  11, 170,  66,  72, 159,  14,  66,  72,  28, 125,  66,  72,  31, 119,  66,  72, 127,  27 },
+  { 111,  36,  66,  72, 124,  28,  90,  51,  16, 155,  51,  90,  16, 155,  66,  72,  29, 124,  66,  72, 124,  29,  66,  72,   5, 192, 136,  23,  51,  90, 144,  19,  51,  90, 149,  18,  56,  83,  18, 149,  66,  72, 142,  20, 101,  43,  84,  56,  31, 119,  83,  56,  35, 114, 170,  10,  25, 133, 183,   7, 133,  25,   1, 222 },
+  { 172,  10, 141,  21,  51,  90, 170,  11,  83,  56,   4, 200,  89,  51,  11, 170,  52,  89,  21, 141, 183,   7, 114,  35, 101,  43,  66,  72,   2, 213,  66,  72,   8, 179,  66,  72,  22, 140,  52,  89, 147,  19,  52,  89, 203,   4, 133,  25,   8, 181,  25, 133,   4, 200, 101,  43,  66,  72, 114,  35,  83,  56,  36, 111 },
+  {  28, 126,  66,  72,   3, 206,  37, 110,  29, 124,  72,  66,  31, 119,  71,  66,   4, 203, 101,  43,  67,  71, 167,  12,  25, 133,  12, 165,  52,  89,  29, 124,  89,  52,   0, 235,  52,  89, 177,   9,  56,  83,  11, 170, 114,  35,  67,  71,  35, 114, 101,  43,  67,  71,  35, 114,   2, 213,  52,  89, 177,   9,  79,  59 },
+  {  51,  90,  15, 156, 122,  30,  67,  71,   8, 181, 133,  25, 185,   7, 141,  21,  52,  89,  16, 155, 114,  35, 101,  43, 101,  43,  67,  71,  36, 111, 165,  12, 136,  23,  56,  83,  31, 119,  67,  71,  35, 114, 127,  27,  89,  52,  15, 156,  52,  89, 167,  12, 133,  25,  12, 165,  83,  56,  16, 155,  89,  52,  15, 156 },
+  {   6, 192, 101,  43,  89,  52, 148,  18, 101,  43, 101,  43, 101,  43,  67,  71, 124,  29,  67,  71,   0, 235,  25, 133, 200,   4, 133,  25, 183,   7, 101,  43,  67,  71,  19, 146, 187,   6, 133,  25, 217,   2,  67,  71,   8, 181,  67,  71,   1, 222, 114,  35, 101,  43,  67,  71,  31, 119,  67,  71,  31, 119,  79,  59 },
+  {  51,  90,  23, 135,  10, 172,  67,  71,  24, 135, 222,   1,  26, 130,  15, 158,  10, 174, 114,  35, 100,  43,  67,  71,  31, 119,  67,  70,  35, 114,  21, 141,   5, 194, 114,  35, 100,  43, 100,  43,  89,  52,  16, 155,  52,  89,  27, 127, 122,  30,  56,  83, 197,   5, 132,  25,   8, 181,  25, 133, 206,   3, 137,  23 },
+  {  18, 149, 100,  43,  89,  52, 194,   5, 100,  43, 100,  43, 100,  43, 100,  43,  68,  70,   7, 185,  26, 131, 175,   9,  52,  88,  11, 170, 100,  43, 100,  43, 100,  43,  68,  70,  21, 141,  12, 165, 111,  36,  68,  70,  35, 113,  16, 155,  56,  83,  16, 155, 113,  35,  68,  70,  35, 113,  68,  70, 113,  35,  79,  59 },
+  {  52,  88, 227,   1, 135,  24,  68,  70, 167,  12,  26, 130,  13, 162, 209,   3, 135,  24, 100,  43, 100,  43,  83,  56,  18, 148,  56,  83,   1, 222, 162,  13, 132,  25,  12, 167,  52,  88,  35, 113,   7, 183,  25, 133,   4, 203,  56,  83, 175,   9, 122,  30,  88,  52,  15, 156,  52,  88, 168,  11,  52,  88, 181,   8 },
+  {  27, 129, 113,  35,  88,  52,  20, 143, 100,  43, 100,  43, 100,  43,  99,  43,  68,  70,  19, 146,   5, 194, 113,  35,  68,  70,  35, 113,  29, 124,  99,  44,  99,  44,  68,  70,   2, 213,  68,  70,  29, 124,  99,  44,  68,  70,  31, 119,  99,  44,  70,  68, 177,   9,  70,  68, 227,   1,  83,  56,  16, 153,  56,  83 },
+  { 170,  11,  70,  68,  10, 174,  68,  70,  19, 146, 197,   5, 158,  15, 130,  26, 175,   9, 113,  35,  56,  83,  21, 140,   3, 209, 152,  17,  56,  83,  21, 141,   4, 197, 122,  30,  88,  52,  16, 153,  52,  88, 165,  12, 132,  25, 183,   7,  25, 132, 213,   2,  88,  52, 143,  20,  52,  88, 119,  31,  68,  70,  32, 118 },
+  {  88,  52,  19, 147,  88,  52, 213,   2,  99,  44,  99,  44,  99,  44,  99,  44,  99,  44,  68,  70, 161,  14, 111,  36,  99,  44,  68,  70,   7, 183,  99,  44,  70,  68,  16, 153, 122,  30,  70,  68,   5, 197, 113,  35,  68,  70,  35, 113,  68,  70,  35, 113,  29, 124,  68,  70,  24, 135, 187,   6,  25, 132,   3, 209 },
+  {  31, 119,  70,  68, 124,  29,  82,  57,  21, 141,  13, 164, 132,  25, 187,   6,  25, 132, 217,   2, 122,  30,  70,  68,  18, 148, 122,  30,  37, 110,  16, 153, 122,  30,  56,  82,  10, 172, 113,  35,  99,  44,  88,  52,   0, 235,  52,  88,  14, 159,  52,  88,  17, 152,   5, 194,  99,  44,  99,  44,  99,  44,  79,  60 },
+  {  13, 162, 200,   4, 167,  11, 111,  36,  68,  70, 119,  31,  68,  70, 119,  31,  98,  44,  98,  45,  87,  52,   6, 190,  53,  87,   9, 177,  57,  82,  30, 122,   8, 179, 113,  35,  69,  68,   7, 185, 132,  25,  12, 164,  57,  82,  17, 152,  69,  69, 177,   9,  69,  69,  35, 113, 165,  12,  26, 130,  11, 168, 132,  25 },
+  {  37, 109,  69,  69,  36, 111,  17, 151, 190,   6,  53,  87,   1, 227,  53,  87,  10, 174, 130,  26,  14, 159,  69,  69,  20, 143,  69,  69, 206,   3,  98,  45,  69,  69,   1, 227, 142,  21,  98,  45,  98,  45,  69,  69, 124,  29,  37, 110,   5, 197,  87,  53,  20, 143,  53,  87,  29, 124,  98,  45,  98,  45,  79,  60 },
+  {  26, 130,  14, 159,  53,  87,  29, 124,  82,  57,  18, 148,  82,  57,  18, 148,  98,  45,  98,  45,  87,  53, 138,  22,  53,  87, 124,  29,  37, 110, 165,  12, 142,  21,  98,  45,  69,  69,  24, 135,   3, 209, 132,  25, 181,   8,  82,  57,  27, 127, 113,  35,  82,  57, 177,   9,  57,  82, 213,   2,  25, 132,   6, 187 },
+  {  37, 109,  61,  78,   8, 179,  61,  78,  20, 143,  66,  72,  22, 138,  66,  72,  24, 135,   4, 200, 175,   9,  72,  66,   1, 227, 151,  17,  72,  66,  29, 124,  68,  70,  24, 135, 181,   8,  98,  45,  98,  45,  98,  45,  68,  70,  18, 148,  70,  68,  17, 151, 222,   1,  70,  68,  18, 148, 112,  35,  98,  45,  78,  61 }
+};
diff --git a/cups/xform-private.h b/cups/xform-private.h
new file mode 100644 (file)
index 0000000..405f3dd
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Private transform API definitions for CUPS.
+ *
+ * Copyright Â©Â 2016-2018 by Apple Inc.
+ *
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
+ */
+
+#ifndef _CUPS_XFORM_PRIVATE_H_
+#  define _CUPS_XFORM_PRIVATE_H_
+
+
+/*
+ * Include necessary headers...
+ */
+
+#  include <cups/cups.h>
+#  include <cups/raster.h>
+
+
+/*
+ * C++ magic...
+ */
+
+#  ifdef __cplusplus
+extern "C" {
+#  endif /* __cplusplus */
+
+
+/*
+ * Constants...
+ */
+
+/**** Input/Output MIME media types ****/
+#  define XFORM_FORMAT_APPLE_RASTER    "image/urf"
+#  define XFORM_FORMAT_JPEG            "image/jpeg"
+#  define XFORM_FORMAT_PCL             "application/vnd.hp-pcl"
+#  define XFORM_FORMAT_PDF             "application/pdf"
+#  define XFORM_FORMAT_POSTSCRIPT      "application/postscript"
+#  define XFORM_FORMAT_PWG_RASTER      "image/pwg-raster"
+#  define XFORM_FORMAT_TEXT            "text/plain"
+
+typedef enum xform_duplex_e            /**** 2-Sided Capabilities ****/
+{
+  XFORM_DUPLEX_NONE,                   /* No 2-sided support */
+  XFORM_DUPLEX_NORMAL,                 /* 2-sided support, normal back side orientation ('normal') */
+  XFORM_DUPLEX_LONG_TUMBLE,            /* 2-sided support, rotate back side 180 degrees for long edge ('manual-tumble') */
+  XFORM_DUPLEX_SHORT_TUMBLE,           /* 2-sided support, rotate back side 180 degrees for short edge ('rotated') */
+  XFORM_DUPLEX_MIRRORED                        /* 2-sided support, mirror back side ('flippped') */
+} xform_duplex_t;
+
+typedef enum xform_loglevel_e          /**** Logging Levels ****/
+{
+  XFORM_LOGLEVEL_DEBUG,                        /* Debugging message */
+  XFORM_LOGLEVEL_INFO,                 /* Informational message */
+  XFORM_LOGLEVEL_ERROR,                        /* Error message */
+  XFORM_LOGLEVEL_ATTR                  /* Attribute message */
+} xform_loglevel_t;
+
+/*
+ * Local types...
+ */
+
+typedef struct xform_margins_s         /**** Output margins ****/
+{
+  int          bottom,                 /* Bottom margin in hundredths of millimeters */
+               left,                   /* Left margin in hundredths of millimeters */
+               right,                  /* Right margin in hundredths of millimeters */
+               top;                    /* Top margin in hundredths of millimeters */
+} xform_margins_t;
+
+typedef struct xform_size_s            /**** Output size ****/
+{
+  int          width,                  /* Width in hundredths of millimeters */
+               length;                 /* Length in hundredths of millimeters */
+} xform_size_t;
+
+typedef struct xform_capabilities_s    /**** Output Capabilities ****/
+{
+  int          mixed;                  /* Supports pages with different colorspaces and sizes? */
+  cups_cspace_t        color,                  /* Colorspace for printing color documents */
+               monochrome,             /* Colorspace for printing B&W documents */
+               photo;                  /* Colorspace for printing photos */
+  unsigned     draft_bits,             /* Bits per color for printing draft quality */
+               normal_bits,            /* Bits per color for printing normal quality */
+               high_bits;              /* Bits per color for printing high/best/photo quality */
+  unsigned     draft_resolution[2],    /* Draft resolution */
+               normal_resolution[2],   /* Normal resolution */
+               high_resolution[2];     /* High/best/photo resolution */
+  xform_duplex_t duplex;               /* 2-sided capabilities */
+  xform_margins_t margins,             /* Default margins */
+  xform_size_t size;                   /* Default size */
+  xform_margins_t max_margins;         /* Maximum margins */
+  xform_size_t max_size;               /* Maximum size */
+  xform_margins_t min_margins;         /* Minimum margins */
+  xform_size_t min_size;               /* Minimum size */
+} xform_capabilities_t;
+
+typedef struct _xform_ctx_s xform_ctx_t;/**** Transform context ****/
+
+typedef void (*xform_logcb_t)(void *user_data, xform_loglevel_t level, const char *message);
+                                       /**** Logging callback ****/
+
+typedef ssize_t (*xform_writecb_t)(void *user_data, const unsigned char *buffer, size_t length);
+                                       /**** Output callback ****/
+
+
+/*
+ * Functions...
+ */
+
+extern void            xformDelete(xform_ctx_t *ctx);
+extern xform_ctx_t     *xformNew(const char *outformat, xform_capabilities_t *outcaps);
+extern int             xformRun(xform_ctx_t *ctx, const char *infile, const char *informat);
+extern void            xformSetLogCallback(xform_ctx_t *ctx, xform_logcb_t logcb, void *logdata);
+extern void            xformSetOptions(xform_ctx_t *ctx, int num_options, cups_option_t *options);
+extern void            xformSetWriteCallback(xform_ctx_t *ctx, xform_writecb_t writecb, void *writedata);
+
+
+#  ifdef __cplusplus
+}
+#  endif /* __cplusplus */
+
+#endif /* !_CUPS_XFORM_PRIVATE_H_ */
diff --git a/cups/xform.c b/cups/xform.c
new file mode 100644 (file)
index 0000000..4535d1c
--- /dev/null
@@ -0,0 +1,2889 @@
+/*
+ * Private transform API implementation for CUPS.
+ *
+ * Copyright Â©Â 2016-2018 by Apple Inc.
+ *
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include "xform-private.h"
+#include "xform-dither.h"
+#include <cups/ppd-private.h>
+#include "config.h"
+#ifdef __APPLE__
+#  include <CoreGraphics/CoreGraphics.h>
+extern void CGContextSetCTM(CGContextRef c, CGAffineTransform m);
+
+#elif defined(HAVE_MUPDF)
+#  include <mupdf/fitz.h>
+static inline fz_matrix fz_make_matrix(float a, float b, float c, float d, float e, float f) {
+  fz_matrix ret = { a, b, c, d, e, f };
+  return (ret);
+}
+#endif /* __APPLE__ */
+
+
+/*
+ * Constants...
+ */
+
+//#define XFORM_MAX_RASTER     16777216
+
+#  define XFORM_RED_MASK       0x000000ff
+#  define XFORM_GREEN_MASK     0x0000ff00
+#  define XFORM_BLUE_MASK      0x00ff0000
+#  define XFORM_RGB_MASK       (XFORM_RED_MASK | XFORM_GREEN_MASK |  XFORM_BLUE_MASK)
+#  define XFORM_BG_MASK                (XFORM_BLUE_MASK | XFORM_GREEN_MASK)
+#  define XFORM_RG_MASK                (XFORM_RED_MASK | XFORM_GREEN_MASK)
+
+typedef enum _xform_format_e           /**** Transform Destination Format ****/
+{
+  _XFORM_FORMAT_PDF,                   /* PDF output */
+  _XFORM_FORMAT_APPLE_RASTER,          /* Apple Raster output */
+  _XFORM_FORMAT_PWG_RASTER,            /* PWG Raster output */
+  _XFORM_FORMAT_PCL,                   /* PCL output */
+  _XFORM_FORMAT_POSTSCRIPT             /* PostScript output */
+} _xform_format_t;
+
+
+/*
+ * Types...
+ */
+
+struct _xform_ctx_s
+{
+  _xform_format_t      format;         /* Output format (TODO: Need this?) */
+  xform_capabilities_t capabilities;   /* Output capabilities */
+  int                  num_options;    /* Number of job options */
+  cups_option_t                *options;       /* Job options */
+  unsigned             copies;         /* Number of copies */
+  cups_page_header2_t  header;         /* Page header */
+  cups_page_header2_t  back_header;    /* Page header for back side */
+  cups_page_header2_t  mheader;        /* Page header for monochrome pages */
+  cups_page_header2_t  back_mheader;   /* Page header for monochrome pages on back side */
+  int                  borderless;     /* Borderless media? */
+  unsigned char                *band_buffer;   /* Band buffer */
+  unsigned             band_height;    /* Band height */
+  unsigned             band_bpp;       /* Bytes per pixel in band */
+
+  /* Set by start_job callback */
+  cups_raster_t                *ras;           /* Raster stream */
+
+  /* Set by start_page callback */
+  unsigned             left, top, right, bottom;
+                                       /* Image (print) box with origin at top left */
+  unsigned             out_blanks;     /* Blank lines */
+  size_t               out_length;     /* Output buffer size */
+  unsigned char                *out_buffer;    /* Output (bit) buffer */
+  unsigned char                *comp_buffer;   /* Compression buffer */
+
+  /* Callbacks */
+  void                 (*end_job)(xform_ctx_t *ctx);
+  void                 (*end_page)(xform_ctx_t *ctx, unsigned page);
+  void                 (*start_job)(xform_ctx_t *ctx);
+  void                 (*start_page)(xform_ctx_t *ctx, unsigned page);
+  void                 (*write_line)(xform_ctx_t *ctx, unsigned y, const unsigned char *line);
+};
+
+
+/*
+ * Local functions...
+ */
+
+static void    default_log_cb(void *user_data, int debug, const char *message);
+static void    default_write_cb(void *user_data, const unsigned char *buffer, size_t length);
+
+#ifdef HAVE_MUPDF
+static void    pack_graya(unsigned char *row, size_t num_pixels);
+#endif /* HAVE_MUPDF */
+static void    pack_rgba_to_rgb(unsigned char *row, size_t num_pixels);
+static void    pack_rgba_to_gray(unsigned char *row, size_t num_pixels);
+
+static void    pcl_end_job(xform_ctx_t *ctx);
+static void    pcl_end_page(xform_ctx_t *ctx, unsigned page);
+static void    pcl_init(xform_ctx_t *ctx);
+static void    pcl_printf(ctx, const char *format, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
+static void    pcl_start_job(xform_ctx_t *ctx);
+static void    pcl_start_page(xform_ctx_t *ctx, unsigned page);
+static void    pcl_write_line(xform_ctx_t *ctx, unsigned y, const unsigned char *line);
+
+static void    pdf_end_job(xform_ctx_t *ctx);
+static void    pdf_end_page(xform_ctx_t *ctx, unsigned page);
+static void    pdf_init(xform_ctx_t *ctx);
+static void    pdf_start_job(xform_ctx_t *ctx);
+static void    pdf_start_page(xform_ctx_t *ctx, unsigned page);
+
+static void    ps_end_job(xform_ctx_t *ctx);
+static void    ps_end_page(xform_ctx_t *ctx, unsigned page);
+static void    ps_init(xform_ctx_t *ctx);
+static void    ps_start_job(xform_ctx_t *ctx);
+static void    ps_start_page(xform_ctx_t *ctx, unsigned page);
+
+static void    raster_end_job(xform_ctx_t *ctx);
+static void    raster_end_page(xform_ctx_t *ctx, unsigned page);
+static void    raster_init(xform_ctx_t *ctx);
+static void    raster_start_job(xform_ctx_t *ctx);
+static void    raster_start_page(xform_ctx_t *ctx, unsigned page);
+static void    raster_write_line(xform_ctx_t *ctx, unsigned y, const unsigned char *line);
+
+static void    xform_log(xform_ctx_t *ctx, xform_loglevel_t level, const char *message, ...);
+
+static int     xform_document(const char *filename, const char *informat, const char *outformat, const char *resolutions, const char *sheet_back, const char *types, int num_options, cups_option_t *options, _xform_write_cb_t cb, void *ctx);
+static int     xform_setup(xform_ctx_t *ras, const char *outformat, const char *resolutions, const char *types, const char *sheet_back, int color, unsigned pages, int num_options, cups_option_t *options);
+
+
+
+
+/*
+ * 'xformDelete()' - Free memory associated with a transform context.
+ */
+
+void
+xformDelete(xform_ctx_t *ctx)          /* I - Transform context */
+{
+  cupsFreeOptions(ctx->num_options, ctx->options);
+  free(ctx);
+}
+
+
+/*
+ * 'xformNew()' - Create a new transform context.
+ */
+
+xform_ctx_t *                          /* O - New transform context */
+xformNew(
+    const char           *outformat,   /* I - Output MIME media type */
+    xform_capabilities_t *outcaps)     /* I - Output capabilities */
+{
+  xform_ctx_t  *ctx;                   /* New context */
+
+
+  if ((ctx = (xform_ctx_t *)calloc(1, sizeof(xform_ctx_t))) != NULL)
+  {
+    ctx->capabilities = *capabilities;
+    xformSetLogCallback(ctx, NULL, NULL);
+    xformSetWriteCallback(ctx, NULL, NULL);
+
+    if (!_cups_strcasecmp(outformat, XFORM_FORMAT_APPLE_RASTER) || !_cups_strcasecmp(outformat, XFORM_FORMAT_PWG_RASTER))
+      ras_init(ctx);
+    else if (!_cups_strcasecmp(outformat, XFORM_FORMAT_PCL))
+      pcl_init(ctx);
+    else if (!_cups_strcasecmp(outformat, XFORM_FORMAT_PDF))
+      pdf_init(ctx);
+    else if (!_cups_strcasecmp(outformat, XFORM_FORMAT_POSTSCRIPT))
+      ps_init(ctx);
+    else
+    {
+      free(ctx);
+      return (NULL);
+    }
+  }
+
+  return (ctx);
+}
+
+
+/*
+ * 'xformRun()' - Transform a file.
+ */
+
+int                                    /* O - 1 on success, 0 on failure */
+xformRun(xform_ctx_t *ctx,             /* I - Transform context */
+         const char  *infile,          /* I - Input filename or `NULL` for `stdin` */
+         const char  *informat)                /* I - Input MIME media type */
+{
+}
+
+
+/*
+ * 'xformSetLogCallback()' - Set the logging callback.
+ */
+
+void
+xformSetLogCallback(
+    xform_ctx_t   *ctx,                        /* I - Transform context */
+    xform_logcb_t logcb,               /* I - Logging callback */
+    void          *logdata)            /* I - User data pointer for callback */
+{
+  if (logcb)
+  {
+    ctx->logcb   = logcb;
+    ctx->logdata = logdata;
+  }
+  else
+  {
+    ctx->logcb   = default_log_cb;
+    ctx->logdata = NULL;
+  }
+}
+
+
+/*
+ * 'xformSetOptions()' - Set transform options.
+ */
+
+void
+xformSetOptions(
+    xform_ctx_t   *ctx,                        /* I - Transform context */
+    int           num_options,         /* I - Number of options */
+    cups_option_t *options)            /* I - Options */
+{
+  cupsFreeOptions(ctx->num_options, ctx->options);
+
+  ctx->num_options = 0;
+  ctx->options     = NULL;
+
+  while (num_options > 0)
+  {
+    ctx->num_options = cupsAddOption(options->name, options->value, ctx->num_options, &ctx->options);
+    options ++;
+    num_options --;
+  }
+}
+
+
+/*
+ * 'xformSetWriteCallback()' - Set the output callback.
+ */
+
+void
+xformSetWriteCallback(
+    xform_ctx_t     *ctx,              /* I - Transform context */
+    xform_writecb_t writecb,           /* I - Write callback */
+    void            *writedata)                /* I - User data pointer for callback */
+{
+  if (logcb)
+  {
+    ctx->logcb   = logcb;
+    ctx->logdata = logdata;
+  }
+  else
+  {
+    ctx->logcb   = default_log_cb;
+    ctx->logdata = NULL;
+  }
+}
+
+
+/*
+ * 'default_log_cb()' - Default logging callback (to stderr).
+ */
+
+static void
+default_log_cb(
+    void             *user_data,       /* I - User data pointer (unused) */
+    xform_loglevel_t level,            /* I - Log level */
+    const char       *message)         /* I - Message */
+{
+  static const char * const levels[] = /* Log level prefixes */
+  {
+    "DEBUG: ",
+    "INFO: ",
+    "ERROR: ",
+    "ATTR: "
+  };
+
+
+  (void)user_data;
+
+  fprintf(stderr, "%s%s\n", levels[level], message);
+}
+
+
+/*
+ * 'default_write_cb()' - Default output callback (to stdout).
+ */
+
+static ssize_t                         /* O - Number of bytes written */
+default_write_cb(
+    void                *user_data,    /* I - User data pointer (unused) */
+    const unsigned char *buffer,       /* I - Buffer to write */
+    size_t              length)                /* I - Number of bytes */
+{
+  (void)user_data;
+
+  return (write(1, buffer, length));
+}
+
+
+#ifdef HAVE_MUPDF
+/*
+ * 'pack_graya()' - Pack GRAYX scanlines into GRAY scanlines.
+ *
+ * This routine is suitable only for 8 bit GRAYX data packed into GRAY bytes.
+ */
+
+static void
+pack_graya(unsigned char *row,         /* I - Row of pixels to pack */
+          size_t        num_pixels)    /* I - Number of pixels in row */
+{
+  unsigned char *src_byte;             /* Remaining source bytes */
+  unsigned char *dest_byte;            /* Remaining destination bytes */
+
+
+  for (src_byte = row + 2, dest_byte = row + 1, num_pixels --; num_pixels > 0; num_pixels --, src_byte += 2)
+    *dest_byte++ = *src_byte;
+}
+#endif /* HAVE_MUPDF */
+
+
+/*
+ * 'pack_rgba_to_rgb()' - Pack RGBX scanlines into RGB scanlines.
+ *
+ * This routine is suitable only for 8 bit RGBX data packed into RGB bytes.
+ */
+
+static void
+pack_rgba_to_rgb(
+    unsigned char *row,                        /* I - Row of pixels to pack */
+    size_t        num_pixels)          /* I - Number of pixels in row */
+{
+  size_t       num_quads = num_pixels / 4;
+                                       /* Number of 4 byte samples to pack */
+  size_t       leftover_pixels = num_pixels & 3;
+                                       /* Number of pixels remaining */
+  unsigned     *quad_row = (unsigned *)row;
+                                       /* 32-bit pixel pointer */
+  unsigned     *dest = quad_row;       /* Destination pointer */
+  unsigned char *src_byte;             /* Remaining source bytes */
+  unsigned char *dest_byte;            /* Remaining destination bytes */
+
+
+ /*
+  * Copy all of the groups of 4 pixels we can...
+  */
+
+  while (num_quads > 0)
+  {
+    *dest++ = (quad_row[0] & XFORM_RGB_MASK) | (quad_row[1] << 24);
+    *dest++ = ((quad_row[1] & XFORM_BG_MASK) >> 8) |
+              ((quad_row[2] & XFORM_RG_MASK) << 16);
+    *dest++ = ((quad_row[2] & XFORM_BLUE_MASK) >> 16) | (quad_row[3] << 8);
+    quad_row += 4;
+    num_quads --;
+  }
+
+ /*
+  * Then handle the leftover pixels...
+  */
+
+  src_byte  = (unsigned char *)quad_row;
+  dest_byte = (unsigned char *)dest;
+
+  while (leftover_pixels > 0)
+  {
+    *dest_byte++ = *src_byte++;
+    *dest_byte++ = *src_byte++;
+    *dest_byte++ = *src_byte++;
+    src_byte ++;
+    leftover_pixels --;
+  }
+}
+
+
+/*
+ * 'pack_rgba_to_gray()' - Pack RGBX scanlines into GRAY scanlines.
+ *
+ * This routine is suitable only for 8 bit RGBX data packed into GRAY bytes.
+ */
+
+static void
+pack_rgba_to_gray(
+    unsigned char *row,                        /* I - Row of pixels to pack */
+    size_t        num_pixels)          /* I - Number of pixels in row */
+{
+  unsigned char *src_byte;             /* Remaining source bytes */
+  unsigned char *dest_byte;            /* Remaining destination bytes */
+
+
+  for (src_byte = row + 3, dest_byte = row; num_pixels > 0; num_pixels --, src_byte += 4)
+    *dest_byte++ = *src_byte;
+}
+
+
+/*
+ * 'xform_log()' - Log a message.
+ */
+
+static void
+xform_log(xform_ctx_t      *ctx,       /* I - Transform context */
+          xform_loglevel_t level,      /* I - Log level */
+          const char       *message,   /* I - Printf-style message */
+          ...)                         /* I - Additional arguments as needed */
+{
+  va_list      ap;                     /* Pointer to additional arguments */
+  char         buffer[2048];           /* Message buffer */
+
+
+  va_start(ap, message);
+  vsnprintf(buffer, sizeof(buffer), message, ap);
+  va_end(ap);
+
+  (ctx->logcb)(ctx->logdata, level, buffer);
+}
+
+
+#if 0
+/*
+ * 'main()' - Main entry for transform utility.
+ */
+
+int                                    /* O - Exit status */
+main(int  argc,                                /* I - Number of command-line args */
+     char *argv[])                     /* I - Command-line arguments */
+{
+  int          i;                      /* Looping var */
+  const char   *filename = NULL,       /* File to transform */
+               *content_type,          /* Source content type */
+               *device_uri,            /* Destination URI */
+               *output_type,           /* Destination content type */
+               *resolutions,           /* pwg-raster-document-resolution-supported */
+               *sheet_back,            /* pwg-raster-document-sheet-back */
+               *types,                 /* pwg-raster-document-type-supported */
+               *opt;                   /* Option character */
+  int          num_options;            /* Number of options */
+  cups_option_t        *options;               /* Options */
+  int          fd = 1;                 /* Output file/socket */
+  http_t       *http = NULL;           /* Output HTTP connection */
+  void         *write_ptr = &fd;       /* Pointer to file/socket/HTTP connection */
+  char         resource[1024];         /* URI resource path */
+  _xform_write_cb_t write_cb = (_xform_write_cb_t)write_fd;
+                                       /* Write callback */
+  int          status = 0;             /* Exit status */
+  _cups_thread_t monitor = 0;          /* Monitoring thread ID */
+
+
+ /*
+  * Process the command-line...
+  */
+
+  num_options  = load_env_options(&options);
+  content_type = getenv("CONTENT_TYPE");
+  device_uri   = getenv("DEVICE_URI");
+  output_type  = getenv("OUTPUT_TYPE");
+  resolutions  = getenv("PWG_RASTER_DOCUMENT_RESOLUTION_SUPPORTED");
+  sheet_back   = getenv("PWG_RASTER_DOCUMENT_SHEET_BACK");
+  types        = getenv("PWG_RASTER_DOCUMENT_TYPE_SUPPORTED");
+
+  if ((opt = getenv("SERVER_LOGLEVEL")) != NULL)
+  {
+    if (!strcmp(opt, "debug"))
+      Verbosity = 2;
+    else if (!strcmp(opt, "info"))
+      Verbosity = 1;
+  }
+
+  for (i = 1; i < argc; i ++)
+  {
+    if (!strncmp(argv[i], "--", 2))
+    {
+      if (!strcmp(argv[i], "--help"))
+      {
+        usage(0);
+      }
+      else if (!strcmp(argv[i], "--version"))
+      {
+        puts(CUPS_SVERSION);
+      }
+      else
+      {
+       fprintf(stderr, "ERROR: Unknown option '%s'.\n", argv[i]);
+       usage(1);
+      }
+    }
+    else if (argv[i][0] == '-')
+    {
+      for (opt = argv[i] + 1; *opt; opt ++)
+      {
+        switch (*opt)
+       {
+         case 'd' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             device_uri = argv[i];
+             break;
+
+         case 'i' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             content_type = argv[i];
+             break;
+
+         case 'm' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             output_type = argv[i];
+             break;
+
+         case 'o' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             num_options = cupsParseOptions(argv[i], num_options, &options);
+             break;
+
+         case 'r' : /* pwg-raster-document-resolution-supported values */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             resolutions = argv[i];
+             break;
+
+         case 's' : /* pwg-raster-document-sheet-back value */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             sheet_back = argv[i];
+             break;
+
+         case 't' : /* pwg-raster-document-type-supported values */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             types = argv[i];
+             break;
+
+         case 'v' : /* Be verbose... */
+             Verbosity ++;
+             break;
+
+         default :
+             fprintf(stderr, "ERROR: Unknown option '-%c'.\n", *opt);
+             usage(1);
+             break;
+       }
+      }
+    }
+    else if (!filename)
+      filename = argv[i];
+    else
+      usage(1);
+  }
+
+ /*
+  * Check that we have everything we need...
+  */
+
+  if (!filename)
+    usage(1);
+
+  if (!content_type)
+  {
+    if ((opt = strrchr(filename, '.')) != NULL)
+    {
+      if (!strcmp(opt, ".pdf"))
+        content_type = "application/pdf";
+      else if (!strcmp(opt, ".jpg") || !strcmp(opt, ".jpeg"))
+        content_type = "image/jpeg";
+    }
+  }
+
+  if (!content_type)
+  {
+    fprintf(stderr, "ERROR: Unknown format for \"%s\", please specify with '-i' option.\n", filename);
+    usage(1);
+  }
+  else if (strcmp(content_type, "application/pdf") && strcmp(content_type, "image/jpeg"))
+  {
+    fprintf(stderr, "ERROR: Unsupported format \"%s\" for \"%s\".\n", content_type, filename);
+    usage(1);
+  }
+
+  if (!output_type)
+  {
+    fputs("ERROR: Unknown output format, please specify with '-m' option.\n", stderr);
+    usage(1);
+  }
+  else if (strcmp(output_type, "application/vnd.hp-pcl") && strcmp(output_type, "image/pwg-raster") && strcmp(output_type, "image/urf"))
+  {
+    fprintf(stderr, "ERROR: Unsupported output format \"%s\".\n", output_type);
+    usage(1);
+  }
+
+  if (!resolutions)
+    resolutions = "300dpi";
+  if (!sheet_back)
+    sheet_back = "normal";
+  if (!types)
+    types = "sgray_8";
+
+ /*
+  * If the device URI is specified, open the connection...
+  */
+
+  if (device_uri)
+  {
+    char       scheme[32],             /* URI scheme */
+               userpass[256],          /* URI user:pass */
+               host[256],              /* URI host */
+               service[32];            /* Service port */
+    int                port;                   /* URI port number */
+    http_addrlist_t *list;             /* Address list for socket */
+
+    if (httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+    {
+      fprintf(stderr, "ERROR: Invalid device URI \"%s\".\n", device_uri);
+      usage(1);
+    }
+
+    if (strcmp(scheme, "socket") && strcmp(scheme, "ipp") && strcmp(scheme, "ipps"))
+    {
+      fprintf(stderr, "ERROR: Unsupported device URI scheme \"%s\".\n", scheme);
+      usage(1);
+    }
+
+    snprintf(service, sizeof(service), "%d", port);
+    if ((list = httpAddrGetList(host, AF_UNSPEC, service)) == NULL)
+    {
+      fprintf(stderr, "ERROR: Unable to lookup device URI host \"%s\": %s\n", host, cupsLastErrorString());
+      return (1);
+    }
+
+    if (!strcmp(scheme, "socket"))
+    {
+     /*
+      * AppSocket connection...
+      */
+
+      if (!httpAddrConnect2(list, &fd, 30000, NULL))
+      {
+       fprintf(stderr, "ERROR: Unable to connect to \"%s\" on port %d: %s\n", host, port, cupsLastErrorString());
+       return (1);
+      }
+    }
+    else
+    {
+      http_encryption_t encryption;    /* Encryption mode */
+      ipp_t            *request,       /* IPP request */
+                       *response;      /* IPP response */
+      ipp_attribute_t  *attr;          /* operations-supported */
+      int              create_job = 0; /* Support for Create-Job/Send-Document? */
+      const char       *job_name;      /* Title of job */
+      const char       *media;         /* Value of "media" option */
+      const char       *sides;         /* Value of "sides" option */
+
+     /*
+      * Connect to the IPP/IPPS printer...
+      */
+
+      if (port == 443 || !strcmp(scheme, "ipps"))
+        encryption = HTTP_ENCRYPTION_ALWAYS;
+      else
+        encryption = HTTP_ENCRYPTION_IF_REQUESTED;
+
+      if ((http = httpConnect2(host, port, list, AF_UNSPEC, encryption, 1, 30000, NULL)) == NULL)
+      {
+       fprintf(stderr, "ERROR: Unable to connect to \"%s\" on port %d: %s\n", host, port, cupsLastErrorString());
+       return (1);
+      }
+
+     /*
+      * See if it supports Create-Job + Send-Document...
+      */
+
+      request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
+      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", NULL, "operations-supported");
+
+      response = cupsDoRequest(http, request, resource);
+      if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
+      {
+        fprintf(stderr, "ERROR: Unable to get printer capabilities: %s\n", cupsLastErrorString());
+       return (1);
+      }
+
+      if ((attr = ippFindAttribute(response, "operations-supported", IPP_TAG_ENUM)) == NULL)
+      {
+        fputs("ERROR: Unable to get list of supported operations from printer.\n", stderr);
+       return (1);
+      }
+
+      create_job = ippContainsInteger(attr, IPP_OP_CREATE_JOB) && ippContainsInteger(attr, IPP_OP_SEND_DOCUMENT);
+
+      ippDelete(response);
+
+     /*
+      * Create the job and start printing...
+      */
+
+      if ((job_name = getenv("IPP_JOB_NAME")) == NULL)
+      {
+       if ((job_name = strrchr(filename, '/')) != NULL)
+         job_name ++;
+       else
+         job_name = filename;
+      }
+
+      if (create_job)
+      {
+        int            job_id = 0;     /* Job ID */
+
+        request = ippNewRequest(IPP_OP_CREATE_JOB);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, job_name);
+
+        response = cupsDoRequest(http, request, resource);
+        if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
+         job_id = ippGetInteger(attr, 0);
+        ippDelete(response);
+
+       if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
+       {
+         fprintf(stderr, "ERROR: Unable to create print job: %s\n", cupsLastErrorString());
+         return (1);
+       }
+       else if (job_id <= 0)
+       {
+          fputs("ERROR: No job-id for created print job.\n", stderr);
+         return (1);
+       }
+
+        request = ippNewRequest(IPP_OP_SEND_DOCUMENT);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+       ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, output_type);
+        ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);
+      }
+      else
+      {
+        request = ippNewRequest(IPP_OP_PRINT_JOB);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, output_type);
+      }
+
+      if ((media = cupsGetOption("media", num_options, options)) != NULL)
+        ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "media", NULL, media);
+
+      if ((sides = cupsGetOption("sides", num_options, options)) != NULL)
+        ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", NULL, sides);
+
+      if (cupsSendRequest(http, request, resource, 0) != HTTP_STATUS_CONTINUE)
+      {
+        fprintf(stderr, "ERROR: Unable to send print data: %s\n", cupsLastErrorString());
+       return (1);
+      }
+
+      ippDelete(request);
+
+      write_cb  = (_xform_write_cb_t)httpWrite2;
+      write_ptr = http;
+
+      monitor = _cupsThreadCreate((_cups_thread_func_t)monitor_ipp, (void *)device_uri);
+    }
+
+    httpAddrFreeList(list);
+  }
+
+ /*
+  * Do transform...
+  */
+
+  status = xform_document(filename, content_type, output_type, resolutions, sheet_back, types, num_options, options, write_cb, write_ptr);
+
+  if (http)
+  {
+    ippDelete(cupsGetResponse(http, resource));
+
+    if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
+    {
+      fprintf(stderr, "ERROR: Unable to send print data: %s\n", cupsLastErrorString());
+      status = 1;
+    }
+
+    httpClose(http);
+  }
+  else if (fd != 1)
+    close(fd);
+
+  if (monitor)
+    _cupsThreadCancel(monitor);
+
+  return (status);
+}
+
+
+/*
+ * 'load_env_options()' - Load options from the environment.
+ */
+
+extern char **environ;
+
+static int                             /* O - Number of options */
+load_env_options(
+    cups_option_t **options)           /* I - Options */
+{
+  int  i;                              /* Looping var */
+  char name[256],                      /* Option name */
+       *nameptr,                       /* Pointer into name */
+       *envptr;                        /* Pointer into environment variable */
+  int  num_options = 0;                /* Number of options */
+
+
+  *options = NULL;
+
+ /*
+  * Load all of the IPP_xxx environment variables as options...
+  */
+
+  for (i = 0; environ[i]; i ++)
+  {
+    envptr = environ[i];
+
+    if (strncmp(envptr, "IPP_", 4))
+      continue;
+
+    for (nameptr = name, envptr += 4; *envptr && *envptr != '='; envptr ++)
+    {
+      if (nameptr > (name + sizeof(name) - 1))
+        continue;
+
+      if (*envptr == '_')
+        *nameptr++ = '-';
+      else
+        *nameptr++ = (char)_cups_tolower(*envptr);
+    }
+
+    *nameptr = '\0';
+    if (*envptr == '=')
+      envptr ++;
+
+    num_options = cupsAddOption(name, envptr, num_options, options);
+  }
+
+  return (num_options);
+}
+
+
+/*
+ * 'monitor_ipp()' - Monitor IPP printer status.
+ */
+
+static void *                          /* O - Thread exit status */
+monitor_ipp(const char *device_uri)    /* I - Device URI */
+{
+  int          i;                      /* Looping var */
+  http_t       *http;                  /* HTTP connection */
+  ipp_t                *request,               /* IPP request */
+               *response;              /* IPP response */
+  ipp_attribute_t *attr;               /* IPP response attribute */
+  char         scheme[32],             /* URI scheme */
+               userpass[256],          /* URI user:pass */
+               host[256],              /* URI host */
+               resource[1024];         /* URI resource */
+  int          port;                   /* URI port number */
+  http_encryption_t encryption;                /* Encryption to use */
+  int          delay = 1,              /* Current delay */
+               next_delay,             /* Next delay */
+               prev_delay = 0;         /* Previous delay */
+  char         pvalues[10][1024];      /* Current printer attribute values */
+  static const char * const pattrs[10] =/* Printer attributes we need */
+  {
+    "marker-colors",
+    "marker-levels",
+    "marker-low-levels",
+    "marker-high-levels",
+    "marker-names",
+    "marker-types",
+    "printer-alert",
+    "printer-state-reasons",
+    "printer-supply",
+    "printer-supply-description"
+  };
+
+
+  httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource));
+
+  if (port == 443 || !strcmp(scheme, "ipps"))
+    encryption = HTTP_ENCRYPTION_ALWAYS;
+  else
+    encryption = HTTP_ENCRYPTION_IF_REQUESTED;
+
+  while ((http = httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 30000, NULL)) == NULL)
+  {
+    fprintf(stderr, "ERROR: Unable to connect to \"%s\" on port %d: %s\n", host, port, cupsLastErrorString());
+    sleep(30);
+  }
+
+ /*
+  * Report printer state changes until we are canceled...
+  */
+
+  for (;;)
+  {
+   /*
+    * Poll for the current state...
+    */
+
+    request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+    ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
+
+    response = cupsDoRequest(http, request, resource);
+
+   /*
+    * Report any differences...
+    */
+
+    for (attr = ippFirstAttribute(response); attr; attr = ippNextAttribute(response))
+    {
+      const char *name = ippGetName(attr);
+      char     value[1024];            /* Name and value */
+
+
+      if (!name)
+        continue;
+
+      for (i = 0; i < (int)(sizeof(pattrs) / sizeof(pattrs[0])); i ++)
+        if (!strcmp(name, pattrs[i]))
+         break;
+
+      if (i >= (int)(sizeof(pattrs) / sizeof(pattrs[0])))
+        continue;
+
+      ippAttributeString(attr, value, sizeof(value));
+
+      if (strcmp(value, pvalues[i]))
+      {
+        if (!strcmp(name, "printer-state-reasons"))
+         fprintf(stderr, "STATE: %s\n", value);
+       else
+         fprintf(stderr, "ATTR: %s='%s'\n", name, value);
+
+        strlcpy(pvalues[i], value, sizeof(pvalues[i]));
+      }
+    }
+
+    ippDelete(response);
+
+   /*
+    * Sleep until the next update...
+    */
+
+    sleep((unsigned)delay);
+
+    next_delay = (delay + prev_delay) % 12;
+    prev_delay = next_delay < delay ? 0 : delay;
+    delay      = next_delay;
+  }
+
+  return (NULL);
+}
+
+
+
+
+/*
+ * 'pcl_end_job()' - End a PCL "job".
+ */
+
+static void
+pcl_end_job(xform_ctx_t   *ras,        /* I - Raster information */
+            _xform_write_cb_t cb,      /* I - Write callback */
+            void             *ctx)     /* I - Write context */
+{
+  (void)ras;
+
+ /*
+  * Send a PCL reset sequence.
+  */
+
+  (*cb)(ctx, (const unsigned char *)"\033E", 2);
+}
+
+
+/*
+ * 'pcl_end_page()' - End of PCL page.
+ */
+
+static void
+pcl_end_page(xform_ctx_t   *ras,       /* I - Raster information */
+            unsigned         page,     /* I - Current page */
+             _xform_write_cb_t cb,     /* I - Write callback */
+             void             *ctx)    /* I - Write context */
+{
+ /*
+  * End graphics...
+  */
+
+  (*cb)(ctx, (const unsigned char *)"\033*r0B", 5);
+
+ /*
+  * Formfeed as needed...
+  */
+
+  if (!(ras->header.Duplex && (page & 1)))
+    (*cb)(ctx, (const unsigned char *)"\014", 1);
+
+ /*
+  * Free the output buffer...
+  */
+
+  free(ras->out_buffer);
+  ras->out_buffer = NULL;
+}
+
+
+/*
+ * 'pcl_init()' - Initialize callbacks for PCL output.
+ */
+
+static void
+pcl_init(xform_ctx_t *ras)             /* I - Raster information */
+{
+  ras->end_job    = pcl_end_job;
+  ras->end_page   = pcl_end_page;
+  ras->start_job  = pcl_start_job;
+  ras->start_page = pcl_start_page;
+  ras->write_line = pcl_write_line;
+}
+
+
+/*
+ * 'pcl_printf()' - Write a formatted string.
+ */
+
+static void
+pcl_printf(_xform_write_cb_t cb,               /* I - Write callback */
+           void             *ctx,      /* I - Write context */
+          const char       *format,    /* I - Printf-style format string */
+          ...)                         /* I - Additional arguments as needed */
+{
+  va_list      ap;                     /* Argument pointer */
+  char         buffer[8192];           /* Buffer */
+
+
+  va_start(ap, format);
+  vsnprintf(buffer, sizeof(buffer), format, ap);
+  va_end(ap);
+
+  (*cb)(ctx, (const unsigned char *)buffer, strlen(buffer));
+}
+
+
+/*
+ * 'pcl_start_job()' - Start a PCL "job".
+ */
+
+static void
+pcl_start_job(xform_ctx_t   *ras,      /* I - Raster information */
+              _xform_write_cb_t cb,    /* I - Write callback */
+              void             *ctx)   /* I - Write context */
+{
+  (void)ras;
+
+ /*
+  * Send a PCL reset sequence.
+  */
+
+  (*cb)(ctx, (const unsigned char *)"\033E", 2);
+}
+
+
+/*
+ * 'pcl_start_page()' - Start a PCL page.
+ */
+
+static void
+pcl_start_page(xform_ctx_t   *ras,     /* I - Raster information */
+               unsigned         page,  /* I - Current page */
+               _xform_write_cb_t cb,   /* I - Write callback */
+               void             *ctx)  /* I - Write context */
+{
+ /*
+  * Setup margins to be 1/6" top and bottom and 1/4" or .135" on the
+  * left and right.
+  */
+
+  ras->top    = ras->header.HWResolution[1] / 6;
+  ras->bottom = ras->header.cupsHeight - ras->header.HWResolution[1] / 6 - 1;
+
+  if (ras->header.PageSize[1] == 842)
+  {
+   /* A4 gets special side margins to expose an 8" print area */
+    ras->left  = (ras->header.cupsWidth - 8 * ras->header.HWResolution[0]) / 2;
+    ras->right = ras->left + 8 * ras->header.HWResolution[0] - 1;
+  }
+  else
+  {
+   /* All other sizes get 1/4" margins */
+    ras->left  = ras->header.HWResolution[0] / 4;
+    ras->right = ras->header.cupsWidth - ras->header.HWResolution[0] / 4 - 1;
+  }
+
+  if (!ras->header.Duplex || (page & 1))
+  {
+   /*
+    * Set the media size...
+    */
+
+    pcl_printf(cb, ctx, "\033&l12D\033&k12H");
+                                       /* Set 12 LPI, 10 CPI */
+    pcl_printf(cb, ctx, "\033&l0O");   /* Set portrait orientation */
+
+    switch (ras->header.PageSize[1])
+    {
+      case 540 : /* Monarch Envelope */
+          pcl_printf(cb, ctx, "\033&l80A");
+         break;
+
+      case 595 : /* A5 */
+          pcl_printf(cb, ctx, "\033&l25A");
+         break;
+
+      case 624 : /* DL Envelope */
+          pcl_printf(cb, ctx, "\033&l90A");
+         break;
+
+      case 649 : /* C5 Envelope */
+          pcl_printf(cb, ctx, "\033&l91A");
+         break;
+
+      case 684 : /* COM-10 Envelope */
+          pcl_printf(cb, ctx, "\033&l81A");
+         break;
+
+      case 709 : /* B5 Envelope */
+          pcl_printf(cb, ctx, "\033&l100A");
+         break;
+
+      case 756 : /* Executive */
+          pcl_printf(cb, ctx, "\033&l1A");
+         break;
+
+      case 792 : /* Letter */
+          pcl_printf(cb, ctx, "\033&l2A");
+         break;
+
+      case 842 : /* A4 */
+          pcl_printf(cb, ctx, "\033&l26A");
+         break;
+
+      case 1008 : /* Legal */
+          pcl_printf(cb, ctx, "\033&l3A");
+         break;
+
+      case 1191 : /* A3 */
+          pcl_printf(cb, ctx, "\033&l27A");
+         break;
+
+      case 1224 : /* Tabloid */
+          pcl_printf(cb, ctx, "\033&l6A");
+         break;
+    }
+
+   /*
+    * Set top margin and turn off perforation skip...
+    */
+
+    pcl_printf(cb, ctx, "\033&l%uE\033&l0L", 12 * ras->top / ras->header.HWResolution[1]);
+
+    if (ras->header.Duplex)
+    {
+      int mode = ras->header.Duplex ? 1 + ras->header.Tumble != 0 : 0;
+
+      pcl_printf(cb, ctx, "\033&l%dS", mode);
+                                       /* Set duplex mode */
+    }
+  }
+  else if (ras->header.Duplex)
+    pcl_printf(cb, ctx, "\033&a2G");   /* Print on back side */
+
+ /*
+  * Set graphics mode...
+  */
+
+  pcl_printf(cb, ctx, "\033*t%uR", ras->header.HWResolution[0]);
+                                       /* Set resolution */
+  pcl_printf(cb, ctx, "\033*r%uS", ras->right - ras->left + 1);
+                                       /* Set width */
+  pcl_printf(cb, ctx, "\033*r%uT", ras->bottom - ras->top + 1);
+                                       /* Set height */
+  pcl_printf(cb, ctx, "\033&a0H\033&a%uV", 720 * ras->top / ras->header.HWResolution[1]);
+                                       /* Set position */
+
+  pcl_printf(cb, ctx, "\033*b2M");     /* Use PackBits compression */
+  pcl_printf(cb, ctx, "\033*r1A");     /* Start graphics */
+
+ /*
+  * Allocate the output buffer...
+  */
+
+  ras->out_blanks  = 0;
+  ras->out_length  = (ras->right - ras->left + 8) / 8;
+  ras->out_buffer  = malloc(ras->out_length);
+  ras->comp_buffer = malloc(2 * ras->out_length + 2);
+}
+
+
+/*
+ * 'pcl_write_line()' - Write a line of raster data.
+ */
+
+static void
+pcl_write_line(
+    xform_ctx_t      *ras,             /* I - Raster information */
+    unsigned            y,             /* I - Line number */
+    const unsigned char *line,         /* I - Pixels on line */
+    _xform_write_cb_t    cb,           /* I - Write callback */
+    void                *ctx)          /* I - Write context */
+{
+  unsigned     x;                      /* Column number */
+  unsigned char        bit,                    /* Current bit */
+               byte,                   /* Current byte */
+               *outptr,                /* Pointer into output buffer */
+               *outend,                /* End of output buffer */
+               *start,                 /* Start of sequence */
+               *compptr;               /* Pointer into compression buffer */
+  unsigned     count;                  /* Count of bytes for output */
+
+
+  if (line[0] == 255 && !memcmp(line, line + 1, ras->right - ras->left))
+  {
+   /*
+    * Skip blank line...
+    */
+
+    ras->out_blanks ++;
+    return;
+  }
+
+ /*
+  * Dither the line into the output buffer...
+  */
+
+  y &= 63;
+
+  for (x = ras->left, bit = 128, byte = 0, outptr = ras->out_buffer; x <= ras->right; x ++, line ++)
+  {
+    if (*line <= threshold[x & 63][y])
+      byte |= bit;
+
+    if (bit == 1)
+    {
+      *outptr++ = byte;
+      byte      = 0;
+      bit       = 128;
+    }
+    else
+      bit >>= 1;
+  }
+
+  if (bit != 128)
+    *outptr++ = byte;
+
+ /*
+  * Apply compression...
+  */
+
+  compptr = ras->comp_buffer;
+  outend  = outptr;
+  outptr  = ras->out_buffer;
+
+  while (outptr < outend)
+  {
+    if ((outptr + 1) >= outend)
+    {
+     /*
+      * Single byte on the end...
+      */
+
+      *compptr++ = 0x00;
+      *compptr++ = *outptr++;
+    }
+    else if (outptr[0] == outptr[1])
+    {
+     /*
+      * Repeated sequence...
+      */
+
+      outptr ++;
+      count = 2;
+
+      while (outptr < (outend - 1) &&
+            outptr[0] == outptr[1] &&
+            count < 127)
+      {
+       outptr ++;
+       count ++;
+      }
+
+      *compptr++ = (unsigned char)(257 - count);
+      *compptr++ = *outptr++;
+    }
+    else
+    {
+     /*
+      * Non-repeated sequence...
+      */
+
+      start = outptr;
+      outptr ++;
+      count = 1;
+
+      while (outptr < (outend - 1) &&
+            outptr[0] != outptr[1] &&
+            count < 127)
+      {
+       outptr ++;
+       count ++;
+      }
+
+      *compptr++ = (unsigned char)(count - 1);
+
+      memcpy(compptr, start, count);
+      compptr += count;
+    }
+  }
+
+ /*
+  * Output the line...
+  */
+
+  if (ras->out_blanks > 0)
+  {
+   /*
+    * Skip blank lines first...
+    */
+
+    pcl_printf(cb, ctx, "\033*b%dY", ras->out_blanks);
+    ras->out_blanks = 0;
+  }
+
+  pcl_printf(cb, ctx, "\033*b%dW", (int)(compptr - ras->comp_buffer));
+  (*cb)(ctx, ras->comp_buffer, (size_t)(compptr - ras->comp_buffer));
+}
+
+
+/*
+ * 'raster_end_job()' - End a raster "job".
+ */
+
+static void
+raster_end_job(xform_ctx_t   *ras,     /* I - Raster information */
+              _xform_write_cb_t cb,    /* I - Write callback */
+              void             *ctx)   /* I - Write context */
+{
+  (void)cb;
+  (void)ctx;
+
+  cupsRasterClose(ras->ras);
+}
+
+
+/*
+ * 'raster_end_page()' - End of raster page.
+ */
+
+static void
+raster_end_page(xform_ctx_t   *ras,    /* I - Raster information */
+               unsigned         page,  /* I - Current page */
+               _xform_write_cb_t cb,   /* I - Write callback */
+               void             *ctx)  /* I - Write context */
+{
+  (void)page;
+  (void)cb;
+  (void)ctx;
+
+  if (ras->header.cupsBitsPerPixel == 1)
+  {
+    free(ras->out_buffer);
+    ras->out_buffer = NULL;
+  }
+}
+
+
+/*
+ * 'raster_init()' - Initialize callbacks for raster output.
+ */
+
+static void
+raster_init(xform_ctx_t *ras)  /* I - Raster information */
+{
+  ras->end_job    = raster_end_job;
+  ras->end_page   = raster_end_page;
+  ras->start_job  = raster_start_job;
+  ras->start_page = raster_start_page;
+  ras->write_line = raster_write_line;
+}
+
+
+/*
+ * 'raster_start_job()' - Start a raster "job".
+ */
+
+static void
+raster_start_job(xform_ctx_t   *ras,   /* I - Raster information */
+                _xform_write_cb_t cb,  /* I - Write callback */
+                void             *ctx) /* I - Write context */
+{
+  ras->ras = cupsRasterOpenIO((cups_raster_iocb_t)cb, ctx, !strcmp(ras->format, "image/pwg-raster") ? CUPS_RASTER_WRITE_PWG : CUPS_RASTER_WRITE_APPLE);
+}
+
+
+/*
+ * 'raster_start_page()' - Start a raster page.
+ */
+
+static void
+raster_start_page(xform_ctx_t   *ras,/* I - Raster information */
+                 unsigned         page,/* I - Current page */
+                 _xform_write_cb_t cb, /* I - Write callback */
+                 void             *ctx)/* I - Write context */
+{
+  (void)cb;
+  (void)ctx;
+
+  ras->left   = 0;
+  ras->top    = 0;
+  ras->right  = ras->header.cupsWidth - 1;
+  ras->bottom = ras->header.cupsHeight - 1;
+
+  if (ras->header.Duplex && !(page & 1))
+    cupsRasterWriteHeader2(ras->ras, &ras->back_header);
+  else
+    cupsRasterWriteHeader2(ras->ras, &ras->header);
+
+  if (ras->header.cupsBitsPerPixel == 1)
+  {
+    ras->out_length = ras->header.cupsBytesPerLine;
+    ras->out_buffer = malloc(ras->header.cupsBytesPerLine);
+  }
+}
+
+
+/*
+ * 'raster_write_line()' - Write a line of raster data.
+ */
+
+static void
+raster_write_line(
+    xform_ctx_t      *ras,             /* I - Raster information */
+    unsigned            y,             /* I - Line number */
+    const unsigned char *line,         /* I - Pixels on line */
+    _xform_write_cb_t    cb,           /* I - Write callback */
+    void                *ctx)          /* I - Write context */
+{
+  (void)cb;
+  (void)ctx;
+
+  if (ras->header.cupsBitsPerPixel == 1)
+  {
+   /*
+    * Dither the line into the output buffer...
+    */
+
+    unsigned           x;              /* Column number */
+    unsigned char      bit,            /* Current bit */
+                       byte,           /* Current byte */
+                       *outptr;        /* Pointer into output buffer */
+
+    y &= 63;
+
+    if (ras->header.cupsColorSpace == CUPS_CSPACE_SW)
+    {
+      for (x = ras->left, bit = 128, byte = 0, outptr = ras->out_buffer; x <= ras->right; x ++, line ++)
+      {
+       if (*line > threshold[x % 25][y])
+         byte |= bit;
+
+       if (bit == 1)
+       {
+         *outptr++ = byte;
+         byte      = 0;
+         bit       = 128;
+       }
+       else
+         bit >>= 1;
+      }
+    }
+    else
+    {
+      for (x = ras->left, bit = 128, byte = 0, outptr = ras->out_buffer; x <= ras->right; x ++, line ++)
+      {
+       if (*line <= threshold[x & 63][y])
+         byte |= bit;
+
+       if (bit == 1)
+       {
+         *outptr++ = byte;
+         byte      = 0;
+         bit       = 128;
+       }
+       else
+         bit >>= 1;
+      }
+    }
+
+    if (bit != 128)
+      *outptr++ = byte;
+
+    cupsRasterWritePixels(ras->ras, ras->out_buffer, ras->header.cupsBytesPerLine);
+  }
+  else
+    cupsRasterWritePixels(ras->ras, (unsigned char *)line, ras->header.cupsBytesPerLine);
+}
+
+
+/*
+ * 'usage()' - Show program usage.
+ */
+
+static void
+usage(int status)                      /* I - Exit status */
+{
+  puts("Usage: ipptransform [options] filename\n");
+  puts("Options:");
+  puts("  --help");
+  puts("  -d device-uri");
+  puts("  -i input/format");
+  puts("  -m output/format");
+  puts("  -o \"name=value [... name=value]\"");
+  puts("  -r resolution[,...,resolution]");
+  puts("  -s {flipped|manual-tumble|normal|rotated}");
+  puts("  -t type[,...,type]");
+  puts("  -v\n");
+  puts("Device URIs: socket://address[:port], ipp://address[:port]/resource, ipps://address[:port]/resource");
+  puts("Input Formats: application/pdf, image/jpeg");
+  puts("Output Formats: application/vnd.hp-pcl, image/pwg-raster, image/urf");
+  puts("Options: copies, media, media-col, page-ranges, print-color-mode, print-quality, print-scaling, printer-resolution, sides");
+  puts("Resolutions: NNNdpi or NNNxNNNdpi");
+  puts("Types: black_1, sgray_1, sgray_8, srgb_8");
+
+  exit(status);
+}
+
+
+/*
+ * 'write_fd()' - Write to a file/socket.
+ */
+
+static ssize_t                         /* O - Number of bytes written or -1 on error */
+write_fd(int                 *fd,      /* I - File descriptor */
+         const unsigned char *buffer,  /* I - Buffer */
+         size_t              bytes)    /* I - Number of bytes to write */
+{
+  ssize_t      temp,                   /* Temporary byte count */
+               total = 0;              /* Total bytes written */
+
+
+  while (bytes > 0)
+  {
+    if ((temp = write(*fd, buffer, bytes)) < 0)
+    {
+      if (errno == EINTR || errno == EAGAIN)
+        continue;
+      else
+        return (-1);
+    }
+
+    total  += temp;
+    bytes  -= (size_t)temp;
+    buffer += temp;
+  }
+
+  return (total);
+}
+
+
+
+#ifdef __APPLE__
+/*
+ * 'xform_document()' - Transform a file for printing.
+ */
+
+static int                             /* O - 0 on success, 1 on error */
+xform_document(
+    const char       *filename,                /* I - File to transform */
+    const char       *informat,                /* I - Input document (MIME media type */
+    const char       *outformat,       /* I - Output format (MIME media type) */
+    const char       *resolutions,     /* I - Supported resolutions */
+    const char       *sheet_back,      /* I - Back side transform */
+    const char       *types,           /* I - Supported types */
+    int              num_options,      /* I - Number of options */
+    cups_option_t    *options,         /* I - Options */
+    _xform_write_cb_t cb,              /* I - Write callback */
+    void             *ctx)             /* I - Write context */
+{
+  CFURLRef             url;            /* CFURL object for PDF filename */
+  CGPDFDocumentRef     document= NULL; /* Input document */
+  CGPDFPageRef         pdf_page;       /* Page in PDF file */
+  CGImageSourceRef     src;            /* Image reader */
+  CGImageRef           image = NULL;   /* Image */
+  xform_ctx_t  ras;            /* Raster info */
+  size_t               max_raster;     /* Maximum raster memory to use */
+  const char           *max_raster_env;/* IPPTRANSFORM_MAX_RASTER env var */
+  CGColorSpaceRef      cs;             /* Quartz color space */
+  CGContextRef         context;        /* Quartz bitmap context */
+  CGBitmapInfo         info;           /* Bitmap flags */
+  size_t               band_size;      /* Size of band line */
+  double               xscale, yscale; /* Scaling factor */
+  CGAffineTransform    transform,      /* Transform for page */
+                       back_transform; /* Transform for back side */
+  CGRect               dest;           /* Destination rectangle */
+  unsigned             pages = 1;      /* Number of pages */
+  int                  color = 1;      /* Does the PDF have color? */
+  const char           *page_ranges;   /* "page-ranges" option */
+  unsigned             first = 1,      /* First page of range */
+                       last = 1;       /* Last page of range */
+  const char           *print_scaling; /* print-scaling option */
+  size_t               image_width,    /* Image width */
+                       image_height;   /* Image height */
+  int                  image_rotation; /* Image rotation */
+  double               image_xscale,   /* Image scaling */
+                       image_yscale;
+  unsigned             copy;           /* Current copy */
+  unsigned             page;           /* Current page */
+  unsigned             media_sheets = 0,
+                       impressions = 0;/* Page/sheet counters */
+
+
+ /*
+  * Open the file...
+  */
+
+  if ((url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)filename, (CFIndex)strlen(filename), false)) == NULL)
+  {
+    fputs("ERROR: Unable to create CFURL for file.\n", stderr);
+    return (1);
+  }
+
+  if (!strcmp(informat, "application/pdf"))
+  {
+   /*
+    * Open the PDF...
+    */
+
+    document = CGPDFDocumentCreateWithURL(url);
+    CFRelease(url);
+
+    if (!document)
+    {
+      fputs("ERROR: Unable to create CFPDFDocument for file.\n", stderr);
+      return (1);
+    }
+
+    if (CGPDFDocumentIsEncrypted(document))
+    {
+     /*
+      * Only support encrypted PDFs with a blank password...
+      */
+
+      if (!CGPDFDocumentUnlockWithPassword(document, ""))
+      {
+       fputs("ERROR: Document is encrypted and cannot be unlocked.\n", stderr);
+       CGPDFDocumentRelease(document);
+       return (1);
+      }
+    }
+
+    if (!CGPDFDocumentAllowsPrinting(document))
+    {
+      fputs("ERROR: Document does not allow printing.\n", stderr);
+      CGPDFDocumentRelease(document);
+      return (1);
+    }
+
+   /*
+    * Check page ranges...
+    */
+
+    if ((page_ranges = cupsGetOption("page-ranges", num_options, options)) != NULL)
+    {
+      if (sscanf(page_ranges, "%u-%u", &first, &last) != 2 || first > last)
+      {
+       fprintf(stderr, "ERROR: Bad \"page-ranges\" value '%s'.\n", page_ranges);
+       CGPDFDocumentRelease(document);
+       return (1);
+      }
+
+      pages = (unsigned)CGPDFDocumentGetNumberOfPages(document);
+      if (first > pages)
+      {
+       fputs("ERROR: \"page-ranges\" value does not include any pages to print in the document.\n", stderr);
+       CGPDFDocumentRelease(document);
+       return (1);
+      }
+
+      if (last > pages)
+       last = pages;
+    }
+    else
+    {
+      first = 1;
+      last  = (unsigned)CGPDFDocumentGetNumberOfPages(document);
+    }
+
+    pages = last - first + 1;
+  }
+  else
+  {
+   /*
+    * Open the image...
+    */
+
+    if ((src = CGImageSourceCreateWithURL(url, NULL)) == NULL)
+    {
+      CFRelease(url);
+      fputs("ERROR: Unable to create CFImageSourceRef for file.\n", stderr);
+      return (1);
+    }
+
+    if ((image = CGImageSourceCreateImageAtIndex(src, 0, NULL)) == NULL)
+    {
+      CFRelease(src);
+      CFRelease(url);
+
+      fputs("ERROR: Unable to create CFImageRef for file.\n", stderr);
+      return (1);
+    }
+
+    CFRelease(src);
+    CFRelease(url);
+
+    pages = 1;
+  }
+
+ /*
+  * Setup the raster context...
+  */
+
+  if (xform_setup(&ras, outformat, resolutions, sheet_back, types, color, pages, num_options, options))
+  {
+    CGPDFDocumentRelease(document);
+    return (1);
+  }
+
+  if (ras.header.cupsBitsPerPixel != 24)
+  {
+   /*
+    * Grayscale output...
+    */
+
+    ras.band_bpp = 1;
+    info         = kCGImageAlphaNone;
+    cs           = CGColorSpaceCreateWithName(kCGColorSpaceGenericGrayGamma2_2);
+  }
+  else
+  {
+   /*
+    * Color (sRGB) output...
+    */
+
+    ras.band_bpp = 4;
+    info         = kCGImageAlphaNoneSkipLast;
+    cs           = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
+  }
+
+  max_raster     = XFORM_MAX_RASTER;
+  max_raster_env = getenv("IPPTRANSFORM_MAX_RASTER");
+  if (max_raster_env && strtol(max_raster_env, NULL, 10) > 0)
+    max_raster = (size_t)strtol(max_raster_env, NULL, 10);
+
+  band_size = ras.header.cupsWidth * ras.band_bpp;
+  if ((ras.band_height = (unsigned)(max_raster / band_size)) < 1)
+    ras.band_height = 1;
+  else if (ras.band_height > ras.header.cupsHeight)
+    ras.band_height = ras.header.cupsHeight;
+
+  ras.band_buffer = malloc(ras.band_height * band_size);
+  context         = CGBitmapContextCreate(ras.band_buffer, ras.header.cupsWidth, ras.band_height, 8, band_size, cs, info);
+
+  CGColorSpaceRelease(cs);
+
+  /* Don't anti-alias or interpolate when creating raster data */
+  CGContextSetAllowsAntialiasing(context, 0);
+  CGContextSetInterpolationQuality(context, kCGInterpolationNone);
+
+  xscale = ras.header.HWResolution[0] / 72.0;
+  yscale = ras.header.HWResolution[1] / 72.0;
+
+  if (Verbosity > 1)
+    fprintf(stderr, "DEBUG: xscale=%g, yscale=%g\n", xscale, yscale);
+  CGContextScaleCTM(context, xscale, yscale);
+
+  if (Verbosity > 1)
+    fprintf(stderr, "DEBUG: Band height=%u, page height=%u, page translate 0.0,%g\n", ras.band_height, ras.header.cupsHeight, -1.0 * (ras.header.cupsHeight - ras.band_height) / yscale);
+  CGContextTranslateCTM(context, 0.0, -1.0 * (ras.header.cupsHeight - ras.band_height) / yscale);
+
+  dest.origin.x    = dest.origin.y = 0.0;
+  dest.size.width  = ras.header.cupsWidth * 72.0 / ras.header.HWResolution[0];
+  dest.size.height = ras.header.cupsHeight * 72.0 / ras.header.HWResolution[1];
+
+ /*
+  * Get print-scaling value...
+  */
+
+  if ((print_scaling = cupsGetOption("print-scaling", num_options, options)) == NULL)
+    if ((print_scaling = getenv("PRINTER_PRINT_SCALING_DEFAULT")) == NULL)
+      print_scaling = "auto";
+
+ /*
+  * Start the conversion...
+  */
+
+  if (Verbosity > 1)
+    fprintf(stderr, "DEBUG: cupsPageSize=[%g %g]\n", ras.header.cupsPageSize[0], ras.header.cupsPageSize[1]);
+
+  (*(ras.start_job))(&ras, cb, ctx);
+
+  if (document)
+  {
+   /*
+    * Render pages in the PDF...
+    */
+
+    if (pages > 1 && sheet_back && ras.header.Duplex)
+    {
+     /*
+      * Setup the back page transform...
+      */
+
+      if (!strcmp(sheet_back, "flipped"))
+      {
+       if (ras.header.Tumble)
+         back_transform = CGAffineTransformMake(-1, 0, 0, 1, ras.header.cupsPageSize[0], 0);
+       else
+         back_transform = CGAffineTransformMake(1, 0, 0, -1, 0, ras.header.cupsPageSize[1]);
+      }
+      else if (!strcmp(sheet_back, "manual-tumble") && ras.header.Tumble)
+       back_transform = CGAffineTransformMake(-1, 0, 0, -1, ras.header.cupsPageSize[0], ras.header.cupsPageSize[1]);
+      else if (!strcmp(sheet_back, "rotated") && !ras.header.Tumble)
+       back_transform = CGAffineTransformMake(-1, 0, 0, -1, ras.header.cupsPageSize[0], ras.header.cupsPageSize[1]);
+      else
+       back_transform = CGAffineTransformMake(1, 0, 0, 1, 0, 0);
+    }
+    else
+      back_transform = CGAffineTransformMake(1, 0, 0, 1, 0, 0);
+
+    if (Verbosity > 1)
+      fprintf(stderr, "DEBUG: back_transform=[%g %g %g %g %g %g]\n", back_transform.a, back_transform.b, back_transform.c, back_transform.d, back_transform.tx, back_transform.ty);
+
+   /*
+    * Draw all of the pages...
+    */
+
+    for (copy = 0; copy < ras.copies; copy ++)
+    {
+      for (page = 1; page <= pages; page ++)
+      {
+       unsigned        y,              /* Current line */
+                       band_starty = 0,/* Start line of band */
+                       band_endy = 0;  /* End line of band */
+       unsigned char   *lineptr;       /* Pointer to line */
+
+       pdf_page  = CGPDFDocumentGetPage(document, page + first - 1);
+       transform = CGPDFPageGetDrawingTransform(pdf_page, kCGPDFCropBox,dest, 0, true);
+
+       if (Verbosity > 1)
+         fprintf(stderr, "DEBUG: Printing copy %d/%d, page %d/%d, transform=[%g %g %g %g %g %g]\n", copy + 1, ras.copies, page, pages, transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty);
+
+       (*(ras.start_page))(&ras, page, cb, ctx);
+
+       for (y = ras.top; y <= ras.bottom; y ++)
+       {
+         if (y > band_endy)
+         {
+          /*
+           * Draw the next band of raster data...
+           */
+
+           band_starty = y;
+           band_endy   = y + ras.band_height - 1;
+           if (band_endy > ras.bottom)
+             band_endy = ras.bottom;
+
+           if (Verbosity > 1)
+             fprintf(stderr, "DEBUG: Drawing band from %u to %u.\n", band_starty, band_endy);
+
+           CGContextSaveGState(context);
+             if (ras.header.cupsNumColors == 1)
+               CGContextSetGrayFillColor(context, 1., 1.);
+             else
+               CGContextSetRGBFillColor(context, 1., 1., 1., 1.);
+
+             CGContextSetCTM(context, CGAffineTransformIdentity);
+             CGContextFillRect(context, CGRectMake(0., 0., ras.header.cupsWidth, ras.band_height));
+           CGContextRestoreGState(context);
+
+           CGContextSaveGState(context);
+             if (Verbosity > 1)
+               fprintf(stderr, "DEBUG: Band translate 0.0,%g\n", y / yscale);
+             CGContextTranslateCTM(context, 0.0, y / yscale);
+             if (!(page & 1) && ras.header.Duplex)
+               CGContextConcatCTM(context, back_transform);
+             CGContextConcatCTM(context, transform);
+
+             CGContextClipToRect(context, CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox));
+             CGContextDrawPDFPage(context, pdf_page);
+           CGContextRestoreGState(context);
+         }
+
+        /*
+         * Prepare and write a line...
+         */
+
+         lineptr = ras.band_buffer + (y - band_starty) * band_size + ras.left * ras.band_bpp;
+         if (ras.band_bpp == 4)
+           pack_rgba(lineptr, ras.right - ras.left + 1);
+
+         (*(ras.write_line))(&ras, y, lineptr, cb, ctx);
+       }
+
+       (*(ras.end_page))(&ras, page, cb, ctx);
+
+       impressions ++;
+       fprintf(stderr, "ATTR: job-impressions-completed=%u\n", impressions);
+       if (!ras.header.Duplex || !(page & 1))
+       {
+         media_sheets ++;
+         fprintf(stderr, "ATTR: job-media-sheets-completed=%u\n", media_sheets);
+       }
+      }
+
+      if (ras.copies > 1 && (pages & 1) && ras.header.Duplex)
+      {
+       /*
+       * Duplex printing, add a blank back side image...
+       */
+
+       unsigned        y;              /* Current line */
+
+       if (Verbosity > 1)
+         fprintf(stderr, "DEBUG: Printing blank page %u for duplex.\n", pages + 1);
+
+       memset(ras.band_buffer, 255, ras.header.cupsBytesPerLine);
+
+       (*(ras.start_page))(&ras, page, cb, ctx);
+
+       for (y = ras.top; y < ras.bottom; y ++)
+         (*(ras.write_line))(&ras, y, ras.band_buffer, cb, ctx);
+
+       (*(ras.end_page))(&ras, page, cb, ctx);
+
+       impressions ++;
+       fprintf(stderr, "ATTR: job-impressions-completed=%u\n", impressions);
+       if (!ras.header.Duplex || !(page & 1))
+       {
+         media_sheets ++;
+         fprintf(stderr, "ATTR: job-media-sheets-completed=%u\n", media_sheets);
+       }
+      }
+    }
+
+    CGPDFDocumentRelease(document);
+  }
+  else
+  {
+   /*
+    * Render copies of the image...
+    */
+
+    image_width  = CGImageGetWidth(image);
+    image_height = CGImageGetHeight(image);
+
+    if ((image_height < image_width && ras.header.cupsWidth < ras.header.cupsHeight) ||
+        (image_width < image_height && ras.header.cupsHeight < ras.header.cupsWidth))
+    {
+     /*
+      * Rotate image 90 degrees...
+      */
+
+      image_rotation = 90;
+    }
+    else
+    {
+     /*
+      * Leave image as-is...
+      */
+
+      image_rotation = 0;
+    }
+
+    if (Verbosity > 1)
+      fprintf(stderr, "DEBUG: image_width=%u, image_height=%u, image_rotation=%d\n", (unsigned)image_width, (unsigned)image_height, image_rotation);
+
+    if ((!strcmp(print_scaling, "auto") && ras.borderless) || !strcmp(print_scaling, "fill"))
+    {
+     /*
+      * Scale to fill...
+      */
+
+      if (image_rotation)
+      {
+       image_xscale = ras.header.cupsPageSize[0] / (double)image_height;
+       image_yscale = ras.header.cupsPageSize[1] / (double)image_width;
+      }
+      else
+      {
+       image_xscale = ras.header.cupsPageSize[0] / (double)image_width;
+       image_yscale = ras.header.cupsPageSize[1] / (double)image_height;
+      }
+
+      if (image_xscale < image_yscale)
+       image_xscale = image_yscale;
+      else
+       image_yscale = image_xscale;
+
+    }
+    else
+    {
+     /*
+      * Scale to fit with 1/4" margins...
+      */
+
+      if (image_rotation)
+      {
+       image_xscale = (ras.header.cupsPageSize[0] - 36.0) / (double)image_height;
+       image_yscale = (ras.header.cupsPageSize[1] - 36.0) / (double)image_width;
+      }
+      else
+      {
+       image_xscale = (ras.header.cupsPageSize[0] - 36.0) / (double)image_width;
+       image_yscale = (ras.header.cupsPageSize[1] - 36.0) / (double)image_height;
+      }
+
+      if (image_xscale > image_yscale)
+       image_xscale = image_yscale;
+      else
+       image_yscale = image_xscale;
+    }
+
+    if (image_rotation)
+    {
+      transform = CGAffineTransformMake(image_xscale, 0, 0, image_yscale, 0.5 * (ras.header.cupsPageSize[0] - image_xscale * image_height), 0.5 * (ras.header.cupsPageSize[1] - image_yscale * image_width));
+    }
+    else
+    {
+      transform = CGAffineTransformMake(image_xscale, 0, 0, image_yscale, 0.5 * (ras.header.cupsPageSize[0] - image_xscale * image_width), 0.5 * (ras.header.cupsPageSize[1] - image_yscale * image_height));
+    }
+
+   /*
+    * Draw all of the copies...
+    */
+
+    for (copy = 0; copy < ras.copies; copy ++)
+    {
+      unsigned         y,              /* Current line */
+                       band_starty = 0,/* Start line of band */
+                       band_endy = 0;  /* End line of band */
+      unsigned char    *lineptr;       /* Pointer to line */
+
+      if (Verbosity > 1)
+       fprintf(stderr, "DEBUG: Printing copy %d/%d, transform=[%g %g %g %g %g %g]\n", copy + 1, ras.copies, transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty);
+
+      (*(ras.start_page))(&ras, 1, cb, ctx);
+
+      for (y = ras.top; y <= ras.bottom; y ++)
+      {
+       if (y > band_endy)
+       {
+        /*
+         * Draw the next band of raster data...
+         */
+
+         band_starty = y;
+         band_endy   = y + ras.band_height - 1;
+         if (band_endy > ras.bottom)
+           band_endy = ras.bottom;
+
+         if (Verbosity > 1)
+           fprintf(stderr, "DEBUG: Drawing band from %u to %u.\n", band_starty, band_endy);
+
+         CGContextSaveGState(context);
+           if (ras.header.cupsNumColors == 1)
+             CGContextSetGrayFillColor(context, 1., 1.);
+           else
+             CGContextSetRGBFillColor(context, 1., 1., 1., 1.);
+
+           CGContextSetCTM(context, CGAffineTransformIdentity);
+           CGContextFillRect(context, CGRectMake(0., 0., ras.header.cupsWidth, ras.band_height));
+         CGContextRestoreGState(context);
+
+         CGContextSaveGState(context);
+           if (Verbosity > 1)
+             fprintf(stderr, "DEBUG: Band translate 0.0,%g\n", y / yscale);
+           CGContextTranslateCTM(context, 0.0, y / yscale);
+           CGContextConcatCTM(context, transform);
+
+           if (image_rotation)
+             CGContextConcatCTM(context, CGAffineTransformMake(0, -1, 1, 0, 0, image_width));
+
+           CGContextDrawImage(context, CGRectMake(0, 0, image_width, image_height), image);
+         CGContextRestoreGState(context);
+       }
+
+       /*
+       * Prepare and write a line...
+       */
+
+       lineptr = ras.band_buffer + (y - band_starty) * band_size + ras.left * ras.band_bpp;
+       if (ras.band_bpp == 4)
+         pack_rgba(lineptr, ras.right - ras.left + 1);
+
+       (*(ras.write_line))(&ras, y, lineptr, cb, ctx);
+      }
+
+      (*(ras.end_page))(&ras, 1, cb, ctx);
+
+      impressions ++;
+      fprintf(stderr, "ATTR: job-impressions-completed=%u\n", impressions);
+      media_sheets ++;
+      fprintf(stderr, "ATTR: job-media-sheets-completed=%u\n", media_sheets);
+    }
+
+    CFRelease(image);
+  }
+
+  (*(ras.end_job))(&ras, cb, ctx);
+
+ /*
+  * Clean up...
+  */
+
+  CGContextRelease(context);
+
+  return (0);
+}
+
+
+#else
+/*
+ * 'xform_document()' - Transform a file for printing.
+ */
+
+static int                             /* O - 0 on success, 1 on error */
+xform_document(
+    const char       *filename,                /* I - File to transform */
+    const char       *informat,                /* I - Input format (MIME media type) */
+    const char       *outformat,       /* I - Output format (MIME media type) */
+    const char       *resolutions,     /* I - Supported resolutions */
+    const char       *sheet_back,      /* I - Back side transform */
+    const char       *types,           /* I - Supported types */
+    int              num_options,      /* I - Number of options */
+    cups_option_t    *options,         /* I - Options */
+    _xform_write_cb_t cb,              /* I - Write callback */
+    void             *ctx)             /* I - Write context */
+{
+  fz_context           *context;       /* MuPDF context */
+  fz_document          *document;      /* Document to print */
+  fz_page              *pdf_page;      /* Page in PDF file */
+  fz_pixmap            *pixmap;        /* Pixmap for band */
+  fz_device            *device;        /* Device for rendering */
+  fz_colorspace                *cs;            /* Quartz color space */
+  xform_ctx_t  ras;            /* Raster info */
+  size_t               max_raster;     /* Maximum raster memory to use */
+  const char           *max_raster_env;/* IPPTRANSFORM_MAX_RASTER env var */
+  unsigned             pages = 1;      /* Number of pages */
+  int                  color = 1;      /* Color PDF? */
+  const char           *page_ranges;   /* "page-ranges" option */
+  unsigned             first, last;    /* First and last page of range */
+  const char           *print_scaling; /* print-scaling option */
+  unsigned             copy;           /* Current copy */
+  unsigned             page;           /* Current page */
+  unsigned             media_sheets = 0,
+                       impressions = 0;/* Page/sheet counters */
+  size_t               band_size;      /* Size of band line */
+  double               xscale, yscale; /* Scaling factor */
+  fz_rect              image_box;      /* Bounding box of content */
+  fz_matrix            base_transform, /* Base transform */
+                       image_transform,/* Transform for content ("page image") */
+                       transform,      /* Transform for page */
+                       back_transform; /* Transform for back side */
+
+
+ /*
+  * Open the PDF file...
+  */
+
+  if ((context = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED)) == NULL)
+  {
+    fputs("ERROR: Unable to create context.\n", stderr);
+    return (1);
+  }
+
+  fz_register_document_handlers(context);
+
+  fz_try(context) document = fz_open_document(context, filename);
+  fz_catch(context)
+  {
+    fprintf(stderr, "ERROR: Unable to open '%s': %s\n", filename, fz_caught_message(context));
+    fz_drop_context(context);
+    return (1);
+  }
+
+  if (fz_needs_password(context, document))
+  {
+    fputs("ERROR: Document is encrypted and cannot be unlocked.\n", stderr);
+    fz_drop_document(context, document);
+    fz_drop_context(context);
+    return (1);
+  }
+
+ /*
+  * Check page ranges...
+  */
+
+  if ((page_ranges = cupsGetOption("page-ranges", num_options, options)) != NULL)
+  {
+    if (sscanf(page_ranges, "%u-%u", &first, &last) != 2 || first > last)
+    {
+      fprintf(stderr, "ERROR: Bad \"page-ranges\" value '%s'.\n", page_ranges);
+
+      fz_drop_document(context, document);
+      fz_drop_context(context);
+
+      return (1);
+    }
+
+    pages = (unsigned)fz_count_pages(context, document);
+    if (first > pages)
+    {
+      fputs("ERROR: \"page-ranges\" value does not include any pages to print in the document.\n", stderr);
+
+      fz_drop_document(context, document);
+      fz_drop_context(context);
+
+      return (1);
+    }
+
+    if (last > pages)
+      last = pages;
+  }
+  else
+  {
+    first = 1;
+    last  = (unsigned)fz_count_pages(context, document);
+  }
+
+  pages = last - first + 1;
+
+ /*
+  * Setup the raster context...
+  */
+
+  if (xform_setup(&ras, outformat, resolutions, sheet_back, types, color, 1, num_options, options))
+  {
+    fz_drop_document(context, document);
+    fz_drop_context(context);
+
+    return (1);
+  }
+
+  if (ras.header.cupsBitsPerPixel != 24)
+  {
+   /*
+    * Grayscale output...
+    */
+
+    ras.band_bpp = 2; /* TODO: Update to not use alpha (Issue #93) */
+    cs           = fz_device_gray(context);
+  }
+  else
+  {
+   /*
+    * Color (sRGB) output...
+    */
+
+    ras.band_bpp = 4; /* TODO: Update to not use alpha (Issue #93) */
+    cs           = fz_device_rgb(context);
+  }
+
+  max_raster     = XFORM_MAX_RASTER;
+  max_raster_env = getenv("IPPTRANSFORM_MAX_RASTER");
+  if (max_raster_env && strtol(max_raster_env, NULL, 10) > 0)
+    max_raster = (size_t)strtol(max_raster_env, NULL, 10);
+
+  band_size = ras.header.cupsWidth * ras.band_bpp;
+  if ((ras.band_height = (unsigned)(max_raster / band_size)) < 1)
+    ras.band_height = 1;
+  else if (ras.band_height > ras.header.cupsHeight)
+    ras.band_height = ras.header.cupsHeight;
+
+  /* TODO: Update code to not use RGBA/GrayA pixmap now that MuPDF supports it (Issue #93) */
+#  if HAVE_FZ_NEW_PIXMAP_5_ARG
+  pixmap = fz_new_pixmap(context, cs, (int)ras.header.cupsWidth,  (int)ras.band_height, 1);
+#  else
+  pixmap = fz_new_pixmap(context, cs, (int)ras.header.cupsWidth,  (int)ras.band_height, NULL, 1);
+  pixmap->flags = 0;
+#  endif /* HAVE_FZ_NEW_PIXMAP_5_ARG */
+
+  pixmap->xres = (int)ras.header.HWResolution[0];
+  pixmap->yres = (int)ras.header.HWResolution[1];
+
+  xscale = ras.header.HWResolution[0] / 72.0;
+  yscale = ras.header.HWResolution[1] / 72.0;
+
+  if (Verbosity > 1)
+    fprintf(stderr, "DEBUG: xscale=%g, yscale=%g\n", xscale, yscale);
+  fz_scale(&base_transform, xscale, yscale);
+
+  if (Verbosity > 1)
+    fprintf(stderr, "DEBUG: Band height=%u, page height=%u\n", ras.band_height, ras.header.cupsHeight);
+
+  device = fz_new_draw_device(context, &base_transform, pixmap);
+
+#  ifndef HAVE_FZ_NEW_PIXMAP_5_ARG /* Bug in MuPDF 1.11 */
+  /* Don't anti-alias or interpolate when creating raster data */
+  fz_set_aa_level(context, 0);
+#  endif /* !HAVE_FZ_NEW_PIXMAP_5_ARG */
+
+  fz_enable_device_hints(context, device, FZ_DONT_INTERPOLATE_IMAGES);
+
+ /*
+  * Setup the back page transform, if any...
+  */
+
+  if (sheet_back && ras.header.Duplex)
+  {
+    if (!strcmp(sheet_back, "flipped"))
+    {
+      if (ras.header.Tumble)
+        back_transform = fz_make_matrix(-1, 0, 0, 1, ras.header.cupsPageSize[0], 0);
+      else
+        back_transform = fz_make_matrix(1, 0, 0, -1, 0, ras.header.cupsPageSize[1]);
+    }
+    else if (!strcmp(sheet_back, "manual-tumble") && ras.header.Tumble)
+      back_transform = fz_make_matrix(-1, 0, 0, -1, ras.header.cupsPageSize[0], ras.header.cupsPageSize[1]);
+    else if (!strcmp(sheet_back, "rotated") && !ras.header.Tumble)
+      back_transform = fz_make_matrix(-1, 0, 0, -1, ras.header.cupsPageSize[0], ras.header.cupsPageSize[1]);
+    else
+      back_transform = fz_make_matrix(1, 0, 0, 1, 0, 0);
+  }
+  else
+    back_transform = fz_make_matrix(1, 0, 0, 1, 0, 0);
+
+  if (Verbosity > 1)
+    fprintf(stderr, "DEBUG: cupsPageSize=[%g %g]\n", ras.header.cupsPageSize[0], ras.header.cupsPageSize[1]);
+  if (Verbosity > 1)
+    fprintf(stderr, "DEBUG: back_transform=[%g %g %g %g %g %g]\n", back_transform.a, back_transform.b, back_transform.c, back_transform.d, back_transform.e, back_transform.f);
+
+ /*
+  * Get print-scaling value...
+  */
+
+  if ((print_scaling = cupsGetOption("print-scaling", num_options, options)) == NULL)
+    if ((print_scaling = getenv("PRINTER_PRINT_SCALING_DEFAULT")) == NULL)
+      print_scaling = "auto";
+
+ /*
+  * Draw all of the pages...
+  */
+
+  (*(ras.start_job))(&ras, cb, ctx);
+
+  for (copy = 0; copy < ras.copies; copy ++)
+  {
+    for (page = 1; page <= pages; page ++)
+    {
+      unsigned         y,              /* Current line */
+                       band_starty = 0,/* Start line of band */
+                       band_endy = 0;  /* End line of band */
+      unsigned char    *lineptr;       /* Pointer to line */
+
+      pdf_page = fz_load_page(context, document, (int)(page + first - 2));
+
+      fz_bound_page(context, pdf_page, &image_box);
+
+      fprintf(stderr, "DEBUG: image_box=[%g %g %g %g]\n", image_box.x0, image_box.y0, image_box.x1, image_box.y1);
+
+      float image_width = image_box.x1 - image_box.x0;
+      float image_height = image_box.y1 - image_box.y0;
+      int image_rotation = 0;
+      int is_image = strcmp(informat, "application/pdf") != 0;
+      float image_xscale, image_yscale;
+
+      if ((image_height < image_width && ras.header.cupsWidth < ras.header.cupsHeight) ||
+          (image_width < image_height && ras.header.cupsHeight < ras.header.cupsWidth))
+      {
+       /*
+       * Rotate image/page 90 degrees...
+       */
+
+       image_rotation = 90;
+      }
+
+      if ((!strcmp(print_scaling, "auto") && ras.borderless && is_image) || !strcmp(print_scaling, "fill"))
+      {
+       /*
+       * Scale to fill...
+       */
+
+       if (image_rotation)
+       {
+         image_xscale = ras.header.cupsPageSize[0] / (double)image_height;
+         image_yscale = ras.header.cupsPageSize[1] / (double)image_width;
+       }
+       else
+       {
+         image_xscale = ras.header.cupsPageSize[0] / (double)image_width;
+         image_yscale = ras.header.cupsPageSize[1] / (double)image_height;
+       }
+
+       if (image_xscale < image_yscale)
+         image_xscale = image_yscale;
+       else
+         image_yscale = image_xscale;
+
+      }
+      else if ((!strcmp(print_scaling, "auto") && (is_image || (image_rotation == 0 && (image_width > ras.header.cupsPageSize[0] || image_height > ras.header.cupsPageSize[1])) || (image_rotation == 90 && (image_height > ras.header.cupsPageSize[1] || image_width > ras.header.cupsPageSize[1])))) || !strcmp(print_scaling, "fit"))
+      {
+       /*
+       * Scale to fit...
+       */
+
+       if (image_rotation)
+       {
+         image_xscale = ras.header.cupsPageSize[0] / (double)image_height;
+         image_yscale = ras.header.cupsPageSize[1] / (double)image_width;
+       }
+       else
+       {
+         image_xscale = ras.header.cupsPageSize[0] / (double)image_width;
+         image_yscale = ras.header.cupsPageSize[1] / (double)image_height;
+       }
+
+       if (image_xscale > image_yscale)
+         image_xscale = image_yscale;
+       else
+         image_yscale = image_xscale;
+      }
+      else
+      {
+       /*
+        * Do not scale...
+       */
+
+        image_xscale = image_yscale = 1.0;
+      }
+
+      if (image_rotation)
+      {
+       image_transform = fz_make_matrix(image_xscale, 0, 0, image_yscale, 0.5 * (ras.header.cupsPageSize[0] - image_xscale * image_height), 0.5 * (ras.header.cupsPageSize[1] - image_yscale * image_width));
+      }
+      else
+      {
+       image_transform = fz_make_matrix(image_xscale, 0, 0, image_yscale, 0.5 * (ras.header.cupsPageSize[0] - image_xscale * image_width), 0.5 * (ras.header.cupsPageSize[1] - image_yscale * image_height));
+      }
+
+      if (Verbosity > 1)
+        fprintf(stderr, "DEBUG: Printing copy %d/%d, page %d/%d, image_transform=[%g %g %g %g %g %g]\n", copy + 1, ras.copies, page, pages, image_transform.a, image_transform.b, image_transform.c, image_transform.d, image_transform.e, image_transform.f);
+
+      (*(ras.start_page))(&ras, page, cb, ctx);
+
+      for (y = ras.top; y <= ras.bottom; y ++)
+      {
+       if (y > band_endy)
+       {
+        /*
+         * Draw the next band of raster data...
+         */
+
+         band_starty = y;
+         band_endy   = y + ras.band_height - 1;
+         if (band_endy > ras.bottom)
+           band_endy = ras.bottom;
+
+         if (Verbosity > 1)
+           fprintf(stderr, "DEBUG: Drawing band from %u to %u.\n", band_starty, band_endy);
+
+          fz_clear_pixmap_with_value(context, pixmap, 0xff);
+
+          transform = fz_identity;
+         fz_pre_translate(&transform, 0.0, -1.0 * y / yscale);
+         if (!(page & 1) && ras.header.Duplex)
+           fz_concat(&transform, &transform, &back_transform);
+
+         fz_concat(&transform, &transform, &image_transform);
+
+          fprintf(stderr, "DEBUG: page transform=[%g %g %g %g %g %g]\n", transform.a, transform.b, transform.c, transform.d, transform.e, transform.f);
+
+          fz_run_page(context, pdf_page, device, &transform, NULL);
+       }
+
+       /*
+       * Prepare and write a line...
+       */
+
+       lineptr = pixmap->samples + (y - band_starty) * band_size + ras.left * ras.band_bpp;
+        if (ras.band_bpp == 4)
+          pack_rgba(lineptr, ras.right - ras.left + 1);
+        else
+          pack_graya(lineptr, ras.right - ras.left + 1);
+
+       (*(ras.write_line))(&ras, y, lineptr, cb, ctx);
+      }
+
+      (*(ras.end_page))(&ras, page, cb, ctx);
+
+      impressions ++;
+      fprintf(stderr, "ATTR: job-impressions-completed=%u\n", impressions);
+      if (!ras.header.Duplex || !(page & 1))
+      {
+        media_sheets ++;
+       fprintf(stderr, "ATTR: job-media-sheets-completed=%u\n", media_sheets);
+      }
+    }
+
+    if (ras.copies > 1 && (pages & 1) && ras.header.Duplex)
+    {
+     /*
+      * Duplex printing, add a blank back side image...
+      */
+
+      unsigned         y;              /* Current line */
+
+      if (Verbosity > 1)
+        fprintf(stderr, "DEBUG: Printing blank page %u for duplex.\n", pages + 1);
+
+      memset(pixmap->samples, 255, ras.header.cupsBytesPerLine);
+
+      (*(ras.start_page))(&ras, page, cb, ctx);
+
+      for (y = ras.top; y < ras.bottom; y ++)
+       (*(ras.write_line))(&ras, y, pixmap->samples, cb, ctx);
+
+      (*(ras.end_page))(&ras, page, cb, ctx);
+
+      impressions ++;
+      fprintf(stderr, "ATTR: job-impressions-completed=%u\n", impressions);
+      if (!ras.header.Duplex || !(page & 1))
+      {
+        media_sheets ++;
+       fprintf(stderr, "ATTR: job-media-sheets-completed=%u\n", media_sheets);
+      }
+    }
+  }
+
+  (*(ras.end_job))(&ras, cb, ctx);
+
+ /*
+  * Clean up...
+  */
+
+  fz_drop_device(context, device);
+  fz_drop_pixmap(context, pixmap);
+  fz_drop_document(context, document);
+  fz_drop_context(context);
+
+  return (1);
+}
+#endif /* __APPLE__ */
+
+
+/*
+ * 'xform_setup()' - Setup a raster context for printing.
+ */
+
+static int                             /* O - 0 on success, -1 on failure */
+xform_setup(xform_ctx_t *ras,  /* I - Raster information */
+            const char     *format,    /* I - Output format (MIME media type) */
+           const char     *resolutions,/* I - Supported resolutions */
+           const char     *sheet_back, /* I - Back side transform */
+           const char     *types,      /* I - Supported types */
+           int            color,       /* I - Document contains color? */
+            unsigned       pages,      /* I - Number of pages */
+            int            num_options,        /* I - Number of options */
+            cups_option_t  *options)   /* I - Options */
+{
+  const char   *copies,                /* "copies" option */
+               *media,                 /* "media" option */
+               *media_col;             /* "media-col" option */
+  pwg_media_t  *pwg_media = NULL;      /* PWG media value */
+  const char   *print_color_mode,      /* "print-color-mode" option */
+               *print_quality,         /* "print-quality" option */
+               *printer_resolution,    /* "printer-resolution" option */
+               *sides,                 /* "sides" option */
+               *type;                  /* Raster type to use */
+  int          draft = 0,              /* Draft quality? */
+               xdpi, ydpi;             /* Resolution to use */
+  cups_array_t *res_array,             /* Resolutions in array */
+               *type_array;            /* Types in array */
+
+
+ /*
+  * Initialize raster information...
+  */
+
+  memset(ras, 0, sizeof(xform_ctx_t));
+
+  ras->format      = format;
+  ras->num_options = num_options;
+  ras->options     = options;
+
+  if (!strcmp(format, "application/vnd.hp-pcl"))
+    pcl_init(ras);
+  else
+    raster_init(ras);
+
+ /*
+  * Get the number of copies...
+  */
+
+  if ((copies = cupsGetOption("copies", num_options, options)) != NULL)
+  {
+    int temp = atoi(copies);           /* Copies value */
+
+    if (temp < 1 || temp > 9999)
+    {
+      fprintf(stderr, "ERROR: Invalid \"copies\" value '%s'.\n", copies);
+      return (-1);
+    }
+
+    ras->copies = (unsigned)temp;
+  }
+  else
+    ras->copies = 1;
+
+ /*
+  * Figure out the media size...
+  */
+
+  if ((media = cupsGetOption("media", num_options, options)) != NULL)
+  {
+    if ((pwg_media = pwgMediaForPWG(media)) == NULL)
+      pwg_media = pwgMediaForLegacy(media);
+
+    if (!pwg_media)
+    {
+      fprintf(stderr, "ERROR: Unknown \"media\" value '%s'.\n", media);
+      return (-1);
+    }
+  }
+  else if ((media_col = cupsGetOption("media-col", num_options, options)) != NULL)
+  {
+    int                        num_cols;       /* Number of collection values */
+    cups_option_t      *cols;          /* Collection values */
+    const char         *media_size_name,
+                       *media_size,    /* Collection attributes */
+                       *media_bottom_margin,
+                       *media_left_margin,
+                       *media_right_margin,
+                       *media_top_margin;
+
+    num_cols = cupsParseOptions(media_col, 0, &cols);
+    if ((media_size_name = cupsGetOption("media-size-name", num_cols, cols)) != NULL)
+    {
+      if ((pwg_media = pwgMediaForPWG(media_size_name)) == NULL)
+      {
+       fprintf(stderr, "ERROR: Unknown \"media-size-name\" value '%s'.\n", media_size_name);
+       cupsFreeOptions(num_cols, cols);
+       return (-1);
+      }
+    }
+    else if ((media_size = cupsGetOption("media-size", num_cols, cols)) != NULL)
+    {
+      int              num_sizes;      /* Number of collection values */
+      cups_option_t    *sizes;         /* Collection values */
+      const char       *x_dim,         /* Collection attributes */
+                       *y_dim;
+
+      num_sizes = cupsParseOptions(media_size, 0, &sizes);
+      if ((x_dim = cupsGetOption("x-dimension", num_sizes, sizes)) != NULL && (y_dim = cupsGetOption("y-dimension", num_sizes, sizes)) != NULL)
+      {
+        pwg_media = pwgMediaForSize(atoi(x_dim), atoi(y_dim));
+      }
+      else
+      {
+        fprintf(stderr, "ERROR: Bad \"media-size\" value '%s'.\n", media_size);
+       cupsFreeOptions(num_sizes, sizes);
+       cupsFreeOptions(num_cols, cols);
+       return (-1);
+      }
+
+      cupsFreeOptions(num_sizes, sizes);
+    }
+
+   /*
+    * Check whether the media-col is for a borderless size...
+    */
+
+    if ((media_bottom_margin = cupsGetOption("media-bottom-margin", num_cols, cols)) != NULL && !strcmp(media_bottom_margin, "0") &&
+        (media_left_margin = cupsGetOption("media-left-margin", num_cols, cols)) != NULL && !strcmp(media_left_margin, "0") &&
+        (media_right_margin = cupsGetOption("media-right-margin", num_cols, cols)) != NULL && !strcmp(media_right_margin, "0") &&
+        (media_top_margin = cupsGetOption("media-top-margin", num_cols, cols)) != NULL && !strcmp(media_top_margin, "0"))
+      ras->borderless = 1;
+
+    cupsFreeOptions(num_cols, cols);
+  }
+
+  if (!pwg_media)
+  {
+   /*
+    * Use default size...
+    */
+
+    const char *media_default = getenv("PRINTER_MEDIA_DEFAULT");
+                               /* "media-default" value */
+
+    if (!media_default)
+      media_default = "na_letter_8.5x11in";
+
+    if ((pwg_media = pwgMediaForPWG(media_default)) == NULL)
+    {
+      fprintf(stderr, "ERROR: Unknown \"media-default\" value '%s'.\n", media_default);
+      return (-1);
+    }
+  }
+
+ /*
+  * Map certain photo sizes (4x6, 5x7, 8x10) to borderless...
+  */
+
+  if ((pwg_media->width == 10160 && pwg_media->length == 15240) ||(pwg_media->width == 12700 && pwg_media->length == 17780) ||(pwg_media->width == 20320 && pwg_media->length == 25400))
+    ras->borderless = 1;
+
+ /*
+  * Figure out the proper resolution, etc.
+  */
+
+  res_array = _cupsArrayNewStrings(resolutions, ',');
+
+  if ((printer_resolution = cupsGetOption("printer-resolution", num_options, options)) != NULL && !cupsArrayFind(res_array, (void *)printer_resolution))
+  {
+    if (Verbosity)
+      fprintf(stderr, "INFO: Unsupported \"printer-resolution\" value '%s'.\n", printer_resolution);
+    printer_resolution = NULL;
+  }
+
+  if (!printer_resolution)
+  {
+    if ((print_quality = cupsGetOption("print-quality", num_options, options)) != NULL)
+    {
+      switch (atoi(print_quality))
+      {
+        case IPP_QUALITY_DRAFT :
+           draft              = 1;
+           printer_resolution = cupsArrayIndex(res_array, 0);
+           break;
+
+        case IPP_QUALITY_NORMAL :
+           printer_resolution = cupsArrayIndex(res_array, cupsArrayCount(res_array) / 2);
+           break;
+
+        case IPP_QUALITY_HIGH :
+           printer_resolution = cupsArrayIndex(res_array, cupsArrayCount(res_array) - 1);
+           break;
+
+       default :
+           if (Verbosity)
+             fprintf(stderr, "INFO: Unsupported \"print-quality\" value '%s'.\n", print_quality);
+           break;
+      }
+    }
+  }
+
+  if (!printer_resolution)
+    printer_resolution = cupsArrayIndex(res_array, cupsArrayCount(res_array) / 2);
+
+  if (!printer_resolution)
+  {
+    fputs("ERROR: No \"printer-resolution\" or \"pwg-raster-document-resolution-supported\" value.\n", stderr);
+    return (-1);
+  }
+
+ /*
+  * Parse the "printer-resolution" value...
+  */
+
+  if (sscanf(printer_resolution, "%ux%udpi", &xdpi, &ydpi) != 2)
+  {
+    if (sscanf(printer_resolution, "%udpi", &xdpi) == 1)
+    {
+      ydpi = xdpi;
+    }
+    else
+    {
+      fprintf(stderr, "ERROR: Bad resolution value '%s'.\n", printer_resolution);
+      return (-1);
+    }
+  }
+
+  cupsArrayDelete(res_array);
+
+ /*
+  * Now figure out the color space to use...
+  */
+
+  if ((print_color_mode = cupsGetOption("print-color-mode", num_options, options)) == NULL)
+    print_color_mode = getenv("PRINTER_PRINT_COLOR_MODE_DEFAULT");
+
+  if (print_color_mode)
+  {
+    if (!strcmp(print_color_mode, "monochrome") || !strcmp(print_color_mode, "process-monochrome") || !strcmp(print_color_mode, "auto-monochrome"))
+    {
+      color = 0;
+    }
+    else if (!strcmp(print_color_mode, "bi-level") || !strcmp(print_color_mode, "process-bi-level"))
+    {
+      color = 0;
+      draft = 1;
+    }
+  }
+
+  type_array = _cupsArrayNewStrings(types, ',');
+
+  if (color && cupsArrayFind(type_array, "srgb_8"))
+    type = "srgb_8";
+  else if (draft && cupsArrayFind(type_array, "black_1"))
+    type = "black_1";
+  else if (draft && cupsArrayFind(type_array, "sgray_1"))
+    type = "sgray_1";
+  else
+    type = "sgray_8";
+
+ /*
+  * Initialize the raster header...
+  */
+
+  if (pages == 1)
+    sides = "one-sided";
+  else if ((sides = cupsGetOption("sides", num_options, options)) == NULL)
+  {
+    if ((sides = getenv("PRINTER_SIDES_DEFAULT")) == NULL)
+      sides = "one-sided";
+  }
+
+  if (ras->copies > 1 && (pages & 1) && strcmp(sides, "one-sided"))
+    pages ++;
+
+  if (!cupsRasterInitPWGHeader(&(ras->header), pwg_media, type, xdpi, ydpi, sides, NULL))
+  {
+    fprintf(stderr, "ERROR: Unable to initialize raster context: %s\n", cupsRasterErrorString());
+    return (-1);
+  }
+
+  if (pages > 1)
+  {
+    if (!cupsRasterInitPWGHeader(&(ras->back_header), pwg_media, type, xdpi, ydpi, sides, sheet_back))
+    {
+      fprintf(stderr, "ERROR: Unable to initialize back side raster context: %s\n", cupsRasterErrorString());
+      return (-1);
+    }
+  }
+
+  ras->header.cupsInteger[CUPS_RASTER_PWG_TotalPageCount]      = ras->copies * pages;
+  ras->back_header.cupsInteger[CUPS_RASTER_PWG_TotalPageCount] = ras->copies * pages;
+
+  return (0);
+}
+#endif /* 0 */
diff --git a/filter/cupsxform.c b/filter/cupsxform.c
new file mode 100644 (file)
index 0000000..c53368f
--- /dev/null
@@ -0,0 +1,997 @@
+/*
+ * Utility for converting PDF and JPEG files to raster data, PCL, PDF, or PS.
+ *
+ * Copyright Â©Â 2016-2018 by Apple Inc..
+ *
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
+ */
+
+/*
+ * Include necessayr headers...
+ */
+
+#include <cups/xform-private.h>
+
+
+/*
+ * 'main()' - Main entry for transform utility.
+ */
+
+int                                    /* O - Exit status */
+main(int  argc,                                /* I - Number of command-line args */
+     char *argv[])                     /* I - Command-line arguments */
+{
+#if 0
+  int          i;                      /* Looping var */
+  const char   *filename = NULL,       /* File to transform */
+               *content_type,          /* Source content type */
+               *device_uri,            /* Destination URI */
+               *output_type,           /* Destination content type */
+               *resolutions,           /* pwg-raster-document-resolution-supported */
+               *sheet_back,            /* pwg-raster-document-sheet-back */
+               *types,                 /* pwg-raster-document-type-supported */
+               *opt;                   /* Option character */
+  int          num_options;            /* Number of options */
+  cups_option_t        *options;               /* Options */
+  int          fd = 1;                 /* Output file/socket */
+  http_t       *http = NULL;           /* Output HTTP connection */
+  void         *write_ptr = &fd;       /* Pointer to file/socket/HTTP connection */
+  char         resource[1024];         /* URI resource path */
+  xform_write_cb_t write_cb = (xform_write_cb_t)write_fd;
+                                       /* Write callback */
+  int          status = 0;             /* Exit status */
+  _cups_thread_t monitor = 0;          /* Monitoring thread ID */
+
+
+ /*
+  * Process the command-line...
+  */
+
+  num_options  = load_env_options(&options);
+  content_type = getenv("CONTENT_TYPE");
+  device_uri   = getenv("DEVICE_URI");
+  output_type  = getenv("OUTPUT_TYPE");
+  resolutions  = getenv("PWG_RASTER_DOCUMENT_RESOLUTION_SUPPORTED");
+  sheet_back   = getenv("PWG_RASTER_DOCUMENT_SHEET_BACK");
+  types        = getenv("PWG_RASTER_DOCUMENT_TYPE_SUPPORTED");
+
+  if ((opt = getenv("SERVER_LOGLEVEL")) != NULL)
+  {
+    if (!strcmp(opt, "debug"))
+      Verbosity = 2;
+    else if (!strcmp(opt, "info"))
+      Verbosity = 1;
+  }
+
+  for (i = 1; i < argc; i ++)
+  {
+    if (!strncmp(argv[i], "--", 2))
+    {
+      if (!strcmp(argv[i], "--help"))
+      {
+        usage(0);
+      }
+      else if (!strcmp(argv[i], "--version"))
+      {
+        puts(CUPS_SVERSION);
+      }
+      else
+      {
+       fprintf(stderr, "ERROR: Unknown option '%s'.\n", argv[i]);
+       usage(1);
+      }
+    }
+    else if (argv[i][0] == '-')
+    {
+      for (opt = argv[i] + 1; *opt; opt ++)
+      {
+        switch (*opt)
+       {
+         case 'd' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             device_uri = argv[i];
+             break;
+
+         case 'i' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             content_type = argv[i];
+             break;
+
+         case 'm' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             output_type = argv[i];
+             break;
+
+         case 'o' :
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             num_options = cupsParseOptions(argv[i], num_options, &options);
+             break;
+
+         case 'r' : /* pwg-raster-document-resolution-supported values */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             resolutions = argv[i];
+             break;
+
+         case 's' : /* pwg-raster-document-sheet-back value */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             sheet_back = argv[i];
+             break;
+
+         case 't' : /* pwg-raster-document-type-supported values */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             types = argv[i];
+             break;
+
+         case 'v' : /* Be verbose... */
+             Verbosity ++;
+             break;
+
+         default :
+             fprintf(stderr, "ERROR: Unknown option '-%c'.\n", *opt);
+             usage(1);
+             break;
+       }
+      }
+    }
+    else if (!filename)
+      filename = argv[i];
+    else
+      usage(1);
+  }
+
+ /*
+  * Check that we have everything we need...
+  */
+
+  if (!filename)
+    usage(1);
+
+  if (!content_type)
+  {
+    if ((opt = strrchr(filename, '.')) != NULL)
+    {
+      if (!strcmp(opt, ".pdf"))
+        content_type = "application/pdf";
+      else if (!strcmp(opt, ".jpg") || !strcmp(opt, ".jpeg"))
+        content_type = "image/jpeg";
+    }
+  }
+
+  if (!content_type)
+  {
+    fprintf(stderr, "ERROR: Unknown format for \"%s\", please specify with '-i' option.\n", filename);
+    usage(1);
+  }
+  else if (strcmp(content_type, "application/pdf") && strcmp(content_type, "image/jpeg"))
+  {
+    fprintf(stderr, "ERROR: Unsupported format \"%s\" for \"%s\".\n", content_type, filename);
+    usage(1);
+  }
+
+  if (!output_type)
+  {
+    fputs("ERROR: Unknown output format, please specify with '-m' option.\n", stderr);
+    usage(1);
+  }
+  else if (strcmp(output_type, "application/vnd.hp-pcl") && strcmp(output_type, "image/pwg-raster") && strcmp(output_type, "image/urf"))
+  {
+    fprintf(stderr, "ERROR: Unsupported output format \"%s\".\n", output_type);
+    usage(1);
+  }
+
+  if (!resolutions)
+    resolutions = "300dpi";
+  if (!sheet_back)
+    sheet_back = "normal";
+  if (!types)
+    types = "sgray_8";
+
+ /*
+  * If the device URI is specified, open the connection...
+  */
+
+  if (device_uri)
+  {
+    char       scheme[32],             /* URI scheme */
+               userpass[256],          /* URI user:pass */
+               host[256],              /* URI host */
+               service[32];            /* Service port */
+    int                port;                   /* URI port number */
+    http_addrlist_t *list;             /* Address list for socket */
+
+    if (httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+    {
+      fprintf(stderr, "ERROR: Invalid device URI \"%s\".\n", device_uri);
+      usage(1);
+    }
+
+    if (strcmp(scheme, "socket") && strcmp(scheme, "ipp") && strcmp(scheme, "ipps"))
+    {
+      fprintf(stderr, "ERROR: Unsupported device URI scheme \"%s\".\n", scheme);
+      usage(1);
+    }
+
+    snprintf(service, sizeof(service), "%d", port);
+    if ((list = httpAddrGetList(host, AF_UNSPEC, service)) == NULL)
+    {
+      fprintf(stderr, "ERROR: Unable to lookup device URI host \"%s\": %s\n", host, cupsLastErrorString());
+      return (1);
+    }
+
+    if (!strcmp(scheme, "socket"))
+    {
+     /*
+      * AppSocket connection...
+      */
+
+      if (!httpAddrConnect2(list, &fd, 30000, NULL))
+      {
+       fprintf(stderr, "ERROR: Unable to connect to \"%s\" on port %d: %s\n", host, port, cupsLastErrorString());
+       return (1);
+      }
+    }
+    else
+    {
+      http_encryption_t encryption;    /* Encryption mode */
+      ipp_t            *request,       /* IPP request */
+                       *response;      /* IPP response */
+      ipp_attribute_t  *attr;          /* operations-supported */
+      int              create_job = 0; /* Support for Create-Job/Send-Document? */
+      const char       *job_name;      /* Title of job */
+      const char       *media;         /* Value of "media" option */
+      const char       *sides;         /* Value of "sides" option */
+
+     /*
+      * Connect to the IPP/IPPS printer...
+      */
+
+      if (port == 443 || !strcmp(scheme, "ipps"))
+        encryption = HTTP_ENCRYPTION_ALWAYS;
+      else
+        encryption = HTTP_ENCRYPTION_IF_REQUESTED;
+
+      if ((http = httpConnect2(host, port, list, AF_UNSPEC, encryption, 1, 30000, NULL)) == NULL)
+      {
+       fprintf(stderr, "ERROR: Unable to connect to \"%s\" on port %d: %s\n", host, port, cupsLastErrorString());
+       return (1);
+      }
+
+     /*
+      * See if it supports Create-Job + Send-Document...
+      */
+
+      request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
+      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", NULL, "operations-supported");
+
+      response = cupsDoRequest(http, request, resource);
+      if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
+      {
+        fprintf(stderr, "ERROR: Unable to get printer capabilities: %s\n", cupsLastErrorString());
+       return (1);
+      }
+
+      if ((attr = ippFindAttribute(response, "operations-supported", IPP_TAG_ENUM)) == NULL)
+      {
+        fputs("ERROR: Unable to get list of supported operations from printer.\n", stderr);
+       return (1);
+      }
+
+      create_job = ippContainsInteger(attr, IPP_OP_CREATE_JOB) && ippContainsInteger(attr, IPP_OP_SEND_DOCUMENT);
+
+      ippDelete(response);
+
+     /*
+      * Create the job and start printing...
+      */
+
+      if ((job_name = getenv("IPP_JOB_NAME")) == NULL)
+      {
+       if ((job_name = strrchr(filename, '/')) != NULL)
+         job_name ++;
+       else
+         job_name = filename;
+      }
+
+      if (create_job)
+      {
+        int            job_id = 0;     /* Job ID */
+
+        request = ippNewRequest(IPP_OP_CREATE_JOB);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, job_name);
+
+        response = cupsDoRequest(http, request, resource);
+        if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
+         job_id = ippGetInteger(attr, 0);
+        ippDelete(response);
+
+       if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
+       {
+         fprintf(stderr, "ERROR: Unable to create print job: %s\n", cupsLastErrorString());
+         return (1);
+       }
+       else if (job_id <= 0)
+       {
+          fputs("ERROR: No job-id for created print job.\n", stderr);
+         return (1);
+       }
+
+        request = ippNewRequest(IPP_OP_SEND_DOCUMENT);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+       ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, output_type);
+        ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);
+      }
+      else
+      {
+        request = ippNewRequest(IPP_OP_PRINT_JOB);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, device_uri);
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, output_type);
+      }
+
+      if ((media = cupsGetOption("media", num_options, options)) != NULL)
+        ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "media", NULL, media);
+
+      if ((sides = cupsGetOption("sides", num_options, options)) != NULL)
+        ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "sides", NULL, sides);
+
+      if (cupsSendRequest(http, request, resource, 0) != HTTP_STATUS_CONTINUE)
+      {
+        fprintf(stderr, "ERROR: Unable to send print data: %s\n", cupsLastErrorString());
+       return (1);
+      }
+
+      ippDelete(request);
+
+      write_cb  = (xform_write_cb_t)httpWrite2;
+      write_ptr = http;
+
+      monitor = _cupsThreadCreate((_cups_thread_func_t)monitor_ipp, (void *)device_uri);
+    }
+
+    httpAddrFreeList(list);
+  }
+
+ /*
+  * Do transform...
+  */
+
+  status = xform_document(filename, content_type, output_type, resolutions, sheet_back, types, num_options, options, write_cb, write_ptr);
+
+  if (http)
+  {
+    ippDelete(cupsGetResponse(http, resource));
+
+    if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
+    {
+      fprintf(stderr, "ERROR: Unable to send print data: %s\n", cupsLastErrorString());
+      status = 1;
+    }
+
+    httpClose(http);
+  }
+  else if (fd != 1)
+    close(fd);
+
+  if (monitor)
+    _cupsThreadCancel(monitor);
+
+  return (status);
+#endif /* 0 */
+
+  (void)argc;
+  (void)argv;
+
+  return (0);
+}
+
+
+/*
+ * 'pcl_end_job()' - End a PCL "job".
+ */
+
+static void
+pcl_end_job(xform_raster_t   *ras,     /* I - Raster information */
+            xform_write_cb_t cb,       /* I - Write callback */
+            void             *ctx)     /* I - Write context */
+{
+  (void)ras;
+
+ /*
+  * Send a PCL reset sequence.
+  */
+
+  (*cb)(ctx, (const unsigned char *)"\033E", 2);
+}
+
+
+/*
+ * 'pcl_end_page()' - End of PCL page.
+ */
+
+static void
+pcl_end_page(xform_raster_t   *ras,    /* I - Raster information */
+            unsigned         page,     /* I - Current page */
+             xform_write_cb_t cb,      /* I - Write callback */
+             void             *ctx)    /* I - Write context */
+{
+ /*
+  * End graphics...
+  */
+
+  (*cb)(ctx, (const unsigned char *)"\033*r0B", 5);
+
+ /*
+  * Formfeed as needed...
+  */
+
+  if (!(ras->header.Duplex && (page & 1)))
+    (*cb)(ctx, (const unsigned char *)"\014", 1);
+
+ /*
+  * Free the output buffer...
+  */
+
+  free(ras->out_buffer);
+  ras->out_buffer = NULL;
+}
+
+
+/*
+ * 'pcl_init()' - Initialize callbacks for PCL output.
+ */
+
+static void
+pcl_init(xform_raster_t *ras)          /* I - Raster information */
+{
+  ras->end_job    = pcl_end_job;
+  ras->end_page   = pcl_end_page;
+  ras->start_job  = pcl_start_job;
+  ras->start_page = pcl_start_page;
+  ras->write_line = pcl_write_line;
+}
+
+
+/*
+ * 'pcl_printf()' - Write a formatted string.
+ */
+
+static void
+pcl_printf(xform_write_cb_t cb,                /* I - Write callback */
+           void             *ctx,      /* I - Write context */
+          const char       *format,    /* I - Printf-style format string */
+          ...)                         /* I - Additional arguments as needed */
+{
+  va_list      ap;                     /* Argument pointer */
+  char         buffer[8192];           /* Buffer */
+
+
+  va_start(ap, format);
+  vsnprintf(buffer, sizeof(buffer), format, ap);
+  va_end(ap);
+
+  (*cb)(ctx, (const unsigned char *)buffer, strlen(buffer));
+}
+
+
+/*
+ * 'pcl_start_job()' - Start a PCL "job".
+ */
+
+static void
+pcl_start_job(xform_raster_t   *ras,   /* I - Raster information */
+              xform_write_cb_t cb,     /* I - Write callback */
+              void             *ctx)   /* I - Write context */
+{
+  (void)ras;
+
+ /*
+  * Send a PCL reset sequence.
+  */
+
+  (*cb)(ctx, (const unsigned char *)"\033E", 2);
+}
+
+
+/*
+ * 'pcl_start_page()' - Start a PCL page.
+ */
+
+static void
+pcl_start_page(xform_raster_t   *ras,  /* I - Raster information */
+               unsigned         page,  /* I - Current page */
+               xform_write_cb_t cb,    /* I - Write callback */
+               void             *ctx)  /* I - Write context */
+{
+ /*
+  * Setup margins to be 1/6" top and bottom and 1/4" or .135" on the
+  * left and right.
+  */
+
+  ras->top    = ras->header.HWResolution[1] / 6;
+  ras->bottom = ras->header.cupsHeight - ras->header.HWResolution[1] / 6 - 1;
+
+  if (ras->header.PageSize[1] == 842)
+  {
+   /* A4 gets special side margins to expose an 8" print area */
+    ras->left  = (ras->header.cupsWidth - 8 * ras->header.HWResolution[0]) / 2;
+    ras->right = ras->left + 8 * ras->header.HWResolution[0] - 1;
+  }
+  else
+  {
+   /* All other sizes get 1/4" margins */
+    ras->left  = ras->header.HWResolution[0] / 4;
+    ras->right = ras->header.cupsWidth - ras->header.HWResolution[0] / 4 - 1;
+  }
+
+  if (!ras->header.Duplex || (page & 1))
+  {
+   /*
+    * Set the media size...
+    */
+
+    pcl_printf(cb, ctx, "\033&l12D\033&k12H");
+                                       /* Set 12 LPI, 10 CPI */
+    pcl_printf(cb, ctx, "\033&l0O");   /* Set portrait orientation */
+
+    switch (ras->header.PageSize[1])
+    {
+      case 540 : /* Monarch Envelope */
+          pcl_printf(cb, ctx, "\033&l80A");
+         break;
+
+      case 595 : /* A5 */
+          pcl_printf(cb, ctx, "\033&l25A");
+         break;
+
+      case 624 : /* DL Envelope */
+          pcl_printf(cb, ctx, "\033&l90A");
+         break;
+
+      case 649 : /* C5 Envelope */
+          pcl_printf(cb, ctx, "\033&l91A");
+         break;
+
+      case 684 : /* COM-10 Envelope */
+          pcl_printf(cb, ctx, "\033&l81A");
+         break;
+
+      case 709 : /* B5 Envelope */
+          pcl_printf(cb, ctx, "\033&l100A");
+         break;
+
+      case 756 : /* Executive */
+          pcl_printf(cb, ctx, "\033&l1A");
+         break;
+
+      case 792 : /* Letter */
+          pcl_printf(cb, ctx, "\033&l2A");
+         break;
+
+      case 842 : /* A4 */
+          pcl_printf(cb, ctx, "\033&l26A");
+         break;
+
+      case 1008 : /* Legal */
+          pcl_printf(cb, ctx, "\033&l3A");
+         break;
+
+      case 1191 : /* A3 */
+          pcl_printf(cb, ctx, "\033&l27A");
+         break;
+
+      case 1224 : /* Tabloid */
+          pcl_printf(cb, ctx, "\033&l6A");
+         break;
+    }
+
+   /*
+    * Set top margin and turn off perforation skip...
+    */
+
+    pcl_printf(cb, ctx, "\033&l%uE\033&l0L", 12 * ras->top / ras->header.HWResolution[1]);
+
+    if (ras->header.Duplex)
+    {
+      int mode = ras->header.Duplex ? 1 + ras->header.Tumble != 0 : 0;
+
+      pcl_printf(cb, ctx, "\033&l%dS", mode);
+                                       /* Set duplex mode */
+    }
+  }
+  else if (ras->header.Duplex)
+    pcl_printf(cb, ctx, "\033&a2G");   /* Print on back side */
+
+ /*
+  * Set graphics mode...
+  */
+
+  pcl_printf(cb, ctx, "\033*t%uR", ras->header.HWResolution[0]);
+                                       /* Set resolution */
+  pcl_printf(cb, ctx, "\033*r%uS", ras->right - ras->left + 1);
+                                       /* Set width */
+  pcl_printf(cb, ctx, "\033*r%uT", ras->bottom - ras->top + 1);
+                                       /* Set height */
+  pcl_printf(cb, ctx, "\033&a0H\033&a%uV", 720 * ras->top / ras->header.HWResolution[1]);
+                                       /* Set position */
+
+  pcl_printf(cb, ctx, "\033*b2M");     /* Use PackBits compression */
+  pcl_printf(cb, ctx, "\033*r1A");     /* Start graphics */
+
+ /*
+  * Allocate the output buffer...
+  */
+
+  ras->out_blanks  = 0;
+  ras->out_length  = (ras->right - ras->left + 8) / 8;
+  ras->out_buffer  = malloc(ras->out_length);
+  ras->comp_buffer = malloc(2 * ras->out_length + 2);
+}
+
+
+/*
+ * 'pcl_write_line()' - Write a line of raster data.
+ */
+
+static void
+pcl_write_line(
+    xform_raster_t      *ras,          /* I - Raster information */
+    unsigned            y,             /* I - Line number */
+    const unsigned char *line,         /* I - Pixels on line */
+    xform_write_cb_t    cb,            /* I - Write callback */
+    void                *ctx)          /* I - Write context */
+{
+  unsigned     x;                      /* Column number */
+  unsigned char        bit,                    /* Current bit */
+               byte,                   /* Current byte */
+               *outptr,                /* Pointer into output buffer */
+               *outend,                /* End of output buffer */
+               *start,                 /* Start of sequence */
+               *compptr;               /* Pointer into compression buffer */
+  unsigned     count;                  /* Count of bytes for output */
+
+
+  if (line[0] == 255 && !memcmp(line, line + 1, ras->right - ras->left))
+  {
+   /*
+    * Skip blank line...
+    */
+
+    ras->out_blanks ++;
+    return;
+  }
+
+ /*
+  * Dither the line into the output buffer...
+  */
+
+  y &= 63;
+
+  for (x = ras->left, bit = 128, byte = 0, outptr = ras->out_buffer; x <= ras->right; x ++, line ++)
+  {
+    if (*line <= threshold[x & 63][y])
+      byte |= bit;
+
+    if (bit == 1)
+    {
+      *outptr++ = byte;
+      byte      = 0;
+      bit       = 128;
+    }
+    else
+      bit >>= 1;
+  }
+
+  if (bit != 128)
+    *outptr++ = byte;
+
+ /*
+  * Apply compression...
+  */
+
+  compptr = ras->comp_buffer;
+  outend  = outptr;
+  outptr  = ras->out_buffer;
+
+  while (outptr < outend)
+  {
+    if ((outptr + 1) >= outend)
+    {
+     /*
+      * Single byte on the end...
+      */
+
+      *compptr++ = 0x00;
+      *compptr++ = *outptr++;
+    }
+    else if (outptr[0] == outptr[1])
+    {
+     /*
+      * Repeated sequence...
+      */
+
+      outptr ++;
+      count = 2;
+
+      while (outptr < (outend - 1) &&
+            outptr[0] == outptr[1] &&
+            count < 127)
+      {
+       outptr ++;
+       count ++;
+      }
+
+      *compptr++ = (unsigned char)(257 - count);
+      *compptr++ = *outptr++;
+    }
+    else
+    {
+     /*
+      * Non-repeated sequence...
+      */
+
+      start = outptr;
+      outptr ++;
+      count = 1;
+
+      while (outptr < (outend - 1) &&
+            outptr[0] != outptr[1] &&
+            count < 127)
+      {
+       outptr ++;
+       count ++;
+      }
+
+      *compptr++ = (unsigned char)(count - 1);
+
+      memcpy(compptr, start, count);
+      compptr += count;
+    }
+  }
+
+ /*
+  * Output the line...
+  */
+
+  if (ras->out_blanks > 0)
+  {
+   /*
+    * Skip blank lines first...
+    */
+
+    pcl_printf(cb, ctx, "\033*b%dY", ras->out_blanks);
+    ras->out_blanks = 0;
+  }
+
+  pcl_printf(cb, ctx, "\033*b%dW", (int)(compptr - ras->comp_buffer));
+  (*cb)(ctx, ras->comp_buffer, (size_t)(compptr - ras->comp_buffer));
+}
+
+
+/*
+ * 'raster_end_job()' - End a raster "job".
+ */
+
+static void
+raster_end_job(xform_raster_t   *ras,  /* I - Raster information */
+              xform_write_cb_t cb,     /* I - Write callback */
+              void             *ctx)   /* I - Write context */
+{
+  (void)cb;
+  (void)ctx;
+
+  cupsRasterClose(ras->ras);
+}
+
+
+/*
+ * 'raster_end_page()' - End of raster page.
+ */
+
+static void
+raster_end_page(xform_raster_t   *ras, /* I - Raster information */
+               unsigned         page,  /* I - Current page */
+               xform_write_cb_t cb,    /* I - Write callback */
+               void             *ctx)  /* I - Write context */
+{
+  (void)page;
+  (void)cb;
+  (void)ctx;
+
+  if (ras->header.cupsBitsPerPixel == 1)
+  {
+    free(ras->out_buffer);
+    ras->out_buffer = NULL;
+  }
+}
+
+
+/*
+ * 'raster_init()' - Initialize callbacks for raster output.
+ */
+
+static void
+raster_init(xform_raster_t *ras)       /* I - Raster information */
+{
+  ras->end_job    = raster_end_job;
+  ras->end_page   = raster_end_page;
+  ras->start_job  = raster_start_job;
+  ras->start_page = raster_start_page;
+  ras->write_line = raster_write_line;
+}
+
+
+/*
+ * 'raster_start_job()' - Start a raster "job".
+ */
+
+static void
+raster_start_job(xform_raster_t   *ras,        /* I - Raster information */
+                xform_write_cb_t cb,   /* I - Write callback */
+                void             *ctx) /* I - Write context */
+{
+  ras->ras = cupsRasterOpenIO((cups_raster_iocb_t)cb, ctx, !strcmp(ras->format, "image/pwg-raster") ? CUPS_RASTER_WRITE_PWG : CUPS_RASTER_WRITE_APPLE);
+}
+
+
+/*
+ * 'raster_start_page()' - Start a raster page.
+ */
+
+static void
+raster_start_page(xform_raster_t   *ras,/* I - Raster information */
+                 unsigned         page,/* I - Current page */
+                 xform_write_cb_t cb,  /* I - Write callback */
+                 void             *ctx)/* I - Write context */
+{
+  (void)cb;
+  (void)ctx;
+
+  ras->left   = 0;
+  ras->top    = 0;
+  ras->right  = ras->header.cupsWidth - 1;
+  ras->bottom = ras->header.cupsHeight - 1;
+
+  if (ras->header.Duplex && !(page & 1))
+    cupsRasterWriteHeader2(ras->ras, &ras->back_header);
+  else
+    cupsRasterWriteHeader2(ras->ras, &ras->header);
+
+  if (ras->header.cupsBitsPerPixel == 1)
+  {
+    ras->out_length = ras->header.cupsBytesPerLine;
+    ras->out_buffer = malloc(ras->header.cupsBytesPerLine);
+  }
+}
+
+
+/*
+ * 'raster_write_line()' - Write a line of raster data.
+ */
+
+static void
+raster_write_line(
+    xform_raster_t      *ras,          /* I - Raster information */
+    unsigned            y,             /* I - Line number */
+    const unsigned char *line,         /* I - Pixels on line */
+    xform_write_cb_t    cb,            /* I - Write callback */
+    void                *ctx)          /* I - Write context */
+{
+  (void)cb;
+  (void)ctx;
+
+  if (ras->header.cupsBitsPerPixel == 1)
+  {
+   /*
+    * Dither the line into the output buffer...
+    */
+
+    unsigned           x;              /* Column number */
+    unsigned char      bit,            /* Current bit */
+                       byte,           /* Current byte */
+                       *outptr;        /* Pointer into output buffer */
+
+    y &= 63;
+
+    if (ras->header.cupsColorSpace == CUPS_CSPACE_SW)
+    {
+      for (x = ras->left, bit = 128, byte = 0, outptr = ras->out_buffer; x <= ras->right; x ++, line ++)
+      {
+       if (*line > threshold[x % 25][y])
+         byte |= bit;
+
+       if (bit == 1)
+       {
+         *outptr++ = byte;
+         byte      = 0;
+         bit       = 128;
+       }
+       else
+         bit >>= 1;
+      }
+    }
+    else
+    {
+      for (x = ras->left, bit = 128, byte = 0, outptr = ras->out_buffer; x <= ras->right; x ++, line ++)
+      {
+       if (*line <= threshold[x & 63][y])
+         byte |= bit;
+
+       if (bit == 1)
+       {
+         *outptr++ = byte;
+         byte      = 0;
+         bit       = 128;
+       }
+       else
+         bit >>= 1;
+      }
+    }
+
+    if (bit != 128)
+      *outptr++ = byte;
+
+    cupsRasterWritePixels(ras->ras, ras->out_buffer, ras->header.cupsBytesPerLine);
+  }
+  else
+    cupsRasterWritePixels(ras->ras, (unsigned char *)line, ras->header.cupsBytesPerLine);
+}
+
+
+/*
+ * 'write_fd()' - Write to a file/socket.
+ */
+
+static ssize_t                         /* O - Number of bytes written or -1 on error */
+write_fd(int                 *fd,      /* I - File descriptor */
+         const unsigned char *buffer,  /* I - Buffer */
+         size_t              bytes)    /* I - Number of bytes to write */
+{
+  ssize_t      temp,                   /* Temporary byte count */
+               total = 0;              /* Total bytes written */
+
+
+  while (bytes > 0)
+  {
+    if ((temp = write(*fd, buffer, bytes)) < 0)
+    {
+      if (errno == EINTR || errno == EAGAIN)
+        continue;
+      else
+        return (-1);
+    }
+
+    total  += temp;
+    bytes  -= (size_t)temp;
+    buffer += temp;
+  }
+
+  return (total);
+}