-
Notifications
You must be signed in to change notification settings - Fork 42
feat(drawing): low-level drawing API with operators, gradients, patterns, and transforms #15
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
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add comprehensive implementation plan for exposing PDF operators, Matrix class, gradients, patterns, and form XObjects to enable advanced drawing capabilities and future Canvas2D package support.
Add primitives for advanced PDF drawing capabilities: - DrawingFactory for centralized drawing resource management - Extended graphics state (ExtGState) for opacity and blend modes - Gradient support (axial/radial shading) - Resource registration methods on PDFPage - Colorspace helpers Reorganize drawing module from src/api/drawing/ to src/drawing/ and move integration tests to src/integration/ directory. Ref: plan 045-low-level-drawing-api
Session 2 - DX improvements (B+ → A+):
- Fix stale JSDoc examples in pdf-page.ts
- Add labeled tuple types: AxialCoords, RadialCoords, BBox, ColorStop
- Enhance documentation on clip(), pushGraphicsState(), paintXObject()
- Add convenience methods: fillRectWithShading, fillRectWithPattern
- Add PathBuilder integration with fillWithShading/fillWithPattern
Session 3 - Shading patterns (PatternType 2):
- Add PDFShadingPattern type that wraps gradients as usable patterns
- Add createShadingPattern() factory to wrap shadings
- Update PathOptions with pattern/borderPattern properties
- Update wrapPathOps() and PathBuilder.paint() for pattern support
- Deprecate fillWithShading/fillWithPattern in favor of fill({ pattern })
This enables the clean unified API:
const gradient = pdf.createAxialShading({ ... });
const pattern = pdf.createShadingPattern({ shading: gradient });
page.drawPath().circle(x, y, r).fill({ pattern });
All 2843 tests pass.
Remove deprecated PathBuilder methods in favor of the unified pattern API:
- Remove fillWithShading() - use fill({ pattern: pdf.createShadingPattern({ shading }) })
- Remove fillWithPattern() - use fill({ pattern })
- Update JSDoc examples to show the new pattern-based approach
- Remove associated unit tests (4 tests)
The new API is cleaner and more consistent - both tiling patterns and
shading patterns are used the same way via PathOptions.pattern.
Add pattern and borderPattern options to all high-level drawing methods:
- drawRectangle({ pattern, borderPattern, ... })
- drawCircle({ pattern, borderPattern, ... })
- drawEllipse({ pattern, borderPattern, ... })
- drawSvgPath(path, { pattern, borderPattern, ... })
This enables gradient fills on shapes with the simple high-level API:
const gradient = pdf.createAxialShading({ ... });
const pattern = pdf.createShadingPattern({ shading: gradient });
page.drawRectangle({ x, y, width, height, pattern });
Both tiling patterns and shading patterns work with all shape methods.
Includes:
- Updated DrawRectangleOptions, DrawCircleOptions, DrawEllipseOptions, DrawSvgPathOptions types
- Updated RectangleOpsOptions, EllipseOpsOptions in operations.ts
- Updated drawRectangleOps and drawEllipseOps to handle patterns
- Integration tests demonstrating gradient and tiling patterns on all shapes
- Make rotation pivot dots larger and red for visibility - Add degree symbols to angle labels (0°, 30°, 60°, 90°) - Add missing 'Rotate 12°' example in Combined Transforms section - Fix 'Scale+Rot' positioning and label - Raise angle labels closer to the rectangles
Major layout improvements: - Rotation section: larger red pivot dots, degree symbols on labels - Scale section: repositioned with cleaner spacing - Combined Transforms: 5 examples in a row showing Identity, Scale, Rotate, Scale+Rot, and Stretch Y transforms - fixed matrix multiplication order (transform * translate, not translate * transform) - Watermarks: DRAFT and APPROVED centered in their document boxes with proper rotation around center point The key fix was using transform.multiply(Matrix.translate(x, y)) instead of Matrix.translate(x, y).multiply(transform) - the former applies the transform then moves to position, while the latter scales the position.
- Fix gradient color stops to sort by offset before processing - Fix CMYK-to-RGB conversion to use standard formula (1-C)(1-K) - Document ExtGState opacity clamping behavior in JSDoc - Extract KAPPA constant to shared helpers/constants.ts - Standardize JSDoc examples to use ops. prefix consistently - Split large low-level.test.ts (2187 lines) into 8 focused files
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Adds a convenience method to create tiling patterns from embedded images, similar to CSS background-image with background-repeat. This allows filling shapes with repeating textures without manually constructing pattern streams.
Replace string-based PDF operators with typed Operator instances from helpers/operators for better type safety and consistency with the rest of the codebase. Also removes the now-unused formatNumber helper.
Replace inline ExtGState dict construction with createExtGStateDict from drawing/factory.ts. This: - Uses clearer parameter names (fillOpacity/strokeOpacity vs ca/CA) - Reuses the factory's built-in opacity clamping - Maintains consistency with PDF.createExtGState() API
…tterns - Extract getEffectiveBox() to deduplicate width/height getter logic - Extract computeImageDimensions() for clearer dimension calculation - Remove redundant variable assignments after instanceof checks - Consolidate _contentWrapped flag assignment in prependContent() - Inline simple option extractions in drawSvgPath()
… helpers - Import serializeOperators from #src/drawing/factory instead of duplicating - Remove fillRectWithShading and fillRectWithPattern convenience methods - Remove corresponding tests for removed methods
Replace addGraphicsState (inline dicts) and registerGraphicsStateForOpacity with a single registerGraphicsState that creates registered refs, enabling deduplication via registerExtGState.
- Create src/drawing/resources/ directory with separate files: - types.ts: BBox, PatternMatrix, BlendMode types - operators.ts: serializeOperators utility - shading.ts: PDFShading class with static factory methods - pattern.ts: PDFTilingPattern, PDFShadingPattern classes - extgstate.ts: PDFExtGState class with static factory methods - form-xobject.ts: PDFFormXObject class with static factory methods - index.ts: re-exports everything - Simplify class pattern: replace interface + class + factory function with single class with static methods (e.g., PDFShading.createAxialDict) - Delete old monolithic files: factory.ts and resources.ts - Update imports in pdf.ts and pdf-page.ts to use new module structure
…istency Low-level ops interfaces now consistently use strokeWidth across all shape types (Rectangle, Ellipse, Path, Line). The high-level DrawLineOptions still uses thickness since it's semantically appropriate for lines.
- Change BBox from tuple [x, y, width, height] to object interface - Make BoundingBox a type alias for BBox (same shape, both exported) - Fix PDF serialization to correctly convert to corners [llx, lly, urx, ury] - Update all usages in patterns, form xobjects, and tests This addresses H2 and H3 from the code review: the object form makes it clear you're dealing with width/height (not x1,y1,x2,y2), and the serialization now correctly converts to PDF spec corner format.
Six register methods (Font, Image, Shading, Pattern, ExtGState, XObject) shared ~30 lines of identical logic. Consolidating into a single helper reduces duplication and ensures consistent behavior across resource types.
…ilder - Move serializeOperators from resources/operators.ts to drawing/serialize.ts for better discoverability (M3) - Use serializeOperators directly in PathBuilder.emitOps instead of string round-trip via toString() for better performance (M6)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a comprehensive low-level drawing API that exposes PDF content stream operators directly to users, enabling advanced drawing capabilities not possible with the high-level API alone.
Features
opsnamespace): ~50 operators for graphics state, path construction, color, text, and XObjectsAPI Design
Three-layer architecture:
PDF.create*()- Create resources (shadings, patterns, ExtGState, XObjects)page.register*()- Register resources and get namespage.drawOperators()- Emit operators to content streamChanges
src/drawing/for types, operations, factory, path-builder, resourcessrc/api/pdf.tsandsrc/api/pdf-page.tswith new methodssrc/helpers/operators.tswith comprehensive JSDocsrc/helpers/matrix.tsfor transform compositionTesting
test-output/low-level-api/