Skip to content
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

add rounded rectangle #9

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

kyasu1
Copy link

@kyasu1 kyasu1 commented Mar 9, 2025

What?

Add radius options to drawRectangle which enable us to draw rounded rectangles.

Why?

This will help to solve pdfme/pdfme#779

How?

Testing?

Please open the modified test file:

http://localhost:8080/apps/web/test9.html

New Dependencies?

Screenshots

Screenshot 2025-03-09 at 20 12 45

Suggested Reading?

Anything Else?

Checklist

  • I read CONTRIBUTING.md.
  • I read MAINTAINERSHIP.md#pull-requests.
  • I added/updated unit tests for my changes.
  • I added/updated integration tests for my changes.
  • I ran the integration tests.
  • I tested my changes in Node, Deno, and the browser.
  • I viewed documents produced with my changes in Adobe Acrobat, Foxit Reader, Firefox, and Chrome.
  • I added/updated doc comments for any new/modified public APIs.
  • My changes work for both new and existing PDF files.
  • I ran the linter on my changes.

Summary by CodeRabbit

  • New Features

    • Introduced a new option to render rectangles with rounded corners by specifying a radius value, enhancing visual appeal.
  • Documentation

    • Added a new section in the README to explain the rounded-corner feature for rectangles, including usage guidelines.

Copy link

coderabbitai bot commented Mar 9, 2025

Walkthrough

This pull request introduces a new optional radius property for drawing rectangles. In the UI test page, the rectangle configuration now includes radius: 10.0 to specify rounded corners. In the API, the PDFPage.drawRectangle method, its options interface, and the operations function were updated to accept and validate the radius value. The drawing logic in the operations module has been modified to compute Bezier curves for rounded corners when a valid radius is provided, while maintaining standard rectangle drawing otherwise. Documentation and type declarations were updated accordingly.

Changes

File(s) Summary of Changes
apps/.../test9.html Added radius: 10.0 to the rectangle drawing configuration in the test function to render rectangles with rounded corners.
src/api/{PDFPage.ts, PDFPageOptions.ts, operations.ts} Added optional radius property in rectangle drawing options. Updated drawRectangle methods to validate the radius, compute Bezier curves for rounded corners, and update documentation.
README.md Added documentation for the new radius option in the drawRectangle function, explaining its usage for rendering rectangles with rounded corners.

Sequence Diagram(s)

sequenceDiagram
    participant UI as apps/.../test9.html
    participant PDFPage
    participant Operations

    UI->>PDFPage: Call test() with rectangle config (radius: 10.0)
    PDFPage->>Operations: Invoke drawRectangle with radius option
    Operations->>Operations: If radius > 0, calculate Bezier curves for rounded corners
    Operations-->>PDFPage: Return drawing parameters
    PDFPage->>UI: Render rounded rectangle
Loading

Poem

Hop, hop, the code has grown,
A rounded touch in every zone.
I skip through functions, swift and neat,
With curves that make this work complete.
A rabbit’s cheer in every byte,
Hop into this new delight! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@hand-dot hand-dot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kyasu1 !!
Thank you!

I have added comments, so please review them.

Also, could you update the README to include details about this change?
CleanShot 2025-03-09 at 21 46 27
It will help in understanding how it differs from the original.

