-
-
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: add Form Annotation support #2013
Changes from all commits
eee292a
e39acab
51b4493
687aa06
b562369
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,12 @@ export const Note = 'NOTE'; | |
export const Path = 'PATH'; | ||
export const Rect = 'RECT'; | ||
export const Line = 'LINE'; | ||
export const Form = 'FORM'; | ||
export const FormField = 'FORM_FIELD'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary? I get how you can create hierarchical structures but wonder it it's useful at all to expose this |
||
export const FormText = 'FORM_TEXT'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think on renaming this to |
||
export const FormPushButton = 'FORM_PUSH_BUTTON'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about renaming this to |
||
export const FormCombo = 'FORM_COMBO'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about renaming this to |
||
export const FormList = 'FORM_LIST'; | ||
export const Stop = 'STOP'; | ||
export const Defs = 'DEFS'; | ||
export const Image = 'IMAGE'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import renderNode from '../renderNode'; | ||
|
||
const renderForm = (ctx, node, options) => { | ||
ctx.save(); | ||
ctx.initForm(); | ||
|
||
const children = node.children || []; | ||
children.forEach((child) => renderNode(ctx, child, options)) | ||
|
||
ctx.restore(); | ||
}; | ||
|
||
export default renderForm; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {parseComboAndListFieldOptions} from '../../utils/parseFormOptions'; | ||
|
||
const renderFormCombo = (ctx, node) => { | ||
const { top, left, width, height } = node.box || {}; | ||
|
||
// Element's name | ||
const name = node.props?.name || ''; | ||
|
||
ctx.formCombo(name, left, top, width, height, parseComboAndListFieldOptions(node)); | ||
}; | ||
|
||
export default renderFormCombo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import renderNode from '../renderNode'; | ||
|
||
const renderFormField = (ctx, node, options) => { | ||
const name = node.props?.name || ''; | ||
|
||
const formField = ctx.formField(name); | ||
|
||
const children = node.children || []; | ||
children.forEach((child) => renderNode(ctx, child, {...options, formField})) | ||
}; | ||
|
||
export default renderFormField; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {parseComboAndListFieldOptions} from '../../utils/parseFormOptions'; | ||
|
||
const renderFormList = (ctx, node) => { | ||
const { top, left, width, height } = node.box || {}; | ||
|
||
// Element's name | ||
const name = node.props?.name || ''; | ||
|
||
ctx.formList(name, left, top, width, height, parseComboAndListFieldOptions(node)); | ||
}; | ||
|
||
export default renderFormList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {parseButtonFieldOptions} from '../../utils/parseFormOptions'; | ||
|
||
const renderFormPushButton = (ctx, node) => { | ||
const { top, left, width, height } = node.box || {}; | ||
|
||
// Element's name | ||
const name = node.props?.name || ''; | ||
|
||
ctx.formPushButton(name, left, top, width, height, parseButtonFieldOptions(node)); | ||
}; | ||
|
||
export default renderFormPushButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import {parseTextFieldOptions} from '../../utils/parseFormOptions'; | ||
|
||
const renderFormText = (ctx, node, options) => { | ||
const { top, left, width, height } = node.box || {}; | ||
|
||
// Element's name | ||
const name = node.props?.name || ''; | ||
|
||
if(!options.formField) | ||
throw new Error('The FormText element must be a children of a FormField element.') | ||
|
||
ctx.formText(name, left, top, width, height, parseTextFieldOptions(node, options.formField)); | ||
}; | ||
|
||
export default renderFormText; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,14 @@ import setLink from '../operations/setLink'; | |
import clipNode from '../operations/clipNode'; | ||
import transform from '../operations/transform'; | ||
import setDestination from '../operations/setDestination'; | ||
import renderForm from './form/renderForm'; | ||
import renderFormField from './form/renderFormField'; | ||
import renderFormText from './form/renderFormText'; | ||
import renderFormPushButton from './form/renderFormPushButton'; | ||
import renderFormCombo from './form/renderFormCombo'; | ||
import renderFormList from './form/renderFormList'; | ||
|
||
const isRecursiveNode = node => node.type !== P.Text && node.type !== P.Svg; | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Why Form and FormField aren't recursive? This creates dependency import cycles between |
||
|
||
const renderChildren = (ctx, node, options) => { | ||
ctx.save(); | ||
|
@@ -34,6 +40,12 @@ const renderFns = { | |
[P.Text]: renderText, | ||
[P.Note]: renderNote, | ||
[P.Image]: renderImage, | ||
[P.Form]: renderForm, | ||
[P.FormField]: renderFormField, | ||
[P.FormText]: renderFormText, | ||
[P.FormPushButton]: renderFormPushButton, | ||
[P.FormCombo]: renderFormCombo, | ||
[P.FormList]: renderFormList, | ||
[P.Canvas]: renderCanvas, | ||
[P.Svg]: renderSvg, | ||
[P.Link]: setLink, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
const clean = options => { | ||
const opt = { ...options }; | ||
|
||
// We need to ensure the elements are no present if not true | ||
Object.entries(opt).forEach(pair => { | ||
if (!pair[1]) { | ||
delete opt[pair[0]]; | ||
} | ||
}); | ||
|
||
return opt; | ||
}; | ||
|
||
const parseCommonFormOptions = node => { | ||
// Common Options | ||
return { | ||
required: node.props?.required || false, | ||
noExport: node.props?.noExport || false, | ||
readOnly: node.props?.readOnly || false, | ||
value: node.props?.value || undefined, | ||
defaultValue: node.props?.defaultValue || undefined, | ||
}; | ||
}; | ||
|
||
const parseTextFieldOptions = (node, formField) => { | ||
return clean({ | ||
...parseCommonFormOptions(node), | ||
parent: formField || undefined, | ||
align: node.props?.align || 'left', | ||
multiline: node.props?.multiline || undefined, | ||
password: node.props?.password || false, | ||
noSpell: node.props?.noSpell || false, | ||
format: node.props?.format || undefined, | ||
}); | ||
}; | ||
|
||
const parseComboAndListFieldOptions = node => { | ||
return clean({ | ||
...parseCommonFormOptions(node), | ||
sort: node.props?.sort || false, | ||
edit: node.props?.edit || false, | ||
multiSelect: node.props?.multiSelect || false, | ||
noSpell: node.props?.noSpell || false, | ||
select: node.props?.select || [''], | ||
}); | ||
}; | ||
|
||
const parseButtonFieldOptions = node => { | ||
return clean({ | ||
...parseCommonFormOptions(node), | ||
label: node.props?.label || '???', | ||
}); | ||
}; | ||
|
||
export { | ||
parseTextFieldOptions, | ||
parseComboAndListFieldOptions, | ||
parseButtonFieldOptions, | ||
}; | ||
Comment on lines
+55
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to be in an utils file. I think it's better for each helper fn to be closer to their element (button, combo, text, etc) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import * as P from '@react-pdf/primitives'; | ||
|
||
import createCTX from '../ctx'; | ||
import renderFormField from '../../src/primitives/form/renderFormField'; | ||
|
||
describe('primitive renderFormField', () => { | ||
test('should render FormField correctly', () => { | ||
const ctx = createCTX(); | ||
const args = 'example'; | ||
const props = { name: args }; | ||
const node = { type: P.FormField, props }; | ||
|
||
renderFormField(ctx, node); | ||
|
||
expect(ctx.formField.mock.calls).toHaveLength(1); | ||
expect(ctx.formField.mock.calls[0]).toHaveLength(1); | ||
expect(ctx.formField.mock.calls[0][0]).toBe(args); | ||
}); | ||
|
||
test('FormField with one formText direct child', () => { | ||
const ctx = createCTX(); | ||
const node = { type: P.FormField, children: [{type: P.FormText}]}; | ||
|
||
renderFormField(ctx, node); | ||
|
||
expect(ctx.formText.mock.calls).toHaveLength(1); | ||
}); | ||
|
||
test('FormField with one formText indirect child', () => { | ||
const ctx = createCTX(); | ||
const node = { | ||
type: P.FormField, | ||
children: [ | ||
{ | ||
type: P.View, | ||
children: [ | ||
{ | ||
type: P.FormText | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
|
||
renderFormField(ctx, node); | ||
|
||
expect(ctx.formText.mock.calls).toHaveLength(1); | ||
}); | ||
}); |
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.
Is this necessary? From pdfkit docs,
initForm
only needs to be called once. I tested this code and trying to render 2 forms crashes rendering. Maybe we can call it once globally if we detect any form elements