UNPKG

@portabletext/react-pdf

Version:
405 lines (396 loc) 17.1 kB
import { mergeComponents, PortableText as PortableText$1 } from "@portabletext/react"; import { jsx, jsxs } from "react/jsx-runtime"; import { View, Text, Image, Font } from "@react-pdf/renderer"; import merge from "lodash.merge"; import { flatten } from "flat"; import omitBy from "lodash.omitby"; function mergeStyles(defaultStyles, userStyles) { return userStyles ? merge({}, defaultStyles, userStyles) : defaultStyles; } const rem = (baseFontSizePt = 12, units = 1) => units * baseFontSizePt, normalFontSizing = (baseFontSizePt = 12) => ({ fontSize: rem(baseFontSizePt, 1), lineHeight: 1.3 }), blockStylesFactory = (baseFontSizePt = 12) => ({ normal: { marginBottom: rem(baseFontSizePt, 0.25) }, blockquote: { marginHorizontal: rem(baseFontSizePt, 1), marginTop: rem(baseFontSizePt, 0.5), marginBottom: rem(baseFontSizePt, 1), paddingLeft: rem(baseFontSizePt, 0.5), borderLeft: "2px solid gray" }, h1: { marginTop: rem(baseFontSizePt, 1.5), marginBottom: rem(baseFontSizePt, 1) }, h2: { marginTop: rem(baseFontSizePt, 1.25), marginBottom: rem(baseFontSizePt, 0.75) }, h3: { marginTop: rem(baseFontSizePt, 1.25), marginBottom: rem(baseFontSizePt, 0.75) }, h4: { marginTop: rem(baseFontSizePt, 1), marginBottom: rem(baseFontSizePt, 0.5) }, h5: { marginTop: rem(baseFontSizePt, 0.75), marginBottom: rem(baseFontSizePt, 0.5) }, h6: { marginTop: rem(baseFontSizePt, 0.75), marginBottom: rem(baseFontSizePt, 0.5) } }), textStylesFactory = (baseFontSizePt = 12) => ({ normal: { ...normalFontSizing(baseFontSizePt) }, blockquote: { ...normalFontSizing(baseFontSizePt) }, h1: { fontSize: rem(baseFontSizePt, 2.5), lineHeight: 1.1, fontWeight: 700 }, h2: { fontSize: rem(baseFontSizePt, 2), lineHeight: 1.2, fontWeight: 600 }, h3: { fontSize: rem(baseFontSizePt, 1.75), lineHeight: 1.2, fontWeight: 600 }, h4: { fontSize: rem(baseFontSizePt, 1.5), lineHeight: 1.2, fontWeight: 600 }, h5: { fontSize: rem(baseFontSizePt, 1.25), lineHeight: 1.2, fontWeight: 600 }, h6: { fontSize: rem(baseFontSizePt, 1), lineHeight: 1.2, fontWeight: 600 } }), marksStylesFactory = (baseFontSizePt = 12) => ({ strong: { fontWeight: "bold" }, highlight: { backgroundColor: "lightyellow" }, em: { fontStyle: "italic" }, link: { textDecoration: "underline", color: "blue" }, underline: { textDecoration: "underline" }, "strike-through": { textDecoration: "line-through" }, code: { lineHeight: 1, backgroundColor: "rgba(27, 31, 35, 0.05)", fontFamily: "Courier Prime" }, superscript: { verticalAlign: "super", fontSize: rem(baseFontSizePt, 0.75) }, subscript: { verticalAlign: "sub", fontSize: rem(baseFontSizePt, 0.75) } }), imageStylesFactory = (baseFontSizePt = 12) => ({ maxWidth: "100%", height: "auto", objectFit: "contain", marginBottom: rem(baseFontSizePt, 0.5) }), listStylesFactory = (baseFontSizePt = 12) => ({ list: { ...normalFontSizing(baseFontSizePt), marginTop: rem(baseFontSizePt, 0.5), marginBottom: rem(baseFontSizePt, 0.5) }, listDeep: { ...normalFontSizing(baseFontSizePt), marginTop: 0, marginBottom: 0 }, listItemWrapper: { marginVertical: rem(baseFontSizePt, 0.1), flexDirection: "row" }, listItemDecorator: { marginRight: rem(baseFontSizePt, 0.5), fontFamily: "Dejavu Mono" }, listItemNumber: { marginRight: rem(baseFontSizePt, 0.5) } }), defaultStylesFactory = (baseFontSizePt = 12) => ({ block: blockStylesFactory(baseFontSizePt), text: textStylesFactory(baseFontSizePt), marks: marksStylesFactory(baseFontSizePt), list: listStylesFactory(baseFontSizePt), image: imageStylesFactory() }), toRomanNumeral = (num) => { const romanNumerals = [ { value: 50, numeral: "l" }, { value: 40, numeral: "xl" }, { value: 10, numeral: "x" }, { value: 9, numeral: "ix" }, { value: 5, numeral: "v" }, { value: 4, numeral: "iv" }, { value: 1, numeral: "i" } ]; let result = "", remaining = num; for (const { value, numeral } of romanNumerals) for (; remaining >= value; ) result += numeral, remaining -= value; return result; }, toAlphabetic = (num) => { let result = ""; for (; num >= 0; ) result = String.fromCharCode(num % 26 + 97) + result, num = Math.floor(num / 26) - 1; return result; }, getLevelDecorator = (level, itemIndex) => { let cycleLevel = 0; switch (level) { case 1: return (itemIndex + 1).toString(); case 2: return toAlphabetic(itemIndex + 1); case 3: return toRomanNumeral(itemIndex + 1); default: return cycleLevel = (level - 1) % 3 + 1, getLevelDecorator(cycleLevel, itemIndex); } }, defaultListFactory = (styles, baseFontSizePt) => { const mergedStyles = mergeStyles(defaultStylesFactory(baseFontSizePt), styles); return (props) => { const { children, value: list } = props, listStyles = mergedStyles.list || {}, styleKey = list.level && list.level > 1 ? "listDeep" : "list", listStyle = listStyles[styleKey] || {}; return /* @__PURE__ */ jsx(View, { style: listStyle, children }, list._key); }; }, unicodeBullets = ["\u2022", "\u25E6", "\u25AA\uFE0E"], getDecorator = (level, itemType, itemIndex = 0, styles, baseFontSizePt) => { if (itemType === "number") { const decorator = getLevelDecorator(level, itemIndex); return /* @__PURE__ */ jsxs(Text, { style: { ...styles.list?.listItemNumber, fontSize: 0.9 * baseFontSizePt }, children: [ decorator, "." ] }); } else { const bulletCharIndex = (level - 1) % unicodeBullets.length, bulletStyles = bulletCharIndex === 2 ? { ...styles.list?.listItemDecorator, fontSize: 0.8 * baseFontSizePt, paddingTop: baseFontSizePt * 0.05 } : styles.list?.listItemDecorator; return /* @__PURE__ */ jsx(Text, { style: bulletStyles, children: unicodeBullets[bulletCharIndex] }); } }, defaultListItemFactory = (styles, baseFontSizePt, itemType) => { const mergedStyles = mergeStyles(defaultStylesFactory(baseFontSizePt), styles); return (props) => { const { children, value: listItem, index } = props, level = listItem.level || 1, paddingLeft = (level - 1) * baseFontSizePt, listItemWrapperStyle = mergedStyles?.list?.listItemWrapper || {}, key = `${listItem._key}__${level}`; return /* @__PURE__ */ jsx(View, { style: { ...listItemWrapperStyle, paddingLeft }, children: /* @__PURE__ */ jsxs(View, { style: { display: "flex", flexDirection: "row", alignItems: "flex-start" }, children: [ getDecorator(level, itemType, level === 2 ? index - 1 : index, mergedStyles, baseFontSizePt), /* @__PURE__ */ jsx(Text, { children }) ] }) }, key); }; }, hardBreak = () => /* @__PURE__ */ jsx(Text, { children: ` ` }), defaultUnknownMarkFactory = () => (props) => { const { children, value: mark, markType } = props; return console.warn(`Unknown mark type "${markType || "undefined"}", please specify a component for it in the \`components.marks\` prop`), /* @__PURE__ */ jsx(Text, { children }, mark?._key); }, defaultUnknownTypeFactory = () => (props) => { const { value } = props; return console.warn(`Unknown block type "${value._type || "undefined"}", specify a component for it in the \`components.types\` prop`), /* @__PURE__ */ jsx(View, {}); }, defaultUnknownBlockStyleFactory = (styles, baseFontSizePt) => (props) => { const { value: block } = props; return console.warn(`Unknown block style "${block.style || "undefined"}", please specify a component for it in the \`components.block\` prop`), props.value.style = "normal", defaultBlockFactory(styles, baseFontSizePt)(props); }, defaultUnknownListFactory = () => (props) => { const { children, value: block } = props; return console.warn(`Unknown list style "${block.listItem || "undefined"}", please specify a component for it in the \`components.list\` prop`), /* @__PURE__ */ jsx(View, { children }, block._key); }, defaultUnknownListItemFactory = (baseFontSizePt) => (props) => { const { value: block } = props; return console.warn(`Unknown list item style "${block?.listItem || "undefined"}", please specify a component for it in the \`components.list\` prop`), defaultListItemFactory({}, baseFontSizePt, "bullet")(props); }, defaultBlockFactory = (styles, baseFontSizePt) => { const mergedStyles = mergeStyles(defaultStylesFactory(baseFontSizePt), styles); return (props) => { const { children, value: block } = props, styleKey = block.style || "normal", blockStyles = mergedStyles.block || {}, textStyles = mergedStyles.text || {}; return block?.children?.length === 1 && block?.children?.[0]?.text === "" ? hardBreak() : /* @__PURE__ */ jsx(View, { style: blockStyles[styleKey], children: /* @__PURE__ */ jsx(Text, { style: textStyles[styleKey], children }) }, block._key); }; }, defaultImageFactory = (styles, baseFontSizePt) => { const mergedStyles = mergeStyles(defaultStylesFactory(baseFontSizePt), styles); return (props) => { const { value } = props, image = value?.url || value?.src || ""; return /* @__PURE__ */ jsx(Image, { src: image, style: mergedStyles.image }, value?._key); }; }, defaultPageBreakFactory = () => () => /* @__PURE__ */ jsx(View, { break: !0 }), defaultMarksFactory = (styles, baseFontSizePt, itemType) => { const mergedStyles = mergeStyles(defaultStylesFactory(baseFontSizePt), styles); return (props) => { const { children } = props, marksStyles = mergedStyles?.marks || {}; return /* @__PURE__ */ jsx(Text, { style: marksStyles[itemType], children }); }; }, generateStyledDefaultComponentsMap = (styles, baseFontSizePt) => ({ types: { break: defaultPageBreakFactory(), image: defaultImageFactory(styles, baseFontSizePt) }, block: { normal: defaultBlockFactory(styles, baseFontSizePt), blockquote: defaultBlockFactory(styles, baseFontSizePt), h1: defaultBlockFactory(styles, baseFontSizePt), h2: defaultBlockFactory(styles, baseFontSizePt), h3: defaultBlockFactory(styles, baseFontSizePt), h4: defaultBlockFactory(styles, baseFontSizePt), h5: defaultBlockFactory(styles, baseFontSizePt), h6: defaultBlockFactory(styles, baseFontSizePt) }, marks: { em: defaultMarksFactory(styles, baseFontSizePt, "em"), strong: defaultMarksFactory(styles, baseFontSizePt, "strong"), code: defaultMarksFactory(styles, baseFontSizePt, "code"), underline: defaultMarksFactory(styles, baseFontSizePt, "underline"), "strike-through": defaultMarksFactory(styles, baseFontSizePt, "strike-through"), superscript: defaultMarksFactory(styles, baseFontSizePt, "superscript"), subscript: defaultMarksFactory(styles, baseFontSizePt, "subscript"), link: defaultMarksFactory(styles, baseFontSizePt, "link"), highlight: defaultMarksFactory(styles, baseFontSizePt, "highlight") }, list: { bullet: defaultListFactory(styles, baseFontSizePt), number: defaultListFactory(styles, baseFontSizePt) }, listItem: { bullet: defaultListItemFactory(styles, baseFontSizePt, "bullet"), number: defaultListItemFactory(styles, baseFontSizePt, "number") }, hardBreak, unknownType: defaultUnknownTypeFactory(), unknownMark: defaultUnknownMarkFactory(), unknownList: defaultUnknownListFactory(), unknownListItem: defaultUnknownListItemFactory(baseFontSizePt), unknownBlockStyle: defaultUnknownBlockStyleFactory(styles, baseFontSizePt) }), mergeAndStyleComponents = (components, styles, baseFontSizePt) => { const styledDefaultComponentsMap = generateStyledDefaultComponentsMap(styles, baseFontSizePt); return components ? mergeComponents(styledDefaultComponentsMap, components) : styledDefaultComponentsMap; }; Font.register({ family: "Source Sans Pro", fonts: [ { src: "https://cdn.jsdelivr.net/fontsource/fonts/source-sans-pro@latest/latin-400-normal.ttf", fontWeight: "normal", fontStyle: "normal" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/source-sans-pro@latest/latin-400-italic.ttf", fontWeight: "normal", fontStyle: "italic" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/source-sans-pro@latest/latin-700-normal.ttf", fontWeight: "bold", fontStyle: "normal" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/source-sans-pro@latest/latin-700-italic.ttf", fontWeight: "bold", fontStyle: "italic" } ] }); Font.register({ family: "Courier Prime", fonts: [ { src: "https://cdn.jsdelivr.net/fontsource/fonts/courier-prime@latest/latin-400-normal.ttf", fontWeight: "normal", fontStyle: "normal" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/courier-prime@latest/latin-400-italic.ttf", fontWeight: "normal", fontStyle: "italic" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/courier-prime@latest/latin-700-normal.ttf", fontWeight: "bold", fontStyle: "normal" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/courier-prime@latest/latin-700-italic.ttf", fontWeight: "bold", fontStyle: "italic" } ] }); Font.register({ family: "Dejavu Mono", fonts: [ { src: "https://cdn.jsdelivr.net/fontsource/fonts/dejavu-mono@latest/latin-400-normal.ttf", fontWeight: "normal", fontStyle: "normal" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/dejavu-mono@latest/latin-400-italic.ttf", fontWeight: "normal", fontStyle: "italic" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/dejavu-mono@latest/latin-700-normal.ttf", fontWeight: "bold", fontStyle: "normal" }, { src: "https://cdn.jsdelivr.net/fontsource/fonts/dejavu-mono@latest/latin-700-italic.ttf", fontWeight: "bold", fontStyle: "italic" } ] }); const isNil = (value) => value == null, checkPropsOverlap = (props) => { const { components = {}, defaultComponentStyles = {} } = props; if (components && defaultComponentStyles) { const defaultStyleKeys = Object.keys(defaultComponentStyles), typeKeys = Object.keys(components?.types || {}), overlappingTypeKeys = defaultStyleKeys.filter((key) => typeKeys.indexOf(key) !== -1), defaultComponentStylesPaths = Object.keys(omitBy(flatten(defaultComponentStyles, { maxDepth: 2 }) || {}, isNil)), componentPaths = Object.keys(omitBy(flatten(components, { maxDepth: 2 }) || {}, isNil)), overlappingPaths = defaultComponentStylesPaths.filter((key) => componentPaths.indexOf(key) !== -1); if (overlappingPaths?.length > 0) { const errorMessage = ` OVERLAPPING PROPS: Paths with a component defined in "components" and paths with a style defined in "defaultComponentStyles" may not overlap. You've specified both a component and a style for the following path(s) in those objects: ${overlappingPaths.map((elem) => `"${elem}"`).join(", ")}. You may specify both props, as long as there are not matching paths in the two objects resulting in both a component and a style being defined for that path (would lead to confusing behavior). For example, you MAY specify a value for "block.h1" in one of those prop objects and a value for "block.h2" in the other, but you may NOT specify both a component and a style for "block.h1".`; throw console.error(errorMessage), new Error(errorMessage); } else if (overlappingTypeKeys?.length > 0) { const errorMessage = ` OVERLAPPING PROPS: Keys with a component defined in "components.types" and keys with a style defined in "defaultComponentStyles" may not overlap. You've specified both a component and a style for the following key(s) in those objects: ${overlappingTypeKeys.map((elem) => `"${elem}"`).join(", ")}. You may specify both props, as long there are not matching keys in "component.types" and "defaultComponentStyles" resulting in both a component and a style being defined for that same key (would lead to confusing behavior). For example, you MAY specify a component for "components.types.block" and a style for "defaultComponentStyles.list", but you may NOT specify an value for both "components.types.block" and "defaultComponentStyles.block".`; throw console.error(errorMessage), new Error(errorMessage); } } }; function PortableText(props) { const { baseFontSizePt = 12, defaultComponentStyles = {}, components, ...portableTextProps } = props, mergedAndStyledComponents = mergeAndStyleComponents(components, defaultComponentStyles, baseFontSizePt); return checkPropsOverlap(props), /* @__PURE__ */ jsx(PortableText$1, { listNestingMode: "direct", ...portableTextProps, components: mergedAndStyledComponents }); } var types = /* @__PURE__ */ Object.freeze({ __proto__: null }); export { PortableText, generateStyledDefaultComponentsMap, mergeAndStyleComponents, types }; //# sourceMappingURL=index.js.map