radius?: number | PDFNumber;
}) => {
if (!options.radius || asNumber(options.radius) <= 0) {
return [
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many redundancies, so please write it more efficiently.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drawRectangle function is divided into two large blocks due to conditional branching, resulting in significant code duplication.

You can extract the common parts to make the function more DRY.

const height = asNumber(options.height);

if (radius > width / 2.0 || radius > height / 2.0) {
console.warn("radius exceeds the rectangle's width or height");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the radius exceeds half of the rectangle's width/height, a warning is issued, and an empty array is returned. This causes the rendering to fail, but the error may not be clear.

Please adjust it to the maximum possible radius.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not commit formatting changes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/api/operations.ts (1)

277-282: Minor formatting issue in conditional operations

While functionally correct, the conditional operations for fill and stroke have inconsistent indentation that doesn't match the rest of the codebase. The prettier-ignore comment suggests this is intentional, but you may want to consider standardizing the formatting for better readability.

- // prettier-ignore
- options.color && options.borderWidth ? fillAndStroke()
-   : options.color ? fill()
-     : options.borderColor ? stroke()
-       : closePath(),
+ // prettier-ignore
+ options.color && options.borderWidth ? fillAndStroke()
+ : options.color                      ? fill()
+ : options.borderColor                ? stroke()
+ : closePath(),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de64f43 and a91b1b6.

📒 Files selected for processing (2)
  • README.md (1 hunks)
  • src/api/operations.ts (3 hunks)
🔇 Additional comments (5)
README.md (1)

6-9: Well-implemented documentation for the new feature!

The documentation clearly describes the rounded rectangle feature, explaining that it uses Bezier curves, requires a positive radius, and handles the case when the radius exceeds half of the width or height. The documentation is consistent with the project's style and provides good information for users.

src/api/operations.ts (4)

228-228: LGTM: New radius parameter properly integrated

The optional radius parameter is correctly added to the drawRectangle function options, allowing for rounded corner rectangles while maintaining backward compatibility.


230-260: The implementation correctly handles both standard and rounded rectangles

The code elegantly handles two scenarios: standard rectangles (when radius is undefined or <= 0) and rounded rectangles (when radius > 0). The approach of using Bezier curves with the KAPPA constant produces smooth rounded corners, and the radius validation ensures the corners look good even when users provide too large values.


245-247: Excellent handling of the radius constraint

Good solution for capping the radius to a maximum of half the width or height. This prevents visual artifacts and ensures the rectangle maintains its basic shape while still providing rounded corners.


262-286: Code refactoring improves maintainability

The refactoring of the operations into an ops array avoids code duplication and makes the function more maintainable. The code structure now cleanly separates the path creation logic from the styling and transformation operations.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/api/operations.ts (2)

229-286: Consider refactoring to further reduce code duplication.

While the implementation is correct, there's still some duplication between the two branches of the conditional. You could potentially extract common operations to reduce duplication further.

 export const drawRectangle = (options: {
   x: number | PDFNumber;
   y: number | PDFNumber;
   width: number | PDFNumber;
   height: number | PDFNumber;
   borderWidth: number | PDFNumber;
   color: Color | undefined;
   borderColor: Color | undefined;
   rotate: Rotation;
   xSkew: Rotation;
   ySkew: Rotation;
   borderLineCap?: LineCapStyle;
   borderDashArray?: (number | PDFNumber)[];
   borderDashPhase?: number | PDFNumber;
   graphicsState?: string | PDFName;
   matrix?: TransformationMatrix;
   clipSpaces?: Space[];
   radius?: number | PDFNumber;
 }) => {
   let ops = [];
   
   if (!options.radius || asNumber(options.radius) <= 0) {
     ops = [
       moveTo(0, 0),
       lineTo(0, options.height),
       lineTo(options.width, options.height),
       lineTo(options.width, 0),
       closePath(),
     ];
   } else {
     let radius = asNumber(options.radius);
     const width = asNumber(options.width);
     const height = asNumber(options.height);

     if (radius > width / 2.0 || radius > height / 2.0) {
       radius = Math.min(width / 2.0, height / 2.0);
     }
     const offset = KAPPA * radius;
     ops = [
       moveTo(0, radius),
       appendBezierCurve(0, radius - offset, radius - offset, 0, radius, 0),
       lineTo(width - radius, 0),
       appendBezierCurve(width - radius + offset, 0, width, radius - offset, width, radius),
       lineTo(width, height - radius),
       appendBezierCurve(width, height - radius + offset, width - radius + offset, height, width - radius, height),
       lineTo(radius, height),
       appendBezierCurve(radius - offset, height, 0, height - radius + offset, 0, height - radius),
       closePath(),
     ];
   }

   return [
     pushGraphicsState(),
     options.graphicsState && setGraphicsState(options.graphicsState),
     options.color && setFillingColor(options.color),
     options.borderColor && setStrokingColor(options.borderColor),
     setLineWidth(options.borderWidth),
     options.borderLineCap && setLineCap(options.borderLineCap),
     setDashPattern(options.borderDashArray ?? [], options.borderDashPhase ?? 0),
     ...(options.clipSpaces ? clipSpaces(options.clipSpaces) : []),
     options.matrix && concatTransformationMatrix(...options.matrix),
     translate(options.x, options.y),
     rotateRadians(toRadians(options.rotate)),
     skewRadians(toRadians(options.xSkew), toRadians(options.ySkew)),

     ...ops,

     // prettier-ignore
     options.color && options.borderWidth ? fillAndStroke()
   : options.color                      ? fill()
   : options.borderColor                ? stroke()
   : closePath(),

     popGraphicsState(),
   ].filter(Boolean) as PDFOperator[];
 }

228-260: Consider adding documentation for the radius parameter.

Add JSDoc comments to describe the radius parameter, its constraints, and behavior when exceeding limits.

 export const drawRectangle = (options: {
   x: number | PDFNumber;
   y: number | PDFNumber;
   width: number | PDFNumber;
   height: number | PDFNumber;
   borderWidth: number | PDFNumber;
   color: Color | undefined;
   borderColor: Color | undefined;
   rotate: Rotation;
   xSkew: Rotation;
   ySkew: Rotation;
   borderLineCap?: LineCapStyle;
   borderDashArray?: (number | PDFNumber)[];
   borderDashPhase?: number | PDFNumber;
   graphicsState?: string | PDFName;
   matrix?: TransformationMatrix;
   clipSpaces?: Space[];
+  /**
+   * The radius of the rectangle's corners.
+   * If not provided or less than or equal to 0, a regular rectangle is drawn.
+   * If greater than half the width or height, it will be capped at the maximum possible value.
+   */
   radius?: number | PDFNumber;
 }) => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a91b1b6 and e655791.

📒 Files selected for processing (1)
  • src/api/operations.ts (3 hunks)
🔇 Additional comments (6)
src/api/operations.ts (6)

228-228: Good API extension!

Adding the radius option to drawRectangle is a well-designed enhancement that maintains backward compatibility while providing new functionality.


229-231: Clean refactoring approach.

Refactoring to use a local ops array before adding it to the final operations is a good approach for improving code readability and maintenance.


232-240: Good handling of default behavior.

The conditional check ensures backward compatibility by maintaining the original rectangle drawing behavior when radius is not provided or invalid.


241-247: Excellent validation and adjustment of the radius parameter.

The implementation correctly caps the radius to half the width or height, preventing mathematical inconsistencies and rendering issues. This addresses a previous review comment about adjusting to the maximum possible radius rather than failing.


248-259: Well-implemented Bezier curves for rounded corners.

The implementation uses the appropriate mathematical approach with the KAPPA constant to create smooth rounded corners. The code is structured clearly with each corner's Bezier curve and connecting lines properly defined.


275-276: Using the ops array in the return statement is clean.

This approach reduces duplication and makes the code more maintainable, addressing previous review comments about code redundancy.

Copy link

@hand-dot hand-dot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants