Documentation
¶
Overview ¶
Package render implements the image/PDF/document processing pipeline. It wraps libvips (via CGO) for image transformations and go-pdfium for PDF rendering. The benchmark reference is github.com/zextras/preview-bench-go-vips/main.go which this package is derived from.
Index ¶
- Variables
- func BuildSemaphore(n int) chan struct{}
- func CollaboraConvert(ctx context.Context, data []byte, langTag string, docsEditorURL string, ...) ([]byte, error)
- func ConvertRequestedSize(reqX, reqY, origW, origH, minRes int) (int, int)
- func DisableVipsCache()
- func ImageThumbnail(semaphore chan struct{}, data []byte, width, height int, ...) ([]byte, error)
- func InitVips(appName string) error
- func PDFClose()
- func PDFInit(poolSize int, workerBin string) error
- func PDFInitSingleThreadedForTests()
- func PDFRasterize(semaphore chan struct{}, data []byte, page, width, height int, ...) ([]byte, error)
- func PDFSlice(semaphore chan struct{}, data []byte, firstPage, lastPage int) ([]byte, error)
- func QualityToInt(quality string) int
- func SanitizeOutputExtension(ext string) string
- func SetImageMinRes(min int)
- func SetVipsConcurrency(n int)
Constants ¶
This section is empty.
Variables ¶
ErrRenderUnavailable is returned when the PDFium subprocess pool cannot provide a worker — e.g. all workers are busy (pool exhaustion), the startup timeout has elapsed, or a worker failed to start.
NOTE: this is a deliberate divergence from the old Python service, which had no pool-timeout concept. Callers should map this to HTTP 503 (Service Unavailable) to distinguish transient capacity problems from permanent document-level errors (HTTP 400).
var ImageMinRes = 80
ImageMinRes is the minimum resolution enforced by ConvertRequestedSize. It mirrors the Python IMAGE_MIN_RES constant (image_constants.minimum_resolution, default 80). Call render.SetImageMinRes(cfg.ImageMinimumResolution) once in main after config.Load() to pick up operator-configured values.
Functions ¶
func BuildSemaphore ¶
func BuildSemaphore(n int) chan struct{}
BuildSemaphore creates an in-flight semaphore channel of size n. Callers acquire a slot with `sem <- struct{}{}` and release with `<-sem`.
func CollaboraConvert ¶
func CollaboraConvert( ctx context.Context, data []byte, langTag string, docsEditorURL string, timeout time.Duration, ) ([]byte, error)
CollaboraConvert converts a document (any format LibreOffice/Collabora accepts) to the requested output extension by calling the Collabora Online convert-to endpoint.
docsEditorURL is the base URL of the convert-to endpoint, e.g.:
"http://127.78.0.6:20001/services/docs/editor/cool/convert-to"
The full request URL is: {docsEditorURL}/{outputExtension}?lang={langTag}
outputExtension is typically "pdf" for document-preview and "png" for document-thumbnail. The Python service sanitizes jpeg/JPEG/png/PNG → "png" before calling LibreOffice; callers should do the same.
On any error (HTTP, timeout, connection): returns (nil, error). The caller is responsible for mapping this to an appropriate HTTP status.
Retry behaviour: up to 2 retries with exponential backoff (0.5s, 1s) on transient HTTP 5xx errors and connection errors. The total time including retries is bounded by ctx.
func ConvertRequestedSize ¶
ConvertRequestedSize converts a requested (reqX, reqY) target dimension pair to safe non-zero values that libvips will accept, replicating exactly the Python service's _convert_requested_size_to_true_res_to_scale semantics (app/core/services/image_manipulation/image_manipulation.py).
Rules applied in order:
- If reqX == 0, use origW (0 means "keep original width"). If reqY == 0, use origH (0 means "keep original height").
- If reqX < minRes, clamp to minRes. If reqY < minRes, clamp to minRes.
- If origW < minRes and reqX/2 > origW, clamp reqX to minRes. If origH < minRes and reqY/2 > origH, clamp reqY to minRes.
The returned values are always >= 1 (and >= minRes when minRes >= 1). This prevents passing width=0 or height=0 to libvips, which causes:
"value 0 of type gint is invalid for property width/height"
origW/origH are the actual source image dimensions. minRes is the configured minimum resolution (IMAGE_MIN_RES, default 80).
func DisableVipsCache ¶
func DisableVipsCache()
DisableVipsCache turns off the libvips operation cache entirely. Without this, libvips caches every decoded tile in RAM and the process grows without bound under sustained traffic. Safe to call multiple times.
func ImageThumbnail ¶
func ImageThumbnail(
semaphore chan struct{},
data []byte,
width, height int,
outputFormat, quality, shape, cropMode string,
) ([]byte, error)
ImageThumbnail processes an image or SVG buffer and returns an encoded thumbnail/preview. It implements the full image pipeline described in the Python service spec:
- semaphore: pass a channel built with BuildSemaphore to cap concurrency; pass nil to run without a semaphore (not recommended in production).
- data: raw file bytes (JPEG, PNG, GIF, WebP, TIFF, SVG — anything libvips/librsvg can decode).
- width, height: target dimensions; 0 means "use original".
- outputFormat: "jpeg", "png", or "gif". GIF is encoded as PNG (libvips does not support animated GIF encode via this path).
- quality: "lowest", "low", "medium", "high", "highest".
- shape: "rounded" or "rectangular". Rounded thumbnails apply a circular alpha mask (PNG output is forced for rounded to preserve alpha).
- cropMode: "center" for cover-crop from centre (thumbnail path); "none" for scale-to-fit with transparent padding (preview crop=false path).
The "center" cropMode performs a COVER crop (fills the target box, may clip edges) using VIPS_INTERESTING_CENTRE. The "none" cropMode scales to fit within the target box (no cropping) and pads with transparent pixels.
NOTE: GIF animated frames are NOT supported in this path. Multi-frame GIFs are decoded to the first frame by vips_thumbnail_buffer.
NOTE: "rounded" shape forces PNG output regardless of outputFormat.
func InitVips ¶
InitVips initialises the libvips library and disables the operation cache to prevent unbounded RAM growth under concurrent load. It also applies VIPS_CONCURRENCY if set. Call once at process startup, before any other render function.
appName is used only for vips internal diagnostics (e.g. "carbonio-preview").
func PDFInit ¶
PDFInit initialises the multi_threaded PDFium subprocess pool. poolSize controls MinIdle/MaxIdle/MaxTotal (all set to the same value). workerBin is the absolute path to the carbonio-preview-pdfium-worker binary.
Call PDFClose on graceful shutdown.
func PDFInitSingleThreadedForTests ¶
func PDFInitSingleThreadedForTests()
PDFInitSingleThreadedForTests initialises the in-process single_threaded PDFium backend. For use in unit tests only — do NOT call in production code. Uses the CGO backend (links against libpdfium.so).
func PDFRasterize ¶
func PDFRasterize(
semaphore chan struct{},
data []byte,
page, width, height int,
outputFormat, quality, shape string,
) ([]byte, error)
PDFRasterize renders page `page` (0-indexed) of a PDF document to an encoded image and returns the encoded bytes.
The pipeline:
- PDFium renders the page at pdfRasterDPI (72, Python parity) → *image.RGBA (no disk I/O).
- The raw RGBA pixels are fed directly into libvips via vips_image_new_from_memory — no PNG encode/decode round-trip.
- libvips applies COVER resize + center-crop and encodes the result.
Concurrency model:
- semaphore (N_http / workers) gates handler-level processing; pass nil to skip gating (not recommended in production).
- The PDFium subprocess pool (N_pdf / pdf-workers) is the second gate: GetInstance blocks until a subprocess worker is free or the timeout fires. The http gate sits in front so the pool is never flooded.
outputFormat: "jpeg" or "png". quality: "lowest", "low", "medium", "high", "highest". shape: "rounded" or "rectangular" (rounded forces PNG output). Returns ErrRenderUnavailable when the pool cannot supply a worker (timeout, exhaustion, or worker start failure) — callers should map this to HTTP 503.
func PDFSlice ¶
PDFSlice extracts pages [firstPage, lastPage] from a PDF and returns the sliced PDF bytes. firstPage and lastPage are 1-indexed (matching the Python service spec). lastPage == 0 means "last page of the document".
Concurrency model:
- semaphore (N_http / workers) gates handler-level processing; pass nil to skip gating (not recommended in production).
- The PDFium subprocess pool (N_pdf / pdf-workers) is the second gate. The http gate sits in front so the pool is never flooded.
Invalid PDFs: returns (nil, error). Callers should map this to HTTP 400. Pool unavailability: returns (nil, ErrRenderUnavailable). Callers should map this to HTTP 503.
func QualityToInt ¶
QualityToInt maps the Python ImageQualityEnum string values to JPEG integer quality levels. Returns 50 (medium) for any unrecognised value.
func SanitizeOutputExtension ¶
SanitizeOutputExtension replicates the Python _sanitize_output_extension logic: JPEG/PNG (any case) are replaced with "png" before sending to LibreOffice. "pdf" and other extensions are passed through unchanged.
func SetImageMinRes ¶
func SetImageMinRes(min int)
SetImageMinRes sets the minimum resolution used by ConvertRequestedSize. It is NOT goroutine-safe; call it once at program startup, before any concurrent render calls.
func SetVipsConcurrency ¶
func SetVipsConcurrency(n int)
SetVipsConcurrency overrides the number of threads each VipsOperation may use. Passing 0 or a negative value is a no-op.
Types ¶
This section is empty.