@@ -126,6 +126,7 @@ Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
126126 Nan::SetPrototypeMethod (ctor, " strokeRect" , StrokeRect);
127127 Nan::SetPrototypeMethod (ctor, " clearRect" , ClearRect);
128128 Nan::SetPrototypeMethod (ctor, " rect" , Rect);
129+ Nan::SetPrototypeMethod (ctor, " roundRect" , RoundRect);
129130 Nan::SetPrototypeMethod (ctor, " measureText" , MeasureText);
130131 Nan::SetPrototypeMethod (ctor, " moveTo" , MoveTo);
131132 Nan::SetPrototypeMethod (ctor, " lineTo" , LineTo);
@@ -2937,6 +2938,179 @@ NAN_METHOD(Context2d::Rect) {
29372938 }
29382939}
29392940
2941+ // Draws an arc with two potentially different radii.
2942+ inline static
2943+ void elli_arc (cairo_t * ctx, double xc, double yc, double rx, double ry, double a1, double a2, bool clockwise=true ) {
2944+ if (rx == 0 . || ry == 0 .) {
2945+ cairo_line_to (ctx, xc + rx, yc + ry);
2946+ } else {
2947+ cairo_save (ctx);
2948+ cairo_translate (ctx, xc, yc);
2949+ cairo_scale (ctx, rx, ry);
2950+ if (clockwise)
2951+ cairo_arc (ctx, 0 ., 0 ., 1 ., a1, a2);
2952+ else
2953+ cairo_arc_negative (ctx, 0 ., 0 ., 1 ., a2, a1);
2954+ cairo_restore (ctx);
2955+ }
2956+ }
2957+
2958+ inline static
2959+ bool getRadius (Point<double >& p, const Local<Value>& v) {
2960+ if (v->IsObject ()) { // 5.1 DOMPointInit
2961+ auto rx = Nan::Get (v.As <Object>(), Nan::New (" x" ).ToLocalChecked ()).ToLocalChecked ();
2962+ auto ry = Nan::Get (v.As <Object>(), Nan::New (" y" ).ToLocalChecked ()).ToLocalChecked ();
2963+ if (rx->IsNumber () && ry->IsNumber ()) {
2964+ auto rxv = Nan::To<double >(rx).FromJust ();
2965+ auto ryv = Nan::To<double >(ry).FromJust ();
2966+ if (!std::isfinite (rxv) || !std::isfinite (ryv))
2967+ return true ;
2968+ if (rxv < 0 || ryv < 0 ) {
2969+ Nan::ThrowRangeError (" radii must be positive." );
2970+ return true ;
2971+ }
2972+ p.x = rxv;
2973+ p.y = ryv;
2974+ return false ;
2975+ }
2976+ } else if (v->IsNumber ()) { // 5.2 unrestricted double
2977+ auto rv = Nan::To<double >(v).FromJust ();
2978+ if (!std::isfinite (rv))
2979+ return true ;
2980+ if (rv < 0 ) {
2981+ Nan::ThrowRangeError (" radii must be positive." );
2982+ return true ;
2983+ }
2984+ p.x = p.y = rv;
2985+ return false ;
2986+ }
2987+ return true ;
2988+ }
2989+
2990+ /* *
2991+ * https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
2992+ * x, y, w, h, [radius|[radii]]
2993+ */
2994+ NAN_METHOD (Context2d::RoundRect) {
2995+ RECT_ARGS;
2996+ Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This ());
2997+ cairo_t *ctx = context->context ();
2998+
2999+ // 4. Let normalizedRadii be an empty list
3000+ Point<double > normalizedRadii[4 ];
3001+ size_t nRadii = 4 ;
3002+
3003+ if (info[4 ]->IsUndefined ()) {
3004+ for (size_t i = 0 ; i < 4 ; i++)
3005+ normalizedRadii[i].x = normalizedRadii[i].y = 0 .;
3006+
3007+ } else if (info[4 ]->IsArray ()) {
3008+ auto radiiList = info[4 ].As <v8::Array>();
3009+ nRadii = radiiList->Length ();
3010+ if (!(nRadii >= 1 && nRadii <= 4 )) {
3011+ Nan::ThrowRangeError (" radii must be a list of one, two, three or four radii." );
3012+ return ;
3013+ }
3014+ // 5. For each radius of radii
3015+ for (size_t i = 0 ; i < nRadii; i++) {
3016+ auto r = Nan::Get (radiiList, i).ToLocalChecked ();
3017+ if (getRadius (normalizedRadii[i], r))
3018+ return ;
3019+ }
3020+
3021+ } else {
3022+ // 2. If radii is a double, then set radii to <<radii>>
3023+ if (getRadius (normalizedRadii[0 ], info[4 ]))
3024+ return ;
3025+ for (size_t i = 1 ; i < 4 ; i++) {
3026+ normalizedRadii[i].x = normalizedRadii[0 ].x ;
3027+ normalizedRadii[i].y = normalizedRadii[0 ].y ;
3028+ }
3029+ }
3030+
3031+ Point<double > upperLeft, upperRight, lowerRight, lowerLeft;
3032+ if (nRadii == 4 ) {
3033+ upperLeft = normalizedRadii[0 ];
3034+ upperRight = normalizedRadii[1 ];
3035+ lowerRight = normalizedRadii[2 ];
3036+ lowerLeft = normalizedRadii[3 ];
3037+ } else if (nRadii == 3 ) {
3038+ upperLeft = normalizedRadii[0 ];
3039+ upperRight = normalizedRadii[1 ];
3040+ lowerLeft = normalizedRadii[1 ];
3041+ lowerRight = normalizedRadii[2 ];
3042+ } else if (nRadii == 2 ) {
3043+ upperLeft = normalizedRadii[0 ];
3044+ lowerRight = normalizedRadii[0 ];
3045+ upperRight = normalizedRadii[1 ];
3046+ lowerLeft = normalizedRadii[1 ];
3047+ } else {
3048+ upperLeft = normalizedRadii[0 ];
3049+ upperRight = normalizedRadii[0 ];
3050+ lowerRight = normalizedRadii[0 ];
3051+ lowerLeft = normalizedRadii[0 ];
3052+ }
3053+
3054+ bool clockwise = true ;
3055+ if (width < 0 ) {
3056+ clockwise = false ;
3057+ x += width;
3058+ width = -width;
3059+ std::swap (upperLeft, upperRight);
3060+ std::swap (lowerLeft, lowerRight);
3061+ }
3062+
3063+ if (height < 0 ) {
3064+ clockwise = !clockwise;
3065+ y += height;
3066+ height = -height;
3067+ std::swap (upperLeft, lowerLeft);
3068+ std::swap (upperRight, lowerRight);
3069+ }
3070+
3071+ // 11. Corner curves must not overlap. Scale radii to prevent this.
3072+ {
3073+ auto top = upperLeft.x + upperRight.x ;
3074+ auto right = upperRight.y + lowerRight.y ;
3075+ auto bottom = lowerRight.x + lowerLeft.x ;
3076+ auto left = upperLeft.y + lowerLeft.y ;
3077+ auto scale = std::min ({ width / top, height / right, width / bottom, height / left });
3078+ if (scale < 1 .) {
3079+ upperLeft.x *= scale;
3080+ upperLeft.y *= scale;
3081+ upperRight.x *= scale;
3082+ upperRight.x *= scale;
3083+ lowerLeft.y *= scale;
3084+ lowerLeft.y *= scale;
3085+ lowerRight.y *= scale;
3086+ lowerRight.y *= scale;
3087+ }
3088+ }
3089+
3090+ // 12. Draw
3091+ cairo_move_to (ctx, x + upperLeft.x , y);
3092+ if (clockwise) {
3093+ cairo_line_to (ctx, x + width - upperRight.x , y);
3094+ elli_arc (ctx, x + width - upperRight.x , y + upperRight.y , upperRight.x , upperRight.y , 3 . * M_PI / 2 ., 0 .);
3095+ cairo_line_to (ctx, x + width, y + height - lowerRight.y );
3096+ elli_arc (ctx, x + width - lowerRight.x , y + height - lowerRight.y , lowerRight.x , lowerRight.y , 0 , M_PI / 2 .);
3097+ cairo_line_to (ctx, x + lowerLeft.x , y + height);
3098+ elli_arc (ctx, x + lowerLeft.x , y + height - lowerLeft.y , lowerLeft.x , lowerLeft.y , M_PI / 2 ., M_PI);
3099+ cairo_line_to (ctx, x, y + upperLeft.y );
3100+ elli_arc (ctx, x + upperLeft.x , y + upperLeft.y , upperLeft.x , upperLeft.y , M_PI, 3 . * M_PI / 2 .);
3101+ } else {
3102+ elli_arc (ctx, x + upperLeft.x , y + upperLeft.y , upperLeft.x , upperLeft.y , M_PI, 3 . * M_PI / 2 ., false );
3103+ cairo_line_to (ctx, x, y + upperLeft.y );
3104+ elli_arc (ctx, x + lowerLeft.x , y + height - lowerLeft.y , lowerLeft.x , lowerLeft.y , M_PI / 2 ., M_PI, false );
3105+ cairo_line_to (ctx, x + lowerLeft.x , y + height);
3106+ elli_arc (ctx, x + width - lowerRight.x , y + height - lowerRight.y , lowerRight.x , lowerRight.y , 0 , M_PI / 2 ., false );
3107+ cairo_line_to (ctx, x + width, y + height - lowerRight.y );
3108+ elli_arc (ctx, x + width - upperRight.x , y + upperRight.y , upperRight.x , upperRight.y , 3 . * M_PI / 2 ., 0 ., false );
3109+ cairo_line_to (ctx, x + width - upperRight.x , y);
3110+ }
3111+ cairo_close_path (ctx);
3112+ }
3113+
29403114// Adapted from https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/modules/canvas2d/CanvasPathMethods.cpp
29413115static void canonicalizeAngle (double & startAngle, double & endAngle) {
29423116 // Make 0 <= startAngle < 2*PI
0 commit comments