render

package
v3.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ErrRenderUnavailable = errors.New("PDF rendering temporarily unavailable")

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).

View Source
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

func ConvertRequestedSize(reqX, reqY, origW, origH, minRes int) (int, int)

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:

  1. If reqX == 0, use origW (0 means "keep original width"). If reqY == 0, use origH (0 means "keep original height").
  2. If reqX < minRes, clamp to minRes. If reqY < minRes, clamp to minRes.
  3. 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

func InitVips(appName string) error

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 PDFClose

func PDFClose()

PDFClose shuts down the PDFium pool. Call on graceful shutdown.

func PDFInit

func PDFInit(poolSize int, workerBin string) error

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 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:

  1. PDFium renders the page at pdfRasterDPI (72, Python parity) → *image.RGBA (no disk I/O).
  2. The raw RGBA pixels are fed directly into libvips via vips_image_new_from_memory — no PNG encode/decode round-trip.
  3. 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 / document.subprocess-pool-size) 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

func PDFSlice(semaphore chan struct{}, data []byte, firstPage, lastPage int) ([]byte, error)

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 / document.subprocess-pool-size) 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

func QualityToInt(quality string) int

QualityToInt maps the Python ImageQualityEnum string values to JPEG integer quality levels. Returns 50 (medium) for any unrecognised value.

func SanitizeOutputExtension

func SanitizeOutputExtension(ext string) string

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL