brotli

2017-08-11 petasis: I have created a small wrapper for the Brotli compression format: [1 ]

The code uses critcl:

package require Tcl 8.4
package require critcl 3.1

critcl::buildrequirement {
  package require critcl::class
}
# critcl::config language c++

critcl::license {Georgios Petasis} {BSD licensed}
critcl::summary {Brotli (de)compressor objects for Tcl.}
critcl::description {
    This package implements brotli (de)compression objects
    for Tcl.
}
critcl::subject brotli

::critcl::clibraries -lbrotlidec -lbrotlienc

critcl::class::define ::brotli {
  # include vector
  include brotli/decode.h
  include brotli/encode.h

  insvariable BrotliEncoderMode mode {} {
    instance->mode=(BrotliEncoderMode) BROTLI_DEFAULT_MODE;
  }
  insvariable int lgwin   {} {instance->lgwin   = BROTLI_DEFAULT_WINDOW;}
  insvariable int lgblock {} {instance->lgblock = -1;}
  insvariable int quality {} {instance->quality = BROTLI_DEFAULT_QUALITY;}

  method compress command {} {
    int length;
    size_t out_len;
    const uint8_t *input;
    BrotliEncoderState* state;
    uint8_t *output;
    int status;

    if (objc != 3) {
      Tcl_WrongNumArgs (interp, 2, objv, "binary_content_to_compress");
      return TCL_ERROR;
    }
    Tcl_ResetResult(interp);
    input = Tcl_GetByteArrayFromObj(objv[2], &length);
    if (!length) return TCL_OK;

    /* Get an estimation about the output buffer... */
    out_len = BrotliEncoderMaxCompressedSize(length);
    if (out_len == 0) {
      Tcl_SetResult(interp, "needed output buffer too large to encode input",
                            TCL_STATIC);
      return TCL_ERROR;
    }
    output = (uint8_t *) ckalloc(sizeof(uint8_t) * (out_len+1));
    if (output == NULL) {
      Tcl_SetResult(interp, "cannot allocate needed output buffer",
                            TCL_STATIC);
      return TCL_ERROR;
    }
    /* Compress... */
    status = BrotliEncoderCompress(instance->quality, instance->lgwin,
                                   instance->mode, length, input,
                                   &out_len, output);
    if (status) {
      Tcl_SetObjResult(interp,
          Tcl_NewByteArrayObj((unsigned char *) output, out_len));
      status =  TCL_OK;
    } else {
      Tcl_SetResult(interp, "cannot compress input", TCL_STATIC);
      status = TCL_ERROR;
    }
    ckfree((char *) output);
    return status;
  };# compress

  method decompress command {} {
    Tcl_DString output;
    int length;
    size_t input_len;
    const uint8_t *input;
    int status;

    if (objc != 3) {
      Tcl_WrongNumArgs (interp, 2, objv, "binary_content_to_decode");
      return TCL_ERROR;
    }
    Tcl_ResetResult(interp);
    input = Tcl_GetByteArrayFromObj(objv[2], &length);
    if (!length) return TCL_OK;

    Tcl_DStringInit(&output);
    BrotliDecoderState* state  = BrotliDecoderCreateInstance(0, 0, 0);
    BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
    input_len = length;
    while (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
      size_t available_out = 0;
      result = BrotliDecoderDecompressStream(state, &input_len, &input,
                                             &available_out, 0, 0);
      const uint8_t* next_out = BrotliDecoderTakeOutput(state,
                                                        &available_out);
      if (available_out != 0) {
        Tcl_DStringAppend(&output, next_out, available_out);
      }
    }
    status = result == BROTLI_DECODER_RESULT_SUCCESS;
    BrotliDecoderDestroyInstance(state);
    if (status) {
      Tcl_SetObjResult(interp,
          Tcl_NewByteArrayObj(Tcl_DStringValue(&output),
                              Tcl_DStringLength(&output)));
      status = TCL_OK;
    } else {
      BrotliDecoderErrorCode code = BrotliDecoderGetErrorCode(state);
      Tcl_SetResult(interp, (char *) BrotliDecoderErrorString(code),
                    TCL_DYNAMIC);
      status = TCL_ERROR;
    }
    Tcl_DStringFree(&output);
    return status;
  };# decompress

  method decoder_version command {} {
    if (objc != 2) {
      Tcl_WrongNumArgs (interp, 2, objv, NULL);
      return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(BrotliDecoderVersion()));
    return TCL_OK;
  };# decoder_version

  method encoder_version command {} {
    if (objc != 2) {
      Tcl_WrongNumArgs (interp, 2, objv, NULL);
      return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(BrotliEncoderVersion()));
    return TCL_OK;
  };# encoder_version

}

package provide brotli 1.0

You can generate the package by saving the code in a file named "brotli.tcl" and then execute:

  critcl -pkg brotli.tcl

See also