Unified · Split
| @@ -47,7 +47,13 @@ buffree(Buffer *b) · 46 unmodified lines | |||
| 47 | int | 47 | int |
| 48 | bufeof(Buffer *b) | 48 | bufeof(Buffer *b) |
| 49 | { | 49 | { |
| 50 | return b->off == b->sz; | 50 | return bufleft(b) == 0; |
| 51 | } | ||
| 52 | |||
| 53 | int | ||
| 54 | bufleft(Buffer *b) | ||
| 55 | { | ||
| 56 | return b->sz - b->off; | ||
| 51 | } | 57 | } |
| 52 | 58 | ||
| 53 | uchar * | 59 | uchar * |
| @@ -4,6 +4,69 @@ · 3 unmodified lines | |||
| 4 | #include <flate.h> | 4 | #include <flate.h> |
| 5 | #include "pdf.h" | 5 | #include "pdf.h" |
| 6 | 6 | ||
| 7 | typedef struct FlateParms FlateParms; | ||
| 8 | |||
| 9 | struct FlateParms { | ||
| 10 | int predictor; | ||
| 11 | int columns; | ||
| 12 | }; | ||
| 13 | |||
| 14 | static uchar | ||
| 15 | paeth(uchar a, uchar b, uchar c) | ||
| 16 | { | ||
| 17 | int p, pa, pb, pc; | ||
| 18 | |||
| 19 | p = a + b - c; | ||
| 20 | pa = abs(p - a); | ||
| 21 | pb = abs(p - b); | ||
| 22 | pc = abs(p - c); | ||
| 23 | |||
| 24 | if(pa <= pb && pa <= pc) | ||
| 25 | return a; | ||
| 26 | return pb <= pc ? b : c; | ||
| 27 | } | ||
| 28 | |||
| 29 | static int | ||
| 30 | pngunpredict(int pred, uchar *buf, uchar *up, int len) | ||
| 31 | { | ||
| 32 | int i; | ||
| 33 | |||
| 34 | switch(pred){ | ||
| 35 | case 0: /* None */ | ||
| 36 | break; | ||
| 37 | |||
| 38 | case 1: /* Sub */ | ||
| 39 | for(i = 1; i < len; ++i) | ||
| 40 | buf[i] += buf[i-1]; | ||
| 41 | break; | ||
| 42 | |||
| 43 | case 2: /* Up */ | ||
| 44 | for(i = 0; i < len; ++i) | ||
| 45 | buf[i] += up[i]; | ||
| 46 | break; | ||
| 47 | |||
| 48 | case 3: /* Average */ | ||
| 49 | buf[0] += up[0]/2; | ||
| 50 | for(i = 1; i < len; ++i) | ||
| 51 | buf[i] += (buf[i-1]+up[i])/2; | ||
| 52 | break; | ||
| 53 | |||
| 54 | case 4: /* Paeth */ | ||
| 55 | buf[0] += paeth(0, up[0], 0); | ||
| 56 | for(i = 0; i < len; ++i) | ||
| 57 | buf[i] += paeth(buf[i-1], up[i], up[i-1]); | ||
| 58 | break; | ||
| 59 | |||
| 60 | /* FIXME 5 optimum??? */ | ||
| 61 | |||
| 62 | default: | ||
| 63 | werrstr("unsupported predictor %d", pred); | ||
| 64 | return -1; | ||
| 65 | } | ||
| 66 | |||
| 67 | return 0; | ||
| 68 | } | ||
| 69 | |||
| 7 | static int | 70 | static int |
| 8 | bw(void *aux, void *d, int n) | 71 | bw(void *aux, void *d, int n) |
| 9 | { | 72 | { |
| @@ -21,9 +84,11 @@ bget(void *aux) · 11 unmodified lines | |||
| 21 | int | 84 | int |
| 22 | fFlate(void *aux, Buffer *bi, Buffer *bo) | 85 | fFlate(void *aux, Buffer *bi, Buffer *bo) |
| 23 | { | 86 | { |
| 24 | int r; | 87 | int r, i, rows, n; |
| 88 | FlateParms *fp; | ||
| 89 | uchar *x, *y, *zero; | ||
| 25 | 90 | ||
| 26 | USED(aux); | 91 | fp = aux; |
| 27 | 92 | ||
| 28 | do{ | 93 | do{ |
| 29 | r = inflatezlib(bo, bw, bi, bget); | 94 | r = inflatezlib(bo, bw, bi, bget); |
| @@ -34,5 +99,57 @@ fFlate(void *aux, Buffer *bi, Buffer *bo) · 4 unmodified lines | |||
| 34 | return -1; | 99 | return -1; |
| 35 | } | 100 | } |
| 36 | 101 | ||
| 102 | /* 7.4.4.4 LZW and Flate predictor functions */ | ||
| 103 | if(fp->predictor >= 10 && fp->columns > 0){ | ||
| 104 | n = fp->columns + 1; | ||
| 105 | rows = bo->sz/n; | ||
| 106 | x = bo->b; | ||
| 107 | y = bo->b; | ||
| 108 | zero = mallocz(fp->columns, 1); | ||
| 109 | for(i = r = 0; i < rows && r == 0; i++, x += n, y += n) | ||
| 110 | r = pngunpredict(x[0], x+1, i < 1 ? zero : y+1-n, fp->columns); | ||
| 111 | free(zero); | ||
| 112 | |||
| 113 | x = bo->b; | ||
| 114 | y = bo->b+1; | ||
| 115 | for(i = 0; i < rows; i++, x += fp->columns, y += n) | ||
| 116 | memmove(x, y, fp->columns); | ||
| 117 | bo->sz -= rows; | ||
| 118 | } | ||
| 119 | |||
| 120 | return r; | ||
| 121 | } | ||
| 122 | |||
| 123 | int | ||
| 124 | openFlate(Filter *f, Object *o) | ||
| 125 | { | ||
| 126 | Object *parms; | ||
| 127 | FlateParms *fp; | ||
| 128 | int predictor, columns; | ||
| 129 | |||
| 130 | parms = dictget(o, "DecodeParms"); | ||
| 131 | predictor = dictint(parms, "Predictor"); | ||
| 132 | columns = dictint(parms, "Columns"); | ||
| 133 | if((predictor >= 2 && predictor < 10) || predictor >= 15){ | ||
| 134 | werrstr("unsupported flate predictor %d", predictor); | ||
| 135 | return -1; | ||
| 136 | } | ||
| 137 | if(predictor >= 10 && predictor <= 15 && columns < 1){ | ||
| 138 | werrstr("invalid columns %d for predictor %d", columns, predictor); | ||
| 139 | return -1; | ||
| 140 | } | ||
| 141 | |||
| 142 | if((fp = malloc(sizeof(FlateParms))) == nil) | ||
| 143 | return -1; | ||
| 144 | fp->predictor = predictor; | ||
| 145 | fp->columns = columns; | ||
| 146 | f->aux = fp; | ||
| 147 | |||
| 37 | return 0; | 148 | return 0; |
| 38 | } | 149 | } |
| 150 | |||
| 151 | void | ||
| 152 | closeFlate(Filter *f) | ||
| 153 | { | ||
| 154 | free(f->aux); | ||
| 155 | } | ||
| @@ -5,15 +5,9 @@ · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | /* 7.4 Filters */ | 6 | /* 7.4 Filters */ |
| 7 | 7 | ||
| 8 | struct Filter { | ||
| 9 | char *name; | ||
| 10 | int (*readall)(void *aux, Buffer *bi, Buffer *bo); | ||
| 11 | int (*open)(Filter *f, Object *o); | ||
| 12 | void (*close)(Filter *f); | ||
| 13 | void *aux; | ||
| 14 | }; | ||
| 15 | |||
| 16 | int fFlate(void *aux, Buffer *bi, Buffer *bo); | 8 | int fFlate(void *aux, Buffer *bi, Buffer *bo); |
| 9 | int openFlate(Filter *f, Object *o); | ||
| 10 | void closeFlate(Filter *f); | ||
| 17 | 11 | ||
| 18 | static Filter filters[] = { | 12 | static Filter filters[] = { |
| 19 | {"ASCII85Decode", nil, nil, nil}, | 13 | {"ASCII85Decode", nil, nil, nil}, |
| @@ -21,7 +15,7 @@ static Filter filters[] = { · 1 unmodified line | |||
| 21 | {"CCITTFaxDecode", nil, nil, nil}, | 15 | {"CCITTFaxDecode", nil, nil, nil}, |
| 22 | {"CryptDecode", nil, nil, nil}, | 16 | {"CryptDecode", nil, nil, nil}, |
| 23 | {"DCTDecode", nil, nil, nil}, | 17 | {"DCTDecode", nil, nil, nil}, |
| 24 | {"FlateDecode", fFlate}, | 18 | {"FlateDecode", fFlate, openFlate, closeFlate}, |
| 25 | {"JBIG2Decode", nil, nil, nil}, | 19 | {"JBIG2Decode", nil, nil, nil}, |
| 26 | {"JPXDecode", nil, nil, nil}, | 20 | {"JPXDecode", nil, nil, nil}, |
| 27 | {"LZWDecode", nil, nil, nil}, | 21 | {"LZWDecode", nil, nil, nil}, |
| @@ -15,6 +15,12 @@ static char *otypes[] = { · 14 unmodified lines | |||
| 15 | [Oindir] = "indir", | 15 | [Oindir] = "indir", |
| 16 | }; | 16 | }; |
| 17 | 17 | ||
| 18 | static char *xtypes[] = { | ||
| 19 | [Xusual] = "usual", | ||
| 20 | [Xuncompressed] = "uncompressed", | ||
| 21 | [Xcompressed] = "compressed", | ||
| 22 | }; | ||
| 23 | |||
| 18 | Object null = { | 24 | Object null = { |
| 19 | .type = Onull, | 25 | .type = Onull, |
| 20 | }; | 26 | }; |
| @@ -32,6 +38,24 @@ Tfmt(Fmt *f) · 11 unmodified lines | |||
| 32 | return fmtprint(f, "%s", otypes[o->type]); | 38 | return fmtprint(f, "%s", otypes[o->type]); |
| 33 | } | 39 | } |
| 34 | 40 | ||
| 41 | int | ||
| 42 | ⊗fmt(Fmt *f) | ||
| 43 | { | ||
| 44 | Xref x; | ||
| 45 | |||
| 46 | x = va_arg(f->args, Xref); | ||
| 47 | |||
| 48 | switch(x.type){ | ||
| 49 | case Xusual: | ||
| 50 | return fmtprint(f, "<%s id=%d gen=%d off=%d>", xtypes[x.type], x.id, x.gen, x.off); | ||
| 51 | case Xuncompressed: | ||
| 52 | return fmtprint(f, "<%s gen=%d off=%d>", xtypes[x.type], x.gen, x.off); | ||
| 53 | case Xcompressed: | ||
| 54 | return fmtprint(f, "<%s id=%d objnum=%d>", xtypes[x.type], x.id, x.objnum); | ||
| 55 | } | ||
| 56 | return -1; | ||
| 57 | } | ||
| 58 | |||
| 35 | int | 59 | int |
| 36 | isws(int c) | 60 | isws(int c) |
| 37 | { | 61 | { |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | #include "pdf.h" | 5 | #include "pdf.h" |
| 6 | 6 | ||
| 7 | int Tfmt(Fmt *f); | 7 | int Tfmt(Fmt *f); |
| 8 | int ⊗fmt(Fmt *f); | ||
| 8 | 9 | ||
| 9 | /* | 10 | /* |
| 10 | * pre-1.5 xref section reader | 11 | * pre-1.5 xref section reader |
| @@ -41,6 +42,8 @@ xrefread(Pdf *pdf, int xref0, int nxref) · 30 unmodified lines | |||
| 41 | } | 42 | } |
| 42 | xref.id = xref0 + i; | 43 | xref.id = xref0 + i; |
| 43 | xref.off = strtoul(e, nil, 10); | 44 | xref.off = strtoul(e, nil, 10); |
| 45 | /* FIXME xref.gen */ | ||
| 46 | xref.type = Xusual; | ||
| 44 | 47 | ||
| 45 | /* search in already existing xrefs, update if found */ | 48 | /* search in already existing xrefs, update if found */ |
| 46 | for(j = 0; j < pdf->nxref; j++){ | 49 | for(j = 0; j < pdf->nxref; j++){ |
| @@ -97,6 +100,104 @@ err: · 50 unmodified lines | |||
| 97 | return -1; | 100 | return -1; |
| 98 | } | 101 | } |
| 99 | 102 | ||
| 103 | static int | ||
| 104 | getint(uchar *b, int sz, int dflt) | ||
| 105 | { | ||
| 106 | int x, i; | ||
| 107 | |||
| 108 | if(sz == 0) | ||
| 109 | return dflt; | ||
| 110 | x = 0; | ||
| 111 | for(i = 0; i < sz; i++) | ||
| 112 | x = x<<8 | b[i]; | ||
| 113 | |||
| 114 | return x; | ||
| 115 | } | ||
| 116 | |||
| 117 | /* 7.5.8.3 */ | ||
| 118 | static int | ||
| 119 | xrefstreamread(Pdf *pdf) | ||
| 120 | { | ||
| 121 | Object *o; | ||
| 122 | Stream *s; | ||
| 123 | Xref *x; | ||
| 124 | uchar buf[32]; | ||
| 125 | int w[8], nw, i, c, n, nxref, newnxref, prev, extra; | ||
| 126 | |||
| 127 | s = nil; | ||
| 128 | if((o = pdfobj(pdf, pdf->bio)) == nil){ | ||
| 129 | werrstr("xref stream obj: %r"); | ||
| 130 | goto err; | ||
| 131 | } | ||
| 132 | if((prev = dictint(o, "Prev")) > 0){ | ||
| 133 | if(Bseek(pdf->bio, prev, 0) != prev){ | ||
| 134 | werrstr("xref stream prev seek failed"); | ||
| 135 | goto err; | ||
| 136 | } | ||
| 137 | if(xrefstreamread(pdf) != 0){ | ||
| 138 | pdfobjfree(o); | ||
| 139 | return -1; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | if((s = streamopen(o)) == nil){ | ||
| 143 | werrstr("failed to stream xref: %r"); | ||
| 144 | goto err; | ||
| 145 | } | ||
| 146 | if((nw = dictints(o, "W", w, nelem(w))) < 3 || nw >= nelem(w)){ | ||
| 147 | werrstr("nW=%d", nw); | ||
| 148 | goto err; | ||
| 149 | } | ||
| 150 | |||
| 151 | for(n = i = 0; i < nw; i++) | ||
| 152 | n += w[i]; /* size of each element. w[i] MAY be 0 */ | ||
| 153 | if(n > sizeof(buf)){ | ||
| 154 | werrstr("W is beyond imaginable: %d bytes", n); | ||
| 155 | goto err; | ||
| 156 | } | ||
| 157 | if((nxref = streamsize(s)/n) < 1){ | ||
| 158 | werrstr("no xref elements in the stream"); | ||
| 159 | goto err; | ||
| 160 | } | ||
| 161 | extra = streamsize(s) % (nxref*n); | ||
| 162 | if(extra != 0) | ||
| 163 | fprint(2, "extra %d bytes in xref stream", extra); | ||
| 164 | |||
| 165 | newnxref = pdf->nxref + nxref; | ||
| 166 | if((x = realloc(pdf->xref, newnxref*sizeof(Xref))) == nil) | ||
| 167 | goto err; | ||
| 168 | pdf->xref = x; | ||
| 169 | x += pdf->nxref; | ||
| 170 | while(Bread(s->bio, buf, n) == n){ /* stop on short read or error */ | ||
| 171 | c = getint(buf, w[0], 1); /* default type is 1 */ | ||
| 172 | if(c == 1){ /* not compressed */ | ||
| 173 | x->off = getint(buf+w[0], w[1], 0); | ||
| 174 | x->gen = getint(buf+w[0]+w[1], w[2], 0); | ||
| 175 | x->type = Xuncompressed; | ||
| 176 | pdf->nxref++; | ||
| 177 | fprint(2, "xref %⊗\n", *x); | ||
| 178 | x++; | ||
| 179 | }else if(c == 2){ /* compressed */ | ||
| 180 | x->objnum = getint(buf+w[0], w[1], 0); | ||
| 181 | x->id = getint(buf+w[0]+w[1], w[2], 0); | ||
| 182 | x->type = Xcompressed; | ||
| 183 | pdf->nxref++; | ||
| 184 | fprint(2, "xref %⊗\n", *x); | ||
| 185 | x++; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | streamclose(s); | ||
| 190 | pdf->root = pdfref(dictget(o, "Root")); | ||
| 191 | pdf->info = pdfref(dictget(o, "Info")); | ||
| 192 | pdfobjfree(o); | ||
| 193 | |||
| 194 | return 0; | ||
| 195 | err: | ||
| 196 | streamclose(s); | ||
| 197 | pdfobjfree(o); | ||
| 198 | return -1; | ||
| 199 | } | ||
| 200 | |||
| 100 | Pdf * | 201 | Pdf * |
| 101 | pdfopen(Biobuf *b) | 202 | pdfopen(Biobuf *b) |
| 102 | { | 203 | { |
| @@ -106,10 +207,10 @@ pdfopen(Biobuf *b) · 3 unmodified lines | |||
| 106 | int xref0; /* 7.5.4 xref subsection first object number */ | 207 | int xref0; /* 7.5.4 xref subsection first object number */ |
| 107 | int nxref; /* 7.5.4 xref subsection number of objects */ | 208 | int nxref; /* 7.5.4 xref subsection number of objects */ |
| 108 | int xreftb; /* 7.5.4 xref table offset from the beginning of the file */ | 209 | int xreftb; /* 7.5.4 xref table offset from the beginning of the file */ |
| 109 | int i, n, off, w[3]; | 210 | int i, n, off; |
| 110 | Stream *stream; | ||
| 111 | 211 | ||
| 112 | fmtinstall('T', Tfmt); | 212 | fmtinstall('T', Tfmt); |
| 213 | fmtinstall(L'⊗', ⊗fmt); | ||
| 113 | 214 | ||
| 114 | o = nil; | 215 | o = nil; |
| 115 | if((pdf = calloc(1, sizeof(*pdf))) == nil) | 216 | if((pdf = calloc(1, sizeof(*pdf))) == nil) |
| @@ -181,22 +282,14 @@ badxref: · 65 unmodified lines | |||
| 181 | goto err; | 282 | goto err; |
| 182 | } | 283 | } |
| 183 | }else if(isdigit(tmp[0])){ /* could be 7.5.8 xref stream (since PDF 1.5) */ | 284 | }else if(isdigit(tmp[0])){ /* could be 7.5.8 xref stream (since PDF 1.5) */ |
| 184 | Bseek(b, xreftb, 0); | 285 | if(Bseek(b, xreftb, 0) != xreftb) |
| 185 | if((o = pdfobj(pdf, b)) == nil || (stream = streamopen(o)) == nil){ | ||
| 186 | werrstr("failed to stream xref: %r"); | ||
| 187 | goto badxref; | ||
| 188 | } | ||
| 189 | if(dictints(o, "W", w, nelem(w)) != 3){ | ||
| 190 | werrstr("W isn't 3 elements"); | ||
| 191 | goto badxref; | 286 | goto badxref; |
| 192 | } | 287 | if(xrefstreamread(pdf) != 0) |
| 193 | streamclose(stream); | 288 | goto err; |
| 194 | pdf->root = dictget(o, "Root"); | ||
| 195 | pdf->info = dictget(o, "Info"); | ||
| 196 | } | 289 | } |
| 197 | 290 | ||
| 198 | /* root is required, info is optional */ | 291 | /* root is required, info is optional */ |
| 199 | if(pdf->root == nil){ | 292 | if(pdf->root == &null){ |
| 200 | werrstr("no root"); | 293 | werrstr("no root"); |
| 201 | goto err; | 294 | goto err; |
| 202 | } | 295 | } |
| @@ -8,6 +8,10 @@ enum { · 7 unmodified lines | |||
| 8 | Ostream, /* 7.3.8 */ | 8 | Ostream, /* 7.3.8 */ |
| 9 | Onull, /* 7.3.9 */ | 9 | Onull, /* 7.3.9 */ |
| 10 | Oindir, /* 7.3.10 */ | 10 | Oindir, /* 7.3.10 */ |
| 11 | |||
| 12 | Xusual = 0, | ||
| 13 | Xuncompressed, | ||
| 14 | Xcompressed, | ||
| 11 | }; | 15 | }; |
| 12 | 16 | ||
| 13 | typedef struct Buffer Buffer; | 17 | typedef struct Buffer Buffer; |
| @@ -19,7 +23,8 @@ typedef struct Stream Stream; · 5 unmodified lines | |||
| 19 | typedef struct Xref Xref; | 23 | typedef struct Xref Xref; |
| 20 | #pragma incomplete Filter | 24 | #pragma incomplete Filter |
| 21 | 25 | ||
| 22 | #pragma varargck type "T" Object * | 26 | #pragma varargck type "T" Object* |
| 27 | #pragma varargck type "⊗" Xref | ||
| 23 | 28 | ||
| 24 | struct Buffer { | 29 | struct Buffer { |
| 25 | uchar *b; | 30 | uchar *b; |
| @@ -30,6 +35,14 @@ struct Buffer { · 4 unmodified lines | |||
| 30 | int eof; | 35 | int eof; |
| 31 | }; | 36 | }; |
| 32 | 37 | ||
| 38 | struct Filter { | ||
| 39 | char *name; | ||
| 40 | int (*readall)(void *aux, Buffer *bi, Buffer *bo); | ||
| 41 | int (*open)(Filter *f, Object *o); | ||
| 42 | void (*close)(Filter *f); | ||
| 43 | void *aux; | ||
| 44 | }; | ||
| 45 | |||
| 33 | struct Object { | 46 | struct Object { |
| 34 | int type; | 47 | int type; |
| 35 | int ref; | 48 | int ref; |
| @@ -83,8 +96,12 @@ struct Pdf { · 47 unmodified lines | |||
| 83 | 96 | ||
| 84 | struct Xref { | 97 | struct Xref { |
| 85 | u32int id; | 98 | u32int id; |
| 86 | u32int off; | 99 | union{ |
| 100 | u32int off; | ||
| 101 | u32int objnum; | ||
| 102 | }; | ||
| 87 | u16int gen; | 103 | u16int gen; |
| 104 | u16int type; | ||
| 88 | }; | 105 | }; |
| 89 | 106 | ||
| 90 | struct Stream { | 107 | struct Stream { |
| @@ -140,6 +157,7 @@ Object *dictdict(Object *o, char *name); · 49 unmodified lines | |||
| 140 | int dictints(Object *o, char *name, int *el, int nel); | 157 | int dictints(Object *o, char *name, int *el, int nel); |
| 141 | 158 | ||
| 142 | Stream *streamopen(Object *o); | 159 | Stream *streamopen(Object *o); |
| 160 | int streamsize(Stream *s); | ||
| 143 | void streamclose(Stream *s); | 161 | void streamclose(Stream *s); |
| 144 | 162 | ||
| 145 | Filter *filteropen(char *name, Object *o); | 163 | Filter *filteropen(char *name, Object *o); |
| @@ -149,6 +167,7 @@ void filterclose(Filter *f); · 3 unmodified lines | |||
| 149 | void bufinit(Buffer *b, uchar *d, int sz); | 167 | void bufinit(Buffer *b, uchar *d, int sz); |
| 150 | void buffree(Buffer *b); | 168 | void buffree(Buffer *b); |
| 151 | int bufeof(Buffer *b); | 169 | int bufeof(Buffer *b); |
| 170 | int bufleft(Buffer *b); | ||
| 152 | uchar *bufdata(Buffer *b, int *sz); | 171 | uchar *bufdata(Buffer *b, int *sz); |
| 153 | int bufreadn(Buffer *b, Biobuf *bio, int sz); | 172 | int bufreadn(Buffer *b, Biobuf *bio, int sz); |
| 154 | int bufput(Buffer *b, uchar *d, int sz); | 173 | int bufput(Buffer *b, uchar *d, int sz); |
| @@ -57,6 +57,8 @@ streamopen(Object *o) · 56 unmodified lines | |||
| 57 | buffree(&x); | 57 | buffree(&x); |
| 58 | goto err; | 58 | goto err; |
| 59 | } | 59 | } |
| 60 | if(!bufeof(&b)) | ||
| 61 | fprint(2, "buffer has %d bytes left\n", bufleft(&b)); | ||
| 60 | buffree(&b); | 62 | buffree(&b); |
| 61 | b = x; | 63 | b = x; |
| 62 | } | 64 | } |
| @@ -79,9 +81,18 @@ err: · 16 unmodified lines | |||
| 79 | return nil; | 81 | return nil; |
| 80 | } | 82 | } |
| 81 | 83 | ||
| 84 | int | ||
| 85 | streamsize(Stream *s) | ||
| 86 | { | ||
| 87 | return bufleft(&s->buf); | ||
| 88 | } | ||
| 89 | |||
| 82 | void | 90 | void |
| 83 | streamclose(Stream *s) | 91 | streamclose(Stream *s) |
| 84 | { | 92 | { |
| 93 | if(s == nil) | ||
| 94 | return; | ||
| 95 | |||
| 85 | buffree(&s->buf); | 96 | buffree(&s->buf); |
| 86 | Bterm(s->bio); | 97 | Bterm(s->bio); |
| 87 | free(s); | 98 | free(s); |