PDF generation contract

Fenómenus stores reusable PDF designs as private, revision-enabled WordPress content. Each design contains a complete schema-versioned document; clients must preserve schemaVersion and submit the complete document when replacing a design.

Supported API

Authenticated users with the relevant WordPress capabilities can use these routes under pom-restaurant-menus/v1:

Method Route Purpose
GET, POST /catalogs List or create designs
GET, PUT, DELETE /catalogs/{id} Read, replace, or permanently delete a design and its generated file
POST /catalogs/{id}/pdf Generate and replace the design's current PDF
POST /catalogs/{id}/duplicate Copy a design
POST /catalogs/from-preset/{preset_id} Create a design from a preset
GET, POST /presets List presets or create an administrator-managed preset
GET /data-sources Discover allowed content types, fields, and taxonomies
POST /data-sources/preview Load real source data for the editor
GET /document-schema Read the current JSON Schema

Preset creation is administrator-only. Design operations enforce matching edit or delete capabilities. PDF generation additionally requires file-upload permission. Dynamic projections are allowlisted; arbitrary post meta, statuses, taxonomies, ordering keys, and remote assets are not accepted.

The design repository enforces a site-wide maximum of 15 active designs across admin forms and REST creation or duplication. Deletion is always forced and removes the stable uploads file plus a safely matched legacy PDF attachment before deleting the design post.

An editor preview request may pass limit: -1 to load all readable matching posts. A positive value is an editing-performance cap only. The print render always resolves the complete saved data-source query.

Integrations can register an explicit data-source definition with pom_rm_menu_pdf_data_sources. Generation observers can listen to pom_rm_pdf_generation_result, which receives the generated result or WP_Error, followed by the design ID.

Document and component model

A document owns page settings, theme tokens, data sources, templates, sections, assets, and optional custom CSS. Sections use cover, content, or back-cover. A content section contains a resizable feed block and becomes as many physical pages as required without turning each overflow page into an independently editable feed.

Each feed references three reusable component templates:

  • feed for content before the loop;
  • category for terms at every hierarchy level;
  • item for each post.

Each component has a millimetre canvas and freely positioned child blocks. Child X positions and widths scale proportionally to the rendered component width; Y positions, heights, spacing, rotation, and layer order remain explicit. Updating a component updates every instance on every page.

Term templates store independent breakBeforeDepths rules. Blocks can target hierarchy depths, and their margins and padding contribute to measured pagination height. Feed-container and term blocks also support context-specific repetition rules.

Supported authored elements include fixed text, images, icons, shapes, dynamic fields, and feeds. Text and text-rendered dynamic fields can fill their remaining width with a dotted leader. Dynamic item fields can bind title, descriptions, prices, featured image, exposed meta, categories, and allergen terms. Image bindings can declare ordered field and fixed-URL fallbacks. Allergen collections support icon or image display when the term payload exposes an assigned asset.

The feed owns its data-source identifier. Reusable component bindings inherit that source: feed-container fields expose feed, document, and site values; term fields additionally expose the grouped term; loop-item fields additionally expose the current post and its projected taxonomy values. The isolated component editor always uses representative text, image, taxonomy-image, price, and icon placeholders. It never couples component layout to the first readable post. Real data remains visible in the full feed preview and print render.

Document validation rejects a second feed that references a data source already used by another feed. This invariant applies to REST writes as well as editor controls.

Rendering and file storage

Generation performs the following public behavior:

  1. Validate the saved document and current user's capabilities.
  2. Resolve every enabled section's complete source data through the allowlist.
  3. Render the document in a short-lived signed WordPress render page.
  4. Use Chrome or Chromium to print that page to PDF.
  5. Validate that the file is a non-empty PDF.
  6. Reject output above 150 MB, then atomically replace the design's stable file in the active site's uploads directory without creating a Media Library attachment.
  7. Remove temporary render files and release the generation lock.

The permanent file uses the active site's WordPress uploads base followed by pom-restaurant-menus/pdfs/catalog-{design-id}.pdf. On multisite, WordPress supplies the site-specific uploads base before that subdirectory. Regeneration keeps one server copy, returns a cache-versioned URL and filename, and reports an attachment ID of zero for compatibility with existing API consumers.

Temporary files use a private writable temporary directory and are not the download location. Only one generation can run per application instance at a time; clients should surface conflict responses and retry later.

After a generation starts, the active site rejects further attempts for 120 seconds with HTTP 429 and remainingSeconds/retryAfter response data. The generation lock remains authoritative across simultaneous tabs and returns HTTP 409 while another browser process is actively printing.

Runtime dependencies

The plugin's Composer installation supplies chrome-php/chrome, which launches and controls the browser. The server must still provide a compatible Chrome or Chromium executable. Composer cannot package a portable system browser into the PHP library.

The executable is discovered from the supported platform paths or can be configured with POM_RM_PDF_CHROME_PATH. The PHP process must be allowed to execute the browser, access the site's signed local render URL, write to the temporary directory and WordPress uploads, and load the design's permitted local media and font assets. Each browser process receives private writable HOME and XDG directories beneath the configured PDF temporary directory so service users without a writable operating-system home can start Chromium safely. Those runtime directories are removed after the browser closes or PHP shuts down. Crash reporting is disabled for these short-lived render processes, and Chromium uses temporary files instead of a constrained shared-memory mount.

The renderer uses the plugin's bundled JavaScript and Paged.js build. It does not depend on an external rendering service. Custom fonts selected from supported theme settings must expose browser-readable font sources.

Styling contract

Theme variables exposed by the editor are document colours plus heading and body fonts. Safe font choices and theme-configured fonts are copied into the document as supported font definitions. Stored spacing tokens remain schema-compatible, while authored layout uses each block's explicit millimetre margins and padding.

document.customCss is shared by the editor preview and print renderer. The same field is available from the left CSS panel and beneath the current layer's selector reference. Saved CSS is sanitized to remove imports, remote asset URLs, scriptable expressions, and unsafe browser behavior. Stable classes include .pom-rm-pdf-document, .pom-rm-feed, .pom-rm-feed-term, .pom-rm-feed-item, .block-{id}, .field-{id}, .term-depth-{n}, .is-featured, and .has-allergen-{slug}.