When programmatically generating a PDF with lots of text/shape/etc. entries, we found ourselves repeating various text draw options, making the code genre:
page.drawText('foo', {x, y, color: myColor, size: myFontSize, lineHeight: myLeading})
page.drawLine({start, end, color: myColor, thickness: myLineThickness})
page.drawText('bar', {x, y, color: myColor, size: myFontSize, lineHeight: myLeading})
page.drawLine({start, end, color: myColor, thickness: myLineThickness})
page.drawText('fizz', {x, y, color: myColor, size: myFontSize, lineHeight: myLeading})
page.drawLine({start, end, color: myColor, thickness: myLineThickness})
page.drawText('buzz', {x, y, color: myColor, size: myFontSize, lineHeight: myLeading})
(Omitting x/y/start/end declaration/updates here for brevity, otherwise this would be even more verbose.)
We could spread those...
let textOpts: Partial<DrawTextOptions> = {color: myColor, size: myFontSize, lineHeight: myLeading}
let lineOpts: Partial<DrawLineOptions> = {color: myColor, thickness: myLineThickness}
page.drawText('foo', {...textOpts, x, y})
page.drawLine({...lineOpts, start, end})
page.drawText('bar', {...textOpts, x, y})
page.drawLine({...lineOpts, start, end})
page.drawText('fizz', {...textOpts, x, y})
page.drawLine({...lineOpts, start, end})
page.drawText('buzz', {...textOpts, x, y})
(Works, but is still a little verbose, and the constant object spread/dupe is a little intense.)
Or we could one-off drawText()/drawLine() closures, or even extend PDFPage. But all of those approaches feel rather verbose.
Instead, borrowing from other libraries/APIs (e.g. canvas API, or even some PHP PDF libs I vaguely remember from another lifetime), it would be nice if default draw options could be set on the page instance.
Proposal
Borrowing from e.g. the Canvas API, we could store defaults on a single public object prop that can be safely mutated in place.
Some of the defaults may be shared, e.g. the default color used for text, rectangles, lines, etc.
One challenge with this approach relates to ambiguous option names, e.g. maxWidth from text options vs width rect/img options, and neither applying to lines, or lineHeight relating to text, not lines being drawn. This could be solved with prefixes
Example:
page.drawOptions = {
color: myColor,
textSize: myFontSize,
textLineHeight: myLeading,
strokeThickness: myLineThickness,
}
page.drawText('foo', {x, y})
page.drawLine({start, end})
page.drawText('bar', {x, y})
page.drawLine({start, end})
page.drawText('fizz', {x, y})
page.drawLine({start, end})
page.drawText('buzz', {x, y})
This API would still allow for granular updates:
page.drawOptions = { color: myRed, textSize: myFontSize, /*...*/ }
// ...
page.drawOptions.textSize = mySmallFontSize
page.drawText('red fine print 1', {x, y})
page.drawText('red fine print 2', {x, y})
And quick resetting of all defaults:
page.drawOptions = {} // or `undefined`?
These would not replace current inline draw options, which would still take presedence
page.drawOptions.color = black
page.drawOptions.textSize = mySmallFontSize
// ...
page.drawText('im still big and red', {x, y, color: myRed, textSize: myLargeFontSize})
Alternative Proposals
Though not my personal preference, an alternative API could be default set methods per kind of draw operation:
page.setTextOptions({color: myColor, size: myFontSize, lineHeight: myLeading})
page.setLineOptions({color: myColor, thickness: myLineThickness})
page.drawText('foo', {x, y})
page.drawLine({start, end})
page.drawText('bar', {x, y})
page.drawLine({start, end})
// ...
This avoids naming collisions between, but requires polluting PDFPage with another half a dozend or so methods. It also makes resetting or granular updates more verbose, and does not allow sharing defaults (e.g. a default fill color for text, lines, rects etc.).
Combining the set method approach with the single default object from the main proposal:
page.setDrawOptions({
color: myColor,
size: myFontSize,
textLineHeight: myLeading,
strokeThickness: myLineThickness
})
page.drawText('foo', {x, y})
page.drawLine({start, end})
// ...
This this approach makes granular default updates trickier. (Could be solvable via a spread a la { ...page.drawOptions, color: /*...*/ ). But I don't see any benefits to this approach over the exposed defaults object.
An (IMO too) "clever" approach could be overloading the various draw methods with default properties:
page.drawText.color = myColor
page.drawText.size = myFontSize
// ...
page.drawText('foo', {x, y})
page.drawText('bar', {x, y})
// ...
Though technically possible, and a solution to namespacing and methol pollution, it's not typically expected by other devs or AI for functions/methods to have properties of their own.
Thoughts/suggestions?
If open to the idea, I'd be happy to work on a PR. TBD on the exact approach, property/method names, and, in case of the main proposal, how to navigate the sharing vs namespacing of default options.
When programmatically generating a PDF with lots of text/shape/etc. entries, we found ourselves repeating various text draw options, making the code genre:
(Omitting
x/y/start/enddeclaration/updates here for brevity, otherwise this would be even more verbose.)We could spread those...
(Works, but is still a little verbose, and the constant object spread/dupe is a little intense.)
Or we could one-off
drawText()/drawLine()closures, or even extendPDFPage. But all of those approaches feel rather verbose.Instead, borrowing from other libraries/APIs (e.g. canvas API, or even some PHP PDF libs I vaguely remember from another lifetime), it would be nice if default draw options could be set on the page instance.
Proposal
Borrowing from e.g. the Canvas API, we could store defaults on a single public object prop that can be safely mutated in place.
Some of the defaults may be shared, e.g. the default color used for text, rectangles, lines, etc.
One challenge with this approach relates to ambiguous option names, e.g.
maxWidthfrom text options vswidthrect/img options, and neither applying to lines, orlineHeightrelating to text, not lines being drawn. This could be solved with prefixesExample:
This API would still allow for granular updates:
And quick resetting of all defaults:
These would not replace current inline draw options, which would still take presedence
Alternative Proposals
Though not my personal preference, an alternative API could be default set methods per kind of draw operation:
This avoids naming collisions between, but requires polluting
PDFPagewith another half a dozend or so methods. It also makes resetting or granular updates more verbose, and does not allow sharing defaults (e.g. a default fill color for text, lines, rects etc.).Combining the set method approach with the single default object from the main proposal:
This this approach makes granular default updates trickier. (Could be solvable via a spread a la
{ ...page.drawOptions, color: /*...*/). But I don't see any benefits to this approach over the exposed defaults object.An (IMO too) "clever" approach could be overloading the various draw methods with default properties:
Though technically possible, and a solution to namespacing and methol pollution, it's not typically expected by other devs or AI for functions/methods to have properties of their own.
Thoughts/suggestions?
If open to the idea, I'd be happy to work on a PR. TBD on the exact approach, property/method names, and, in case of the main proposal, how to navigate the sharing vs namespacing of default options.