mark2web/vendor/github.com/itchio/go-brotli/enc/encode.go
Sebastian Frank a781485c0a
Some checks failed
continuous-integration/drone/push Build is failing
no git submodules
2022-02-28 10:28:34 +01:00

193 lines
5.0 KiB
Go

// Package enc provides Brotli encoder bindings
package enc // import "github.com/itchio/go-brotli/enc"
/*
#cgo CFLAGS: -I${SRCDIR}/../include
#cgo LDFLAGS: -lm
// for memcpy
#include <string.h>
#include <brotli/encode.h>
void* encodeBrotliDictionary;
struct CompressStreamResult {
size_t bytes_consumed;
const uint8_t* output_data;
size_t output_data_size;
int success;
int has_more;
};
static struct CompressStreamResult CompressStream(
BrotliEncoderState* s, BrotliEncoderOperation op,
const uint8_t* data, size_t data_size) {
struct CompressStreamResult result;
size_t available_in = data_size;
const uint8_t* next_in = data;
size_t available_out = 0;
result.success = BrotliEncoderCompressStream(s, op,
&available_in, &next_in, &available_out, 0, 0) ? 1 : 0;
result.bytes_consumed = data_size - available_in;
result.output_data = 0;
result.output_data_size = 0;
if (result.success) {
result.output_data = BrotliEncoderTakeOutput(s, &result.output_data_size);
}
result.has_more = BrotliEncoderHasMoreOutput(s) ? 1 : 0;
return result;
}
*/
import "C"
import (
"bytes"
"errors"
"io"
"runtime"
"unsafe"
"github.com/itchio/go-brotli/common"
)
// Errors which may be returned when encoding
var (
errWriterClosed = errors.New("brotli-go: Writer is closed")
errEncode = errors.New("brotli-go: encode error")
)
func init() {
// Set up the default dictionary from the data in the shared package
C.encodeBrotliDictionary = unsafe.Pointer(common.GetDictionary())
}
// BrotliWriterOptions configures BrotliWriter
type BrotliWriterOptions struct {
// Quality controls the compression-speed vs compression-density trade-offs.
// The higher the quality, the slower the compression. Range is 0 to 11.
Quality int
// LGWin is the base 2 logarithm of the sliding window size.
// Range is 10 to 24. 0 indicates automatic configuration based on Quality.
LGWin int
}
// CompressBuffer compresses a single block of data.
// Default options are used if options is nil.
// Returns the brotli-compressed version of the data, or an error.
func CompressBuffer(input []byte, options *BrotliWriterOptions) ([]byte, error) {
buf := new(bytes.Buffer)
w := NewBrotliWriter(buf, options)
_, err := w.Write(input)
if err != nil {
return nil, err
}
err = w.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// BrotliWriter implements the io.Writer interface, compressing the stream
// to an output Writer using Brotli.
type BrotliWriter struct {
dst io.Writer
state *C.BrotliEncoderState
buf, encoded []byte
}
// NewBrotliWriter instantiates a new BrotliWriter that writes with dst,
// and configured with the passed options (which may be nil)
func NewBrotliWriter(dst io.Writer, options *BrotliWriterOptions) *BrotliWriter {
state := C.BrotliEncoderCreateInstance(nil, nil, nil)
w := &BrotliWriter{
dst: dst,
state: state,
}
runtime.SetFinalizer(w, brotliWriterFinalizer)
if options != nil {
C.BrotliEncoderSetParameter(state, C.BROTLI_PARAM_QUALITY, C.uint32_t(options.Quality))
if options.LGWin > 0 {
C.BrotliEncoderSetParameter(state, C.BROTLI_PARAM_LGWIN, C.uint32_t(options.LGWin))
}
}
return w
}
func brotliWriterFinalizer(bw *BrotliWriter) {
// not a terribly good idea (swallows errors),
// but at least avoids memory leaks?
bw.Close()
}
func (w *BrotliWriter) Write(buffer []byte) (int, error) {
return w.writeChunk(buffer, C.BROTLI_OPERATION_PROCESS)
}
// Close cleans up the resources used by the Brotli encoder for this
// stream. If the output buffer is an io.Closer, it will also be closed.
func (w *BrotliWriter) Close() error {
// If stream is already closed, it is reported by `writeChunk`.
_, err := w.writeChunk(nil, C.BROTLI_OPERATION_FINISH)
// C-Brotli tolerates `nil` pointer here.
C.BrotliEncoderDestroyInstance(w.state)
w.state = nil
if err != nil {
return err
}
if v, ok := w.dst.(io.Closer); ok {
return v.Close()
}
return nil
}
func (w *BrotliWriter) writeChunk(p []byte, op C.BrotliEncoderOperation) (n int, err error) {
if w.state == nil {
return 0, errWriterClosed
}
for {
var data *C.uint8_t
if len(p) != 0 {
data = (*C.uint8_t)(&p[0])
}
result := C.CompressStream(w.state, op, data, C.size_t(len(p)))
if result.success == 0 {
return n, errEncode
}
p = p[int(result.bytes_consumed):]
n += int(result.bytes_consumed)
length := int(result.output_data_size)
if length != 0 {
// It is a workaround for non-copying-wrapping of native memory.
// C-encoder never pushes output block longer than ((2 << 25) + 502).
// TODO: use natural wrapper, when it becomes available, see
// https://golang.org/issue/13656.
output := (*[1 << 30]byte)(unsafe.Pointer(result.output_data))[:length:length]
_, err = w.dst.Write(output)
if err != nil {
return n, err
}
}
if len(p) == 0 && result.has_more == 0 {
return n, nil
}
}
}
// internal cgo utilities
func toC(array []byte) *C.uint8_t {
return (*C.uint8_t)(unsafe.Pointer(&array[0]))
}