-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: added Form Annotation support #2845
base: master
Are you sure you want to change the base?
feat: added Form Annotation support #2845
Conversation
|
if (!!this._root.data.AcroForm) { | ||
// Form is already initialized | ||
return this; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes an issue when using <Form />
twice on e.g. two pages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not check this inside the render package itself?
That way, it would not interfere with this issue: #2613
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And what about removing the <Form />
component? Its sole purpose is initializing on the document. Shouldn't it be a property on the <Document />
then, or maybe even just a check inside the form components?
packages/primitives/src/index.js
Outdated
export const Form = 'FORM'; | ||
export const FormField = 'FORM_FIELD'; | ||
export const TextInput = 'TEXT_INPUT'; | ||
export const FormPushButton = 'FORM_PUSH_BUTTON'; | ||
export const Picker = 'PICKER'; | ||
export const FormList = 'FORM_LIST'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of keeping the initial names of these components to keep them aligned with the form annotation methods of PDFKit
?
formText( name, x, y, width, height, options)
formPushButton( name, x, y, width, height, name, options)
formCombo( name, x, y, width, height, options)
formList( name, x, y, width, height, options)
(src)
const isRecursiveNode = (node) => | ||
node.type !== P.Text && | ||
node.type !== P.Svg && | ||
node.type !== P.Form && | ||
node.type !== P.FormField; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I encountered the following error when at least node.type !== P.FormField
is not applied: https://github.com/traveltechdeluxe/react-pdf/blob/81b75d26cf57e307256147372718ab8246676af6/packages/render/src/primitives/form/renderTextInput.js#L9-L12
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be solved by introducing a cleanUp function similar to the render function in the renderNode.js, which would be called after render the component and childs. This way a form field would not need to render its own childs.
const isRecursiveNode = (node) => | |
node.type !== P.Text && | |
node.type !== P.Svg && | |
node.type !== P.Form && | |
node.type !== P.FormField; | |
const isRecursiveNode = (node) => | |
node.type !== P.Text && | |
node.type !== P.Svg && | |
node.type !== P.Form; | |
const cleanUpFns = { | |
[P.FormField]: cleanUpFormField | |
} | |
// inside renderNode(): | |
const renderFn = renderFns[node.type]; | |
if (renderFn) renderFn(ctx, node, options); | |
if (shouldRenderChildren) renderChildren(ctx, node, options); | |
const cleanUpFn = cleanUpFns[node.type]; | |
if (cleanUpFn) cleanUpFn(ctx, node, options); | |
// renderFormField.js: | |
const renderFormField = (ctx, node, options) => { | |
const name = node.props?.name || ''; | |
const formField = ctx.formField(name); | |
const option = options; | |
if(!option.formFields) | |
option.formFields = [formField] | |
else | |
option.formFields.push(formField) | |
}; | |
const cleanUpFormField = (_ctx, _node, options) => { | |
options.formFields.pop() | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the form text it would then need something like this: options.formFields?.at(0)
import renderForm from './form/renderForm'; | ||
import renderFormField from './form/renderFormField'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO
- fix
Dependency cycle
<Text>Checkbox</Text> | ||
{/* TODO: how to check automatically? */} | ||
<Checkbox name="checkbox" style={{ height: '20px' }} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- example: how to set
checked
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be value or defaultValue, but then we face the problem of appearance. Like described in the pdfkit docs the appearance is not supported in the initial release and so pdfkit just used the NeedAppearances
flag to ask the viewer for appearances. “The NeedAppearances flag may not be honored by all PDF viewers”~ from the appearance section. This means that we cannot set a value without providing appearances, which means we need to set the AS and AP values. My suggestion for the parseFormOptions.js would be:
const getAppearance = (ctx, data) => {
let appearance = ctx.ref({
Type: 'XObject',
Subtype: 'Form',
BBox: [0, 0, 11.1, 11.1],
Resources: {
ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
}
});
appearance.initDeflate();
appearance.write(data);
appearance.end();
return appearance
}
const parseCheckboxOptions = (ctx, node, formField) => {
let onOption = node.props?.onState || 'Yes';
let offOption = node.props?.offState || 'Off';
let normalAppearance = {}
normalAppearance[onOption] = getAppearance(ctx, '/Tx BMC\nq BT\n0 0 0 rg /F1 11.1 Tf\n1.8 1.8 Td (8) Tj\nET\nQ\nEMC');
normalAppearance[offOption] = getAppearance(ctx, '/Tx BMC\nEMC\n');
return clean({
...parseCommonFormOptions(node),
backgroundColor: node.props?.backgroundColor || undefined,
borderColor: node.props?.borderColor || undefined,
parent: formField || undefined,
value: '/' + node.props?.checked === true ? onOption : offOption,
defaultValue: '/' + node.props?.checked === true ? onOption : offOption,
AS: node.props?.checked === true ? onOption : offOption,
AP: {
N: normalAppearance
}
});
};
// typescript definition
interface CheckboxProps extends FormCommonProps {
backGroundColor?: string;
borderColor?: string;
checked?: boolean;
onState?: string;
offState?: string;
}
//renderChecbox.js
parseCheckboxOptions(ctx, node, options.formField)
And yes AS needs to be without /
and the values with /
. If you provide a /
for AS pdfkit writes two but not for the values for some reason.
The default naming Yes
and Off
comes from the PDF Reference 1.7 page 686 under “Check Boxes”. It is not required but recommended, so I gave the user the option to change it via props. I tested it with your example.
Edit:
Had to update the appearance because pdfkit wants the FlateDecode via function call not dictionary
Right now, push buttons don't have any purpose. They are only there to exist. They cannot hold any value. There should be an option for the user to specify an |
Tasks
Notes
initForm
fix by @kjossendal, see feat: add Form Annotation support #2013 (comment)Docs