pdf

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Jul 1, 2026 License: MPL-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package pdf converts bracket XLSX workbooks into grouped, print-ready PDFs.

The pipeline mirrors square_prep/lc2026/xlsx_to_pdf.py: each XLSX is rendered to a full PDF by LibreOffice (soffice) headless, the per-sheet bookmarks LibreOffice emits are read to map sheet names to page ranges, and the wanted sheets are extracted and merged into grouped output PDFs.

LibreOffice (soffice) is a runtime dependency. It is NOT bundled. Callers detect availability with LocateSoffice and surface an actionable error when it is absent rather than failing deep in the pipeline.

Index

Constants

This section is empty.

Variables

View Source
var ErrSofficeNotFound = errors.New("LibreOffice (soffice) not found")

ErrSofficeNotFound is returned when no LibreOffice binary can be located. Callers map this to an actionable user-facing message (HTTP 503 in the web app) telling the operator to install LibreOffice or pull the -pdf image.

View Source
var Groups = []Group{
	{
		Type:        "registration",
		Output:      "print_registration.pdf",
		Description: "Registration (data sheets)",
		Sheets:      []sheetSpec{exact(helper.SheetData)},
	},
	{
		Type:        "names",
		Output:      "print_names_to_print.pdf",
		Description: "Names to Print",
		Sheets:      []sheetSpec{prefix(helper.SheetNamesToPrint)},
		InsertTitle: true,
		A3Landscape: true,
	},
	{
		Type:              "tags",
		Output:            "print_tags.pdf",
		Description:       "Tags",
		Sheets:            []sheetSpec{prefix(helper.SheetTags)},
		InsertTitle:       true,
		SkipTeamWorkbooks: true,
	},
	{
		Type:        "pools-trees",
		Output:      "print_pools_and_trees.pdf",
		Description: "Pool Draw + Trees (participant booklet)",
		Sheets:      []sheetSpec{exact(helper.SheetPoolDraw), prefix(helper.SheetTree)},
		PageNumbers: true,
	},
	{
		Type:        "full-bracket",
		Output:      "print_full_bracket.pdf",
		Description: "Full bracket (pools, matches, trees)",
		Sheets: []sheetSpec{
			exact(helper.SheetPoolDraw),
			exact(helper.SheetPoolMatches),
			exact(helper.SheetEliminationMatches),
			prefix(helper.SheetTree),
		},
		PageNumbers: true,
	},
}

Groups is the canonical set of output PDFs, mirroring the PDF_GROUPS config in square_prep/lc2026/xlsx_to_pdf.py. Sheet selectors use the Sheet* constants from internal/helper/constants.go (compile-time drift safety). Names/Tags/Tree match by prefix because the generator splits them into suffixed physical sheets (e.g. "Names to Print A", "Tree 1").

Functions

func ExtractPages

func ExtractPages(srcPath string, ranges []SheetRange, outPath string) error

ExtractPages writes a new PDF at outPath containing only the given inclusive 1-indexed page ranges from srcPath, in the order supplied.

func LocateSoffice

func LocateSoffice() (string, error)

LocateSoffice resolves the LibreOffice binary using, in order:

  1. $LIBREOFFICE_PATH (if set and executable)
  2. "soffice" on $PATH
  3. a platform-specific list of well-known install locations

It returns ErrSofficeNotFound (wrapped) when none resolve.

func MergePDFs

func MergePDFs(inPaths []string, outPath string) error

MergePDFs concatenates the given PDFs (in order) into a single outPath.

func PageCount

func PageCount(pdfPath string) (int, error)

PageCount returns the number of pages in a PDF.

func StampPageNumbers

func StampPageNumbers(inPath, outPath string) error

StampPageNumbers writes a copy of inPath to outPath with a "N / M" footer stamped on every page. It returns an error if the PDF has no pages.

Types

type Converter

type Converter struct {
	// contains filtered or unexported fields
}

Converter runs soffice headless conversions. The zero value is not usable; construct with NewConverter.

