schema.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. import { defineSchema } from "@json-render/core";
  2. /**
  3. * The schema for @json-render/react-pdf
  4. *
  5. * Defines:
  6. * - Spec: A flat tree of elements with keys, types, props, and children references
  7. * - Catalog: Components with props schemas
  8. *
  9. * Reuses the same { root, elements } spec format as the React and React Native renderers.
  10. */
  11. export const schema = defineSchema(
  12. (s) => ({
  13. spec: s.object({
  14. root: s.string(),
  15. elements: s.record(
  16. s.object({
  17. type: s.ref("catalog.components"),
  18. props: s.propsOf("catalog.components"),
  19. children: s.array(s.string()),
  20. visible: s.any(),
  21. }),
  22. ),
  23. }),
  24. catalog: s.object({
  25. components: s.map({
  26. props: s.zod(),
  27. slots: s.array(s.string()),
  28. description: s.string(),
  29. example: s.any(),
  30. }),
  31. }),
  32. }),
  33. {
  34. defaultRules: [
  35. "The root element MUST be a Document component. Its children MUST be Page components.",
  36. "Every Page must specify a size (e.g. 'A4', 'LETTER') and can set orientation, margins, and background color.",
  37. "Use Row for horizontal layouts and Column for vertical layouts. Both support gap, align, and justify props.",
  38. "Table columns must define header and optionally width and align. Rows is an array of string arrays matching the column count.",
  39. "All text content must use Heading or Text components. Raw strings are not supported.",
  40. "Image src must be a fully qualified URL. For placeholder or stock images, always use https://picsum.photos/{width}/{height}?random={n} (e.g. https://picsum.photos/400/300?random=1). Never use unsplash URLs directly.",
  41. "PageNumber renders the current page number and total pages. Place it inside a Page.",
  42. "NEVER use emoji characters in any text content. The PDF font (Helvetica) does not support emojis and they will render as garbled/overlapping characters. Use plain text descriptions instead (e.g. 'Phone:' not '📞', 'Email:' not '📧').",
  43. "PAGE LAYOUT: Be conservative with content density. A portrait A4/LETTER page with 40pt margins fits roughly 700pt of content height. For single-page documents (flyers, posters, one-pagers), keep all content on one page using smaller font sizes (10-11), tighter gaps (4-8), less padding (10-15), and smaller images (max height 200). For multi-page documents (resumes, reports), pack content densely to avoid large blank areas at the bottom of pages. Use small margins (marginTop: 30, marginBottom: 20), tight spacing (gap: 4-6), and compact font sizes (9-11 for body text) so pages are well-filled. It is better to fit more content on fewer pages than to spread thin content across many pages.",
  44. "CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.",
  45. ],
  46. },
  47. );
  48. export type ReactPdfSchema = typeof schema;
  49. export type ReactPdfSpec<TCatalog> = typeof schema extends {
  50. createCatalog: (catalog: TCatalog) => { _specType: infer S };
  51. }
  52. ? S
  53. : never;