func NewConverter

func NewConverter() (*Converter, error)

NewConverter locates LibreOffice and returns a Converter. It returns ErrSofficeNotFound (wrapped) when no binary is available, so callers can branch on errors.Is(err, ErrSofficeNotFound).

func (*Converter) ConvertToPDF

func (c *Converter) ConvertToPDF(ctx context.Context, srcPath, outDir string) (string, error)

ConvertToPDF renders srcPath (an XLSX or HTML file) to a PDF in outDir using soffice headless, and returns the resulting PDF path. Calls are serialized process-wide and bounded by convertTimeout. A dedicated, isolated user profile is passed via -env:UserInstallation so a concurrent desktop LibreOffice (or another server) does not collide on the shared profile.

func (*Converter) SofficePath

func (c *Converter) SofficePath() string

SofficePath returns the resolved soffice binary path.

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator drives the XLSX→PDF pipeline. Construct with NewGenerator.

func NewGenerator

func NewGenerator() (*Generator, error)

NewGenerator locates LibreOffice and returns a Generator, or ErrSofficeNotFound (wrapped) when soffice is unavailable.

func (*Generator) GenerateAll

func (g *Generator) GenerateAll(ctx context.Context, sources []SourceWorkbook, outDir string) (map[string]string, error)

GenerateAll produces every group's PDF from the given workbooks into outDir, returning a map of group type → output path. Each workbook is converted to a full PDF exactly once and reused across groups. A group that yields no pages (e.g. tags when only team workbooks are present) is skipped and omitted from the result map. Output files are written atomically (temp file → rename).

func (*Generator) GenerateGroups

func (g *Generator) GenerateGroups(ctx context.Context, types []string, sources []SourceWorkbook, outDir string) (map[string]string, error)

GenerateGroups produces only the named group types. Unknown types error.

type Group

type Group struct {
	// Type is the CLI/web selector (e.g. "registration", "names").
	Type string
	// Output is the default output filename.
	Output string
	// Description is a human-readable label for progress output.
	Description string
	// Sheets are the sheet selectors, applied in order, for each workbook.
	Sheets []sheetSpec
	// InsertTitle prepends a per-tournament title page before each file's pages.
	InsertTitle bool
	// A3Landscape renders the title page in A3 landscape (for Names to Print).
	A3Landscape bool
	// SkipTeamWorkbooks excludes team-registration workbooks (no individual tags).
	SkipTeamWorkbooks bool
	// PageNumbers stamps "N / M" footers on the final merged PDF.
	PageNumbers bool
}

Group describes one output PDF: which sheets to pull from each source workbook, whether to prepend a per-tournament title page, which source files to skip, and whether to stamp page numbers on the merged result.

func GroupByType

func GroupByType(t string) (Group, bool)

GroupByType returns the group with the given Type selector, or false.

type SheetRange

type SheetRange struct {
	Sheet    string
	PageFrom int // 1-indexed, inclusive
	PageThru int // 1-indexed, inclusive
}

SheetRange is a sheet's inclusive 1-indexed page span within a full PDF.

func SheetRanges

func SheetRanges(pdfPath string) ([]SheetRange, error)

SheetRanges reads the per-sheet bookmarks LibreOffice embeds in a converted PDF and returns one SheetRange per sheet, in document order.

pdfcpu leaves PageThru==0 on the final bookmark because it has no successor to bound it; we patch any bookmark whose PageThru < PageFrom to end at the last page of the document. Without this the final sheet's pages are dropped.

type SourceWorkbook

type SourceWorkbook struct {
	// Path is the absolute path to the .xlsx file.
	Path string
	// Title is the human-readable tournament name used on title pages. When
	// empty, the filename stem is used.
	Title string
	// IsTeam marks a team-registration workbook; excluded from the Tags group.
	IsTeam bool
}

SourceWorkbook is one input XLSX with the metadata the pipeline needs.

Jump to

Keyboard shortcuts

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