@shqld/canvas 2.11.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +600 -0
- package/binding.gyp +230 -0
- package/browser.js +35 -0
- package/index.js +94 -0
- package/lib/DOMMatrix.js +620 -0
- package/lib/bindings.js +80 -0
- package/lib/canvas.js +113 -0
- package/lib/context2d.js +14 -0
- package/lib/image.js +96 -0
- package/lib/jpegstream.js +41 -0
- package/lib/parse-font.js +101 -0
- package/lib/pattern.js +17 -0
- package/lib/pdfstream.js +35 -0
- package/lib/pngstream.js +42 -0
- package/package.json +71 -0
- package/scripts/install.js +24 -0
- package/src/Backends.cc +18 -0
- package/src/Backends.h +10 -0
- package/src/Canvas.cc +965 -0
- package/src/Canvas.h +96 -0
- package/src/CanvasError.h +23 -0
- package/src/CanvasGradient.cc +123 -0
- package/src/CanvasGradient.h +22 -0
- package/src/CanvasPattern.cc +136 -0
- package/src/CanvasPattern.h +37 -0
- package/src/CanvasRenderingContext2d.cc +3360 -0
- package/src/CanvasRenderingContext2d.h +225 -0
- package/src/Image.cc +1434 -0
- package/src/Image.h +127 -0
- package/src/ImageData.cc +146 -0
- package/src/ImageData.h +27 -0
- package/src/JPEGStream.h +167 -0
- package/src/PNG.h +292 -0
- package/src/Point.h +11 -0
- package/src/Util.h +9 -0
- package/src/backend/Backend.cc +112 -0
- package/src/backend/Backend.h +69 -0
- package/src/backend/ImageBackend.cc +74 -0
- package/src/backend/ImageBackend.h +26 -0
- package/src/backend/PdfBackend.cc +53 -0
- package/src/backend/PdfBackend.h +24 -0
- package/src/backend/SvgBackend.cc +61 -0
- package/src/backend/SvgBackend.h +24 -0
- package/src/bmp/BMPParser.cc +457 -0
- package/src/bmp/BMPParser.h +60 -0
- package/src/bmp/LICENSE.md +24 -0
- package/src/closure.cc +26 -0
- package/src/closure.h +81 -0
- package/src/color.cc +779 -0
- package/src/color.h +30 -0
- package/src/dll_visibility.h +20 -0
- package/src/init.cc +94 -0
- package/src/register_font.cc +408 -0
- package/src/register_font.h +7 -0
- package/types/index.d.ts +484 -0
- package/util/has_lib.js +119 -0
- package/util/win_jpeg_lookup.js +21 -0
|
@@ -0,0 +1,3360 @@
|
|
|
1
|
+
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|
2
|
+
|
|
3
|
+
#include "CanvasRenderingContext2d.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include "backend/ImageBackend.h"
|
|
7
|
+
#include <cairo-pdf.h>
|
|
8
|
+
#include "Canvas.h"
|
|
9
|
+
#include "CanvasGradient.h"
|
|
10
|
+
#include "CanvasPattern.h"
|
|
11
|
+
#include <cmath>
|
|
12
|
+
#include <cstdlib>
|
|
13
|
+
#include "Image.h"
|
|
14
|
+
#include "ImageData.h"
|
|
15
|
+
#include <limits>
|
|
16
|
+
#include <map>
|
|
17
|
+
#include "Point.h"
|
|
18
|
+
#include <string>
|
|
19
|
+
#include "Util.h"
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
using namespace v8;
|
|
23
|
+
|
|
24
|
+
Nan::Persistent<FunctionTemplate> Context2d::constructor;
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Rectangle arg assertions.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
#define RECT_ARGS \
|
|
31
|
+
double args[4]; \
|
|
32
|
+
if(!checkArgs(info, args, 4)) \
|
|
33
|
+
return; \
|
|
34
|
+
double x = args[0]; \
|
|
35
|
+
double y = args[1]; \
|
|
36
|
+
double width = args[2]; \
|
|
37
|
+
double height = args[3];
|
|
38
|
+
|
|
39
|
+
#define CHECK_RECEIVER(prop) \
|
|
40
|
+
if (!Context2d::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { \
|
|
41
|
+
Nan::ThrowTypeError("Method " #prop " called on incompatible receiver"); \
|
|
42
|
+
return; \
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
constexpr double twoPi = M_PI * 2.;
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* Simple helper macro for a rather verbose function call.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
#define PANGO_LAYOUT_GET_METRICS(LAYOUT) pango_context_get_metrics( \
|
|
52
|
+
pango_layout_get_context(LAYOUT), \
|
|
53
|
+
pango_layout_get_font_description(LAYOUT), \
|
|
54
|
+
pango_context_get_language(pango_layout_get_context(LAYOUT)))
|
|
55
|
+
|
|
56
|
+
inline static bool checkArgs(const Nan::FunctionCallbackInfo<Value> &info, double *args, int argsNum, int offset = 0){
|
|
57
|
+
int argsEnd = offset + argsNum;
|
|
58
|
+
bool areArgsValid = true;
|
|
59
|
+
|
|
60
|
+
for (int i = offset; i < argsEnd; i++) {
|
|
61
|
+
double val = Nan::To<double>(info[i]).FromMaybe(0);
|
|
62
|
+
|
|
63
|
+
if (areArgsValid) {
|
|
64
|
+
if (!std::isfinite(val)) {
|
|
65
|
+
// We should continue the loop instead of returning immediately
|
|
66
|
+
// See https://html.spec.whatwg.org/multipage/canvas.html
|
|
67
|
+
|
|
68
|
+
areArgsValid = false;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
args[i - offset] = val;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return areArgsValid;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Nan::Persistent<Function> Context2d::_DOMMatrix;
|
|
80
|
+
Nan::Persistent<Function> Context2d::_parseFont;
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
* Initialize Context2d.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
void
|
|
87
|
+
Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
|
88
|
+
Nan::HandleScope scope;
|
|
89
|
+
|
|
90
|
+
// Constructor
|
|
91
|
+
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Context2d::New);
|
|
92
|
+
constructor.Reset(ctor);
|
|
93
|
+
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
|
94
|
+
ctor->SetClassName(Nan::New("CanvasRenderingContext2D").ToLocalChecked());
|
|
95
|
+
|
|
96
|
+
// Prototype
|
|
97
|
+
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
|
|
98
|
+
Nan::SetPrototypeMethod(ctor, "drawImage", DrawImage);
|
|
99
|
+
Nan::SetPrototypeMethod(ctor, "putImageData", PutImageData);
|
|
100
|
+
Nan::SetPrototypeMethod(ctor, "getImageData", GetImageData);
|
|
101
|
+
Nan::SetPrototypeMethod(ctor, "createImageData", CreateImageData);
|
|
102
|
+
Nan::SetPrototypeMethod(ctor, "addPage", AddPage);
|
|
103
|
+
Nan::SetPrototypeMethod(ctor, "save", Save);
|
|
104
|
+
Nan::SetPrototypeMethod(ctor, "restore", Restore);
|
|
105
|
+
Nan::SetPrototypeMethod(ctor, "rotate", Rotate);
|
|
106
|
+
Nan::SetPrototypeMethod(ctor, "translate", Translate);
|
|
107
|
+
Nan::SetPrototypeMethod(ctor, "transform", Transform);
|
|
108
|
+
Nan::SetPrototypeMethod(ctor, "getTransform", GetTransform);
|
|
109
|
+
Nan::SetPrototypeMethod(ctor, "resetTransform", ResetTransform);
|
|
110
|
+
Nan::SetPrototypeMethod(ctor, "setTransform", SetTransform);
|
|
111
|
+
Nan::SetPrototypeMethod(ctor, "isPointInPath", IsPointInPath);
|
|
112
|
+
Nan::SetPrototypeMethod(ctor, "scale", Scale);
|
|
113
|
+
Nan::SetPrototypeMethod(ctor, "clip", Clip);
|
|
114
|
+
Nan::SetPrototypeMethod(ctor, "fill", Fill);
|
|
115
|
+
Nan::SetPrototypeMethod(ctor, "stroke", Stroke);
|
|
116
|
+
Nan::SetPrototypeMethod(ctor, "fillText", FillText);
|
|
117
|
+
Nan::SetPrototypeMethod(ctor, "strokeText", StrokeText);
|
|
118
|
+
Nan::SetPrototypeMethod(ctor, "fillRect", FillRect);
|
|
119
|
+
Nan::SetPrototypeMethod(ctor, "strokeRect", StrokeRect);
|
|
120
|
+
Nan::SetPrototypeMethod(ctor, "clearRect", ClearRect);
|
|
121
|
+
Nan::SetPrototypeMethod(ctor, "rect", Rect);
|
|
122
|
+
Nan::SetPrototypeMethod(ctor, "roundRect", RoundRect);
|
|
123
|
+
Nan::SetPrototypeMethod(ctor, "measureText", MeasureText);
|
|
124
|
+
Nan::SetPrototypeMethod(ctor, "moveTo", MoveTo);
|
|
125
|
+
Nan::SetPrototypeMethod(ctor, "lineTo", LineTo);
|
|
126
|
+
Nan::SetPrototypeMethod(ctor, "bezierCurveTo", BezierCurveTo);
|
|
127
|
+
Nan::SetPrototypeMethod(ctor, "quadraticCurveTo", QuadraticCurveTo);
|
|
128
|
+
Nan::SetPrototypeMethod(ctor, "beginPath", BeginPath);
|
|
129
|
+
Nan::SetPrototypeMethod(ctor, "closePath", ClosePath);
|
|
130
|
+
Nan::SetPrototypeMethod(ctor, "arc", Arc);
|
|
131
|
+
Nan::SetPrototypeMethod(ctor, "arcTo", ArcTo);
|
|
132
|
+
Nan::SetPrototypeMethod(ctor, "ellipse", Ellipse);
|
|
133
|
+
Nan::SetPrototypeMethod(ctor, "setLineDash", SetLineDash);
|
|
134
|
+
Nan::SetPrototypeMethod(ctor, "getLineDash", GetLineDash);
|
|
135
|
+
Nan::SetPrototypeMethod(ctor, "createPattern", CreatePattern);
|
|
136
|
+
Nan::SetPrototypeMethod(ctor, "createLinearGradient", CreateLinearGradient);
|
|
137
|
+
Nan::SetPrototypeMethod(ctor, "createRadialGradient", CreateRadialGradient);
|
|
138
|
+
Nan::SetAccessor(proto, Nan::New("pixelFormat").ToLocalChecked(), GetFormat);
|
|
139
|
+
Nan::SetAccessor(proto, Nan::New("patternQuality").ToLocalChecked(), GetPatternQuality, SetPatternQuality);
|
|
140
|
+
Nan::SetAccessor(proto, Nan::New("imageSmoothingEnabled").ToLocalChecked(), GetImageSmoothingEnabled, SetImageSmoothingEnabled);
|
|
141
|
+
Nan::SetAccessor(proto, Nan::New("globalCompositeOperation").ToLocalChecked(), GetGlobalCompositeOperation, SetGlobalCompositeOperation);
|
|
142
|
+
Nan::SetAccessor(proto, Nan::New("globalAlpha").ToLocalChecked(), GetGlobalAlpha, SetGlobalAlpha);
|
|
143
|
+
Nan::SetAccessor(proto, Nan::New("shadowColor").ToLocalChecked(), GetShadowColor, SetShadowColor);
|
|
144
|
+
Nan::SetAccessor(proto, Nan::New("miterLimit").ToLocalChecked(), GetMiterLimit, SetMiterLimit);
|
|
145
|
+
Nan::SetAccessor(proto, Nan::New("lineWidth").ToLocalChecked(), GetLineWidth, SetLineWidth);
|
|
146
|
+
Nan::SetAccessor(proto, Nan::New("lineCap").ToLocalChecked(), GetLineCap, SetLineCap);
|
|
147
|
+
Nan::SetAccessor(proto, Nan::New("lineJoin").ToLocalChecked(), GetLineJoin, SetLineJoin);
|
|
148
|
+
Nan::SetAccessor(proto, Nan::New("lineDashOffset").ToLocalChecked(), GetLineDashOffset, SetLineDashOffset);
|
|
149
|
+
Nan::SetAccessor(proto, Nan::New("shadowOffsetX").ToLocalChecked(), GetShadowOffsetX, SetShadowOffsetX);
|
|
150
|
+
Nan::SetAccessor(proto, Nan::New("shadowOffsetY").ToLocalChecked(), GetShadowOffsetY, SetShadowOffsetY);
|
|
151
|
+
Nan::SetAccessor(proto, Nan::New("shadowBlur").ToLocalChecked(), GetShadowBlur, SetShadowBlur);
|
|
152
|
+
Nan::SetAccessor(proto, Nan::New("antialias").ToLocalChecked(), GetAntiAlias, SetAntiAlias);
|
|
153
|
+
Nan::SetAccessor(proto, Nan::New("textDrawingMode").ToLocalChecked(), GetTextDrawingMode, SetTextDrawingMode);
|
|
154
|
+
Nan::SetAccessor(proto, Nan::New("quality").ToLocalChecked(), GetQuality, SetQuality);
|
|
155
|
+
Nan::SetAccessor(proto, Nan::New("currentTransform").ToLocalChecked(), GetCurrentTransform, SetCurrentTransform);
|
|
156
|
+
Nan::SetAccessor(proto, Nan::New("fillStyle").ToLocalChecked(), GetFillStyle, SetFillStyle);
|
|
157
|
+
Nan::SetAccessor(proto, Nan::New("strokeStyle").ToLocalChecked(), GetStrokeStyle, SetStrokeStyle);
|
|
158
|
+
Nan::SetAccessor(proto, Nan::New("font").ToLocalChecked(), GetFont, SetFont);
|
|
159
|
+
Nan::SetAccessor(proto, Nan::New("textBaseline").ToLocalChecked(), GetTextBaseline, SetTextBaseline);
|
|
160
|
+
Nan::SetAccessor(proto, Nan::New("textAlign").ToLocalChecked(), GetTextAlign, SetTextAlign);
|
|
161
|
+
Local<Context> ctx = Nan::GetCurrentContext();
|
|
162
|
+
Nan::Set(target, Nan::New("CanvasRenderingContext2d").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
|
|
163
|
+
Nan::Set(target, Nan::New("CanvasRenderingContext2dInit").ToLocalChecked(), Nan::New<Function>(SaveExternalModules));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* Create a cairo context.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
Context2d::Context2d(Canvas *canvas) {
|
|
171
|
+
_canvas = canvas;
|
|
172
|
+
_context = canvas->createCairoContext();
|
|
173
|
+
_layout = pango_cairo_create_layout(_context);
|
|
174
|
+
|
|
175
|
+
// As of January 2023, Pango rounds glyph positions which renders text wider
|
|
176
|
+
// or narrower than the browser. See #2184 for more information
|
|
177
|
+
#if PANGO_VERSION_CHECK(1, 44, 0)
|
|
178
|
+
pango_context_set_round_glyph_positions(pango_layout_get_context(_layout), FALSE);
|
|
179
|
+
#endif
|
|
180
|
+
|
|
181
|
+
states.emplace();
|
|
182
|
+
state = &states.top();
|
|
183
|
+
pango_layout_set_font_description(_layout, state->fontDescription);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/*
|
|
187
|
+
* Destroy cairo context.
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
Context2d::~Context2d() {
|
|
191
|
+
g_object_unref(_layout);
|
|
192
|
+
cairo_destroy(_context);
|
|
193
|
+
_resetPersistentHandles();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* Reset canvas state.
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
void Context2d::resetState() {
|
|
201
|
+
states.pop();
|
|
202
|
+
states.emplace();
|
|
203
|
+
pango_layout_set_font_description(_layout, state->fontDescription);
|
|
204
|
+
_resetPersistentHandles();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
void Context2d::_resetPersistentHandles() {
|
|
208
|
+
_fillStyle.Reset();
|
|
209
|
+
_strokeStyle.Reset();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/*
|
|
213
|
+
* Save cairo / canvas state.
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
void
|
|
217
|
+
Context2d::save() {
|
|
218
|
+
cairo_save(_context);
|
|
219
|
+
states.emplace(states.top());
|
|
220
|
+
state = &states.top();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/*
|
|
224
|
+
* Restore cairo / canvas state.
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
void
|
|
228
|
+
Context2d::restore() {
|
|
229
|
+
if (states.size() > 1) {
|
|
230
|
+
cairo_restore(_context);
|
|
231
|
+
states.pop();
|
|
232
|
+
state = &states.top();
|
|
233
|
+
pango_layout_set_font_description(_layout, state->fontDescription);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* Save flat path.
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
void
|
|
242
|
+
Context2d::savePath() {
|
|
243
|
+
_path = cairo_copy_path_flat(_context);
|
|
244
|
+
cairo_new_path(_context);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
* Restore flat path.
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
void
|
|
252
|
+
Context2d::restorePath() {
|
|
253
|
+
cairo_new_path(_context);
|
|
254
|
+
cairo_append_path(_context, _path);
|
|
255
|
+
cairo_path_destroy(_path);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/*
|
|
259
|
+
* Create temporary surface for gradient or pattern transparency
|
|
260
|
+
*/
|
|
261
|
+
cairo_pattern_t*
|
|
262
|
+
create_transparent_gradient(cairo_pattern_t *source, float alpha) {
|
|
263
|
+
double x0;
|
|
264
|
+
double y0;
|
|
265
|
+
double x1;
|
|
266
|
+
double y1;
|
|
267
|
+
double r0;
|
|
268
|
+
double r1;
|
|
269
|
+
int count;
|
|
270
|
+
int i;
|
|
271
|
+
double offset;
|
|
272
|
+
double r;
|
|
273
|
+
double g;
|
|
274
|
+
double b;
|
|
275
|
+
double a;
|
|
276
|
+
cairo_pattern_t *newGradient;
|
|
277
|
+
cairo_pattern_type_t type = cairo_pattern_get_type(source);
|
|
278
|
+
cairo_pattern_get_color_stop_count(source, &count);
|
|
279
|
+
if (type == CAIRO_PATTERN_TYPE_LINEAR) {
|
|
280
|
+
cairo_pattern_get_linear_points (source, &x0, &y0, &x1, &y1);
|
|
281
|
+
newGradient = cairo_pattern_create_linear(x0, y0, x1, y1);
|
|
282
|
+
} else if (type == CAIRO_PATTERN_TYPE_RADIAL) {
|
|
283
|
+
cairo_pattern_get_radial_circles(source, &x0, &y0, &r0, &x1, &y1, &r1);
|
|
284
|
+
newGradient = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1);
|
|
285
|
+
} else {
|
|
286
|
+
Nan::ThrowError("Unexpected gradient type");
|
|
287
|
+
return NULL;
|
|
288
|
+
}
|
|
289
|
+
for ( i = 0; i < count; i++ ) {
|
|
290
|
+
cairo_pattern_get_color_stop_rgba(source, i, &offset, &r, &g, &b, &a);
|
|
291
|
+
cairo_pattern_add_color_stop_rgba(newGradient, offset, r, g, b, a * alpha);
|
|
292
|
+
}
|
|
293
|
+
return newGradient;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
cairo_pattern_t*
|
|
297
|
+
create_transparent_pattern(cairo_pattern_t *source, float alpha) {
|
|
298
|
+
cairo_surface_t *surface;
|
|
299
|
+
cairo_pattern_get_surface(source, &surface);
|
|
300
|
+
int width = cairo_image_surface_get_width(surface);
|
|
301
|
+
int height = cairo_image_surface_get_height(surface);
|
|
302
|
+
cairo_surface_t *mask_surface = cairo_image_surface_create(
|
|
303
|
+
CAIRO_FORMAT_ARGB32,
|
|
304
|
+
width,
|
|
305
|
+
height);
|
|
306
|
+
cairo_t *mask_context = cairo_create(mask_surface);
|
|
307
|
+
if (cairo_status(mask_context) != CAIRO_STATUS_SUCCESS) {
|
|
308
|
+
Nan::ThrowError("Failed to initialize context");
|
|
309
|
+
return NULL;
|
|
310
|
+
}
|
|
311
|
+
cairo_set_source(mask_context, source);
|
|
312
|
+
cairo_paint_with_alpha(mask_context, alpha);
|
|
313
|
+
cairo_destroy(mask_context);
|
|
314
|
+
cairo_pattern_t* newPattern = cairo_pattern_create_for_surface(mask_surface);
|
|
315
|
+
cairo_surface_destroy(mask_surface);
|
|
316
|
+
return newPattern;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/*
|
|
320
|
+
* Fill and apply shadow.
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
void
|
|
324
|
+
Context2d::setFillRule(v8::Local<v8::Value> value) {
|
|
325
|
+
cairo_fill_rule_t rule = CAIRO_FILL_RULE_WINDING;
|
|
326
|
+
if (value->IsString()) {
|
|
327
|
+
Nan::Utf8String str(value);
|
|
328
|
+
if (std::strcmp(*str, "evenodd") == 0) {
|
|
329
|
+
rule = CAIRO_FILL_RULE_EVEN_ODD;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
cairo_set_fill_rule(_context, rule);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
void
|
|
336
|
+
Context2d::fill(bool preserve) {
|
|
337
|
+
cairo_pattern_t *new_pattern;
|
|
338
|
+
bool needsRestore = false;
|
|
339
|
+
if (state->fillPattern) {
|
|
340
|
+
if (state->globalAlpha < 1) {
|
|
341
|
+
new_pattern = create_transparent_pattern(state->fillPattern, state->globalAlpha);
|
|
342
|
+
if (new_pattern == NULL) {
|
|
343
|
+
// failed to allocate; Nan::ThrowError has already been called, so return from this fn.
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
cairo_set_source(_context, new_pattern);
|
|
347
|
+
cairo_pattern_destroy(new_pattern);
|
|
348
|
+
} else {
|
|
349
|
+
cairo_pattern_set_filter(state->fillPattern, state->patternQuality);
|
|
350
|
+
cairo_set_source(_context, state->fillPattern);
|
|
351
|
+
}
|
|
352
|
+
repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->fillPattern);
|
|
353
|
+
if (repeat == NO_REPEAT) {
|
|
354
|
+
cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
|
|
355
|
+
} else if (repeat == REPEAT) {
|
|
356
|
+
cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
|
|
357
|
+
} else {
|
|
358
|
+
cairo_save(_context);
|
|
359
|
+
cairo_path_t *savedPath = cairo_copy_path(_context);
|
|
360
|
+
cairo_surface_t *patternSurface = nullptr;
|
|
361
|
+
cairo_pattern_get_surface(cairo_get_source(_context), &patternSurface);
|
|
362
|
+
|
|
363
|
+
double width, height;
|
|
364
|
+
if (repeat == REPEAT_X) {
|
|
365
|
+
double x1, x2;
|
|
366
|
+
cairo_path_extents(_context, &x1, nullptr, &x2, nullptr);
|
|
367
|
+
width = x2 - x1;
|
|
368
|
+
height = cairo_image_surface_get_height(patternSurface);
|
|
369
|
+
} else {
|
|
370
|
+
double y1, y2;
|
|
371
|
+
cairo_path_extents(_context, nullptr, &y1, nullptr, &y2);
|
|
372
|
+
width = cairo_image_surface_get_width(patternSurface);
|
|
373
|
+
height = y2 - y1;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
cairo_new_path(_context);
|
|
377
|
+
cairo_rectangle(_context, 0, 0, width, height);
|
|
378
|
+
cairo_clip(_context);
|
|
379
|
+
cairo_append_path(_context, savedPath);
|
|
380
|
+
cairo_path_destroy(savedPath);
|
|
381
|
+
cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
|
|
382
|
+
needsRestore = true;
|
|
383
|
+
}
|
|
384
|
+
} else if (state->fillGradient) {
|
|
385
|
+
if (state->globalAlpha < 1) {
|
|
386
|
+
new_pattern = create_transparent_gradient(state->fillGradient, state->globalAlpha);
|
|
387
|
+
if (new_pattern == NULL) {
|
|
388
|
+
// failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn.
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
cairo_pattern_set_filter(new_pattern, state->patternQuality);
|
|
392
|
+
cairo_set_source(_context, new_pattern);
|
|
393
|
+
cairo_pattern_destroy(new_pattern);
|
|
394
|
+
} else {
|
|
395
|
+
cairo_pattern_set_filter(state->fillGradient, state->patternQuality);
|
|
396
|
+
cairo_set_source(_context, state->fillGradient);
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
setSourceRGBA(state->fill);
|
|
400
|
+
}
|
|
401
|
+
if (preserve) {
|
|
402
|
+
hasShadow()
|
|
403
|
+
? shadow(cairo_fill_preserve)
|
|
404
|
+
: cairo_fill_preserve(_context);
|
|
405
|
+
} else {
|
|
406
|
+
hasShadow()
|
|
407
|
+
? shadow(cairo_fill)
|
|
408
|
+
: cairo_fill(_context);
|
|
409
|
+
}
|
|
410
|
+
if (needsRestore) {
|
|
411
|
+
cairo_restore(_context);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/*
|
|
416
|
+
* Stroke and apply shadow.
|
|
417
|
+
*/
|
|
418
|
+
|
|
419
|
+
void
|
|
420
|
+
Context2d::stroke(bool preserve) {
|
|
421
|
+
cairo_pattern_t *new_pattern;
|
|
422
|
+
if (state->strokePattern) {
|
|
423
|
+
if (state->globalAlpha < 1) {
|
|
424
|
+
new_pattern = create_transparent_pattern(state->strokePattern, state->globalAlpha);
|
|
425
|
+
if (new_pattern == NULL) {
|
|
426
|
+
// failed to allocate; Nan::ThrowError has already been called, so return from this fn.
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
cairo_set_source(_context, new_pattern);
|
|
430
|
+
cairo_pattern_destroy(new_pattern);
|
|
431
|
+
} else {
|
|
432
|
+
cairo_pattern_set_filter(state->strokePattern, state->patternQuality);
|
|
433
|
+
cairo_set_source(_context, state->strokePattern);
|
|
434
|
+
}
|
|
435
|
+
repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->strokePattern);
|
|
436
|
+
if (NO_REPEAT == repeat) {
|
|
437
|
+
cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
|
|
438
|
+
} else {
|
|
439
|
+
cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
|
|
440
|
+
}
|
|
441
|
+
} else if (state->strokeGradient) {
|
|
442
|
+
if (state->globalAlpha < 1) {
|
|
443
|
+
new_pattern = create_transparent_gradient(state->strokeGradient, state->globalAlpha);
|
|
444
|
+
if (new_pattern == NULL) {
|
|
445
|
+
// failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn.
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
cairo_pattern_set_filter(new_pattern, state->patternQuality);
|
|
449
|
+
cairo_set_source(_context, new_pattern);
|
|
450
|
+
cairo_pattern_destroy(new_pattern);
|
|
451
|
+
} else {
|
|
452
|
+
cairo_pattern_set_filter(state->strokeGradient, state->patternQuality);
|
|
453
|
+
cairo_set_source(_context, state->strokeGradient);
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
setSourceRGBA(state->stroke);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (preserve) {
|
|
460
|
+
hasShadow()
|
|
461
|
+
? shadow(cairo_stroke_preserve)
|
|
462
|
+
: cairo_stroke_preserve(_context);
|
|
463
|
+
} else {
|
|
464
|
+
hasShadow()
|
|
465
|
+
? shadow(cairo_stroke)
|
|
466
|
+
: cairo_stroke(_context);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/*
|
|
471
|
+
* Apply shadow with the given draw fn.
|
|
472
|
+
*/
|
|
473
|
+
|
|
474
|
+
void
|
|
475
|
+
Context2d::shadow(void (fn)(cairo_t *cr)) {
|
|
476
|
+
cairo_path_t *path = cairo_copy_path_flat(_context);
|
|
477
|
+
cairo_save(_context);
|
|
478
|
+
|
|
479
|
+
// shadowOffset is unaffected by current transform
|
|
480
|
+
cairo_matrix_t path_matrix;
|
|
481
|
+
cairo_get_matrix(_context, &path_matrix);
|
|
482
|
+
cairo_identity_matrix(_context);
|
|
483
|
+
|
|
484
|
+
// Apply shadow
|
|
485
|
+
cairo_push_group(_context);
|
|
486
|
+
|
|
487
|
+
// No need to invoke blur if shadowBlur is 0
|
|
488
|
+
if (state->shadowBlur) {
|
|
489
|
+
// find out extent of path
|
|
490
|
+
double x1, y1, x2, y2;
|
|
491
|
+
if (fn == cairo_fill || fn == cairo_fill_preserve) {
|
|
492
|
+
cairo_fill_extents(_context, &x1, &y1, &x2, &y2);
|
|
493
|
+
} else {
|
|
494
|
+
cairo_stroke_extents(_context, &x1, &y1, &x2, &y2);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// create new image surface that size + padding for blurring
|
|
498
|
+
double dx = x2-x1, dy = y2-y1;
|
|
499
|
+
cairo_user_to_device_distance(_context, &dx, &dy);
|
|
500
|
+
int pad = state->shadowBlur * 2;
|
|
501
|
+
cairo_surface_t *shadow_surface = cairo_image_surface_create(
|
|
502
|
+
CAIRO_FORMAT_ARGB32,
|
|
503
|
+
dx + 2 * pad,
|
|
504
|
+
dy + 2 * pad);
|
|
505
|
+
cairo_t *shadow_context = cairo_create(shadow_surface);
|
|
506
|
+
|
|
507
|
+
// transform path to the right place
|
|
508
|
+
cairo_translate(shadow_context, pad-x1, pad-y1);
|
|
509
|
+
cairo_transform(shadow_context, &path_matrix);
|
|
510
|
+
|
|
511
|
+
// set lineCap lineJoin lineDash
|
|
512
|
+
cairo_set_line_cap(shadow_context, cairo_get_line_cap(_context));
|
|
513
|
+
cairo_set_line_join(shadow_context, cairo_get_line_join(_context));
|
|
514
|
+
|
|
515
|
+
double offset;
|
|
516
|
+
int dashes = cairo_get_dash_count(_context);
|
|
517
|
+
std::vector<double> a(dashes);
|
|
518
|
+
cairo_get_dash(_context, a.data(), &offset);
|
|
519
|
+
cairo_set_dash(shadow_context, a.data(), dashes, offset);
|
|
520
|
+
|
|
521
|
+
// draw the path and blur
|
|
522
|
+
cairo_set_line_width(shadow_context, cairo_get_line_width(_context));
|
|
523
|
+
cairo_new_path(shadow_context);
|
|
524
|
+
cairo_append_path(shadow_context, path);
|
|
525
|
+
setSourceRGBA(shadow_context, state->shadow);
|
|
526
|
+
fn(shadow_context);
|
|
527
|
+
blur(shadow_surface, state->shadowBlur);
|
|
528
|
+
|
|
529
|
+
// paint to original context
|
|
530
|
+
cairo_set_source_surface(_context, shadow_surface,
|
|
531
|
+
x1 - pad + state->shadowOffsetX + 1,
|
|
532
|
+
y1 - pad + state->shadowOffsetY + 1);
|
|
533
|
+
cairo_paint(_context);
|
|
534
|
+
cairo_destroy(shadow_context);
|
|
535
|
+
cairo_surface_destroy(shadow_surface);
|
|
536
|
+
} else {
|
|
537
|
+
// Offset first, then apply path's transform
|
|
538
|
+
cairo_translate(
|
|
539
|
+
_context
|
|
540
|
+
, state->shadowOffsetX
|
|
541
|
+
, state->shadowOffsetY);
|
|
542
|
+
cairo_transform(_context, &path_matrix);
|
|
543
|
+
|
|
544
|
+
// Apply shadow
|
|
545
|
+
cairo_new_path(_context);
|
|
546
|
+
cairo_append_path(_context, path);
|
|
547
|
+
setSourceRGBA(state->shadow);
|
|
548
|
+
|
|
549
|
+
fn(_context);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Paint the shadow
|
|
553
|
+
cairo_pop_group_to_source(_context);
|
|
554
|
+
cairo_paint(_context);
|
|
555
|
+
|
|
556
|
+
// Restore state
|
|
557
|
+
cairo_restore(_context);
|
|
558
|
+
cairo_new_path(_context);
|
|
559
|
+
cairo_append_path(_context, path);
|
|
560
|
+
fn(_context);
|
|
561
|
+
|
|
562
|
+
cairo_path_destroy(path);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/*
|
|
566
|
+
* Set source RGBA for the current context
|
|
567
|
+
*/
|
|
568
|
+
|
|
569
|
+
void
|
|
570
|
+
Context2d::setSourceRGBA(rgba_t color) {
|
|
571
|
+
setSourceRGBA(_context, color);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/*
|
|
575
|
+
* Set source RGBA
|
|
576
|
+
*/
|
|
577
|
+
|
|
578
|
+
void
|
|
579
|
+
Context2d::setSourceRGBA(cairo_t *ctx, rgba_t color) {
|
|
580
|
+
cairo_set_source_rgba(
|
|
581
|
+
ctx
|
|
582
|
+
, color.r
|
|
583
|
+
, color.g
|
|
584
|
+
, color.b
|
|
585
|
+
, color.a * state->globalAlpha);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/*
|
|
589
|
+
* Check if the context has a drawable shadow.
|
|
590
|
+
*/
|
|
591
|
+
|
|
592
|
+
bool
|
|
593
|
+
Context2d::hasShadow() {
|
|
594
|
+
return state->shadow.a
|
|
595
|
+
&& (state->shadowBlur || state->shadowOffsetX || state->shadowOffsetY);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/*
|
|
599
|
+
* Blur the given surface with the given radius.
|
|
600
|
+
*/
|
|
601
|
+
|
|
602
|
+
void
|
|
603
|
+
Context2d::blur(cairo_surface_t *surface, int radius) {
|
|
604
|
+
// Steve Hanov, 2009
|
|
605
|
+
// Released into the public domain.
|
|
606
|
+
radius = radius * 0.57735f + 0.5f;
|
|
607
|
+
// get width, height
|
|
608
|
+
int width = cairo_image_surface_get_width( surface );
|
|
609
|
+
int height = cairo_image_surface_get_height( surface );
|
|
610
|
+
unsigned* precalc =
|
|
611
|
+
(unsigned*)malloc(width*height*sizeof(unsigned));
|
|
612
|
+
cairo_surface_flush( surface );
|
|
613
|
+
unsigned char* src = cairo_image_surface_get_data( surface );
|
|
614
|
+
double mul=1.f/((radius*2)*(radius*2));
|
|
615
|
+
int channel;
|
|
616
|
+
|
|
617
|
+
// The number of times to perform the averaging. According to wikipedia,
|
|
618
|
+
// three iterations is good enough to pass for a gaussian.
|
|
619
|
+
const int MAX_ITERATIONS = 3;
|
|
620
|
+
int iteration;
|
|
621
|
+
|
|
622
|
+
for ( iteration = 0; iteration < MAX_ITERATIONS; iteration++ ) {
|
|
623
|
+
for( channel = 0; channel < 4; channel++ ) {
|
|
624
|
+
int x,y;
|
|
625
|
+
|
|
626
|
+
// precomputation step.
|
|
627
|
+
unsigned char* pix = src;
|
|
628
|
+
unsigned* pre = precalc;
|
|
629
|
+
|
|
630
|
+
pix += channel;
|
|
631
|
+
for (y=0;y<height;y++) {
|
|
632
|
+
for (x=0;x<width;x++) {
|
|
633
|
+
int tot=pix[0];
|
|
634
|
+
if (x>0) tot+=pre[-1];
|
|
635
|
+
if (y>0) tot+=pre[-width];
|
|
636
|
+
if (x>0 && y>0) tot-=pre[-width-1];
|
|
637
|
+
*pre++=tot;
|
|
638
|
+
pix += 4;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// blur step.
|
|
643
|
+
pix = src + (int)radius * width * 4 + (int)radius * 4 + channel;
|
|
644
|
+
for (y=radius;y<height-radius;y++) {
|
|
645
|
+
for (x=radius;x<width-radius;x++) {
|
|
646
|
+
int l = x < radius ? 0 : x - radius;
|
|
647
|
+
int t = y < radius ? 0 : y - radius;
|
|
648
|
+
int r = x + radius >= width ? width - 1 : x + radius;
|
|
649
|
+
int b = y + radius >= height ? height - 1 : y + radius;
|
|
650
|
+
int tot = precalc[r+b*width] + precalc[l+t*width] -
|
|
651
|
+
precalc[l+b*width] - precalc[r+t*width];
|
|
652
|
+
*pix=(unsigned char)(tot*mul);
|
|
653
|
+
pix += 4;
|
|
654
|
+
}
|
|
655
|
+
pix += (int)radius * 2 * 4;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
cairo_surface_mark_dirty(surface);
|
|
661
|
+
free(precalc);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/*
|
|
665
|
+
* Initialize a new Context2d with the given canvas.
|
|
666
|
+
*/
|
|
667
|
+
|
|
668
|
+
NAN_METHOD(Context2d::New) {
|
|
669
|
+
if (!info.IsConstructCall()) {
|
|
670
|
+
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (!info[0]->IsObject())
|
|
674
|
+
return Nan::ThrowTypeError("Canvas expected");
|
|
675
|
+
Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
|
|
676
|
+
if (!Nan::New(Canvas::constructor)->HasInstance(obj))
|
|
677
|
+
return Nan::ThrowTypeError("Canvas expected");
|
|
678
|
+
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
|
|
679
|
+
|
|
680
|
+
bool isImageBackend = canvas->backend()->getName() == "image";
|
|
681
|
+
if (isImageBackend) {
|
|
682
|
+
cairo_format_t format = ImageBackend::DEFAULT_FORMAT;
|
|
683
|
+
if (info[1]->IsObject()) {
|
|
684
|
+
Local<Object> ctxAttributes = Nan::To<Object>(info[1]).ToLocalChecked();
|
|
685
|
+
|
|
686
|
+
Local<Value> pixelFormat = Nan::Get(ctxAttributes, Nan::New("pixelFormat").ToLocalChecked()).ToLocalChecked();
|
|
687
|
+
if (pixelFormat->IsString()) {
|
|
688
|
+
Nan::Utf8String utf8PixelFormat(pixelFormat);
|
|
689
|
+
if (!strcmp(*utf8PixelFormat, "RGBA32")) format = CAIRO_FORMAT_ARGB32;
|
|
690
|
+
else if (!strcmp(*utf8PixelFormat, "RGB24")) format = CAIRO_FORMAT_RGB24;
|
|
691
|
+
else if (!strcmp(*utf8PixelFormat, "A8")) format = CAIRO_FORMAT_A8;
|
|
692
|
+
else if (!strcmp(*utf8PixelFormat, "RGB16_565")) format = CAIRO_FORMAT_RGB16_565;
|
|
693
|
+
else if (!strcmp(*utf8PixelFormat, "A1")) format = CAIRO_FORMAT_A1;
|
|
694
|
+
#ifdef CAIRO_FORMAT_RGB30
|
|
695
|
+
else if (!strcmp(utf8PixelFormat, "RGB30")) format = CAIRO_FORMAT_RGB30;
|
|
696
|
+
#endif
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// alpha: false forces use of RGB24
|
|
700
|
+
Local<Value> alpha = Nan::Get(ctxAttributes, Nan::New("alpha").ToLocalChecked()).ToLocalChecked();
|
|
701
|
+
if (alpha->IsBoolean() && !Nan::To<bool>(alpha).FromMaybe(false)) {
|
|
702
|
+
format = CAIRO_FORMAT_RGB24;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
static_cast<ImageBackend*>(canvas->backend())->setFormat(format);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
Context2d *context = new Context2d(canvas);
|
|
709
|
+
|
|
710
|
+
context->Wrap(info.This());
|
|
711
|
+
info.GetReturnValue().Set(info.This());
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/*
|
|
715
|
+
* Save some external modules as private references.
|
|
716
|
+
*/
|
|
717
|
+
|
|
718
|
+
NAN_METHOD(Context2d::SaveExternalModules) {
|
|
719
|
+
_DOMMatrix.Reset(Nan::To<Function>(info[0]).ToLocalChecked());
|
|
720
|
+
_parseFont.Reset(Nan::To<Function>(info[1]).ToLocalChecked());
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/*
|
|
724
|
+
* Get format (string).
|
|
725
|
+
*/
|
|
726
|
+
|
|
727
|
+
NAN_GETTER(Context2d::GetFormat) {
|
|
728
|
+
CHECK_RECEIVER(Context2d.GetFormat);
|
|
729
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
730
|
+
std::string pixelFormatString;
|
|
731
|
+
switch (context->canvas()->backend()->getFormat()) {
|
|
732
|
+
case CAIRO_FORMAT_ARGB32: pixelFormatString = "RGBA32"; break;
|
|
733
|
+
case CAIRO_FORMAT_RGB24: pixelFormatString = "RGB24"; break;
|
|
734
|
+
case CAIRO_FORMAT_A8: pixelFormatString = "A8"; break;
|
|
735
|
+
case CAIRO_FORMAT_A1: pixelFormatString = "A1"; break;
|
|
736
|
+
case CAIRO_FORMAT_RGB16_565: pixelFormatString = "RGB16_565"; break;
|
|
737
|
+
#ifdef CAIRO_FORMAT_RGB30
|
|
738
|
+
case CAIRO_FORMAT_RGB30: pixelFormatString = "RGB30"; break;
|
|
739
|
+
#endif
|
|
740
|
+
default: return info.GetReturnValue().SetNull();
|
|
741
|
+
}
|
|
742
|
+
info.GetReturnValue().Set(Nan::New<String>(pixelFormatString).ToLocalChecked());
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/*
|
|
746
|
+
* Create a new page.
|
|
747
|
+
*/
|
|
748
|
+
|
|
749
|
+
NAN_METHOD(Context2d::AddPage) {
|
|
750
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
751
|
+
if (context->canvas()->backend()->getName() != "pdf") {
|
|
752
|
+
return Nan::ThrowError("only PDF canvases support .addPage()");
|
|
753
|
+
}
|
|
754
|
+
cairo_show_page(context->context());
|
|
755
|
+
int width = Nan::To<int32_t>(info[0]).FromMaybe(0);
|
|
756
|
+
int height = Nan::To<int32_t>(info[1]).FromMaybe(0);
|
|
757
|
+
if (width < 1) width = context->canvas()->getWidth();
|
|
758
|
+
if (height < 1) height = context->canvas()->getHeight();
|
|
759
|
+
cairo_pdf_surface_set_size(context->canvas()->surface(), width, height);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/*
|
|
764
|
+
* Put image data.
|
|
765
|
+
*
|
|
766
|
+
* - imageData, dx, dy
|
|
767
|
+
* - imageData, dx, dy, sx, sy, sw, sh
|
|
768
|
+
*
|
|
769
|
+
*/
|
|
770
|
+
|
|
771
|
+
NAN_METHOD(Context2d::PutImageData) {
|
|
772
|
+
if (!info[0]->IsObject())
|
|
773
|
+
return Nan::ThrowTypeError("ImageData expected");
|
|
774
|
+
Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
|
|
775
|
+
if (!Nan::New(ImageData::constructor)->HasInstance(obj))
|
|
776
|
+
return Nan::ThrowTypeError("ImageData expected");
|
|
777
|
+
|
|
778
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
779
|
+
ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(obj);
|
|
780
|
+
|
|
781
|
+
uint8_t *src = imageData->data();
|
|
782
|
+
uint8_t *dst = context->canvas()->data();
|
|
783
|
+
|
|
784
|
+
int dstStride = context->canvas()->stride();
|
|
785
|
+
int Bpp = dstStride / context->canvas()->getWidth();
|
|
786
|
+
int srcStride = Bpp * imageData->width();
|
|
787
|
+
|
|
788
|
+
int sx = 0
|
|
789
|
+
, sy = 0
|
|
790
|
+
, sw = 0
|
|
791
|
+
, sh = 0
|
|
792
|
+
, dx = Nan::To<int32_t>(info[1]).FromMaybe(0)
|
|
793
|
+
, dy = Nan::To<int32_t>(info[2]).FromMaybe(0)
|
|
794
|
+
, rows
|
|
795
|
+
, cols;
|
|
796
|
+
|
|
797
|
+
switch (info.Length()) {
|
|
798
|
+
// imageData, dx, dy
|
|
799
|
+
case 3:
|
|
800
|
+
sw = imageData->width();
|
|
801
|
+
sh = imageData->height();
|
|
802
|
+
break;
|
|
803
|
+
// imageData, dx, dy, sx, sy, sw, sh
|
|
804
|
+
case 7:
|
|
805
|
+
sx = Nan::To<int32_t>(info[3]).FromMaybe(0);
|
|
806
|
+
sy = Nan::To<int32_t>(info[4]).FromMaybe(0);
|
|
807
|
+
sw = Nan::To<int32_t>(info[5]).FromMaybe(0);
|
|
808
|
+
sh = Nan::To<int32_t>(info[6]).FromMaybe(0);
|
|
809
|
+
// fix up negative height, width
|
|
810
|
+
if (sw < 0) sx += sw, sw = -sw;
|
|
811
|
+
if (sh < 0) sy += sh, sh = -sh;
|
|
812
|
+
// clamp the left edge
|
|
813
|
+
if (sx < 0) sw += sx, sx = 0;
|
|
814
|
+
if (sy < 0) sh += sy, sy = 0;
|
|
815
|
+
// clamp the right edge
|
|
816
|
+
if (sx + sw > imageData->width()) sw = imageData->width() - sx;
|
|
817
|
+
if (sy + sh > imageData->height()) sh = imageData->height() - sy;
|
|
818
|
+
// start destination at source offset
|
|
819
|
+
dx += sx;
|
|
820
|
+
dy += sy;
|
|
821
|
+
break;
|
|
822
|
+
default:
|
|
823
|
+
return Nan::ThrowError("invalid arguments");
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// chop off outlying source data
|
|
827
|
+
if (dx < 0) sw += dx, sx -= dx, dx = 0;
|
|
828
|
+
if (dy < 0) sh += dy, sy -= dy, dy = 0;
|
|
829
|
+
// clamp width at canvas size
|
|
830
|
+
// Need to wrap std::min calls using parens to prevent macro expansion on
|
|
831
|
+
// windows. See http://stackoverflow.com/questions/5004858/stdmin-gives-error
|
|
832
|
+
cols = (std::min)(sw, context->canvas()->getWidth() - dx);
|
|
833
|
+
rows = (std::min)(sh, context->canvas()->getHeight() - dy);
|
|
834
|
+
|
|
835
|
+
if (cols <= 0 || rows <= 0) return;
|
|
836
|
+
|
|
837
|
+
switch (context->canvas()->backend()->getFormat()) {
|
|
838
|
+
case CAIRO_FORMAT_ARGB32: {
|
|
839
|
+
src += sy * srcStride + sx * 4;
|
|
840
|
+
dst += dstStride * dy + 4 * dx;
|
|
841
|
+
for (int y = 0; y < rows; ++y) {
|
|
842
|
+
uint8_t *dstRow = dst;
|
|
843
|
+
uint8_t *srcRow = src;
|
|
844
|
+
for (int x = 0; x < cols; ++x) {
|
|
845
|
+
// rgba
|
|
846
|
+
uint8_t r = *srcRow++;
|
|
847
|
+
uint8_t g = *srcRow++;
|
|
848
|
+
uint8_t b = *srcRow++;
|
|
849
|
+
uint8_t a = *srcRow++;
|
|
850
|
+
|
|
851
|
+
// argb
|
|
852
|
+
// performance optimization: fully transparent/opaque pixels can be
|
|
853
|
+
// processed more efficiently.
|
|
854
|
+
if (a == 0) {
|
|
855
|
+
*dstRow++ = 0;
|
|
856
|
+
*dstRow++ = 0;
|
|
857
|
+
*dstRow++ = 0;
|
|
858
|
+
*dstRow++ = 0;
|
|
859
|
+
} else if (a == 255) {
|
|
860
|
+
*dstRow++ = b;
|
|
861
|
+
*dstRow++ = g;
|
|
862
|
+
*dstRow++ = r;
|
|
863
|
+
*dstRow++ = a;
|
|
864
|
+
} else {
|
|
865
|
+
float alpha = (float)a / 255;
|
|
866
|
+
*dstRow++ = b * alpha;
|
|
867
|
+
*dstRow++ = g * alpha;
|
|
868
|
+
*dstRow++ = r * alpha;
|
|
869
|
+
*dstRow++ = a;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
dst += dstStride;
|
|
873
|
+
src += srcStride;
|
|
874
|
+
}
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
case CAIRO_FORMAT_RGB24: {
|
|
878
|
+
src += sy * srcStride + sx * 4;
|
|
879
|
+
dst += dstStride * dy + 4 * dx;
|
|
880
|
+
for (int y = 0; y < rows; ++y) {
|
|
881
|
+
uint8_t *dstRow = dst;
|
|
882
|
+
uint8_t *srcRow = src;
|
|
883
|
+
for (int x = 0; x < cols; ++x) {
|
|
884
|
+
// rgba
|
|
885
|
+
uint8_t r = *srcRow++;
|
|
886
|
+
uint8_t g = *srcRow++;
|
|
887
|
+
uint8_t b = *srcRow++;
|
|
888
|
+
srcRow++;
|
|
889
|
+
|
|
890
|
+
// argb
|
|
891
|
+
*dstRow++ = b;
|
|
892
|
+
*dstRow++ = g;
|
|
893
|
+
*dstRow++ = r;
|
|
894
|
+
*dstRow++ = 255;
|
|
895
|
+
}
|
|
896
|
+
dst += dstStride;
|
|
897
|
+
src += srcStride;
|
|
898
|
+
}
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
case CAIRO_FORMAT_A8: {
|
|
902
|
+
src += sy * srcStride + sx;
|
|
903
|
+
dst += dstStride * dy + dx;
|
|
904
|
+
if (srcStride == dstStride && cols == dstStride) {
|
|
905
|
+
// fast path: strides are the same and doing a full-width put
|
|
906
|
+
memcpy(dst, src, cols * rows);
|
|
907
|
+
} else {
|
|
908
|
+
for (int y = 0; y < rows; ++y) {
|
|
909
|
+
memcpy(dst, src, cols);
|
|
910
|
+
dst += dstStride;
|
|
911
|
+
src += srcStride;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
case CAIRO_FORMAT_A1: {
|
|
917
|
+
// TODO Should this be totally packed, or maintain a stride divisible by 4?
|
|
918
|
+
Nan::ThrowError("putImageData for CANVAS_FORMAT_A1 is not yet implemented");
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
case CAIRO_FORMAT_RGB16_565: {
|
|
922
|
+
src += sy * srcStride + sx * 2;
|
|
923
|
+
dst += dstStride * dy + 2 * dx;
|
|
924
|
+
for (int y = 0; y < rows; ++y) {
|
|
925
|
+
memcpy(dst, src, cols * 2);
|
|
926
|
+
dst += dstStride;
|
|
927
|
+
src += srcStride;
|
|
928
|
+
}
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
#ifdef CAIRO_FORMAT_RGB30
|
|
932
|
+
case CAIRO_FORMAT_RGB30: {
|
|
933
|
+
// TODO
|
|
934
|
+
Nan::ThrowError("putImageData for CANVAS_FORMAT_RGB30 is not yet implemented");
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
#endif
|
|
938
|
+
default: {
|
|
939
|
+
Nan::ThrowError("Invalid pixel format or not an image canvas");
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
cairo_surface_mark_dirty_rectangle(
|
|
945
|
+
context->canvas()->surface()
|
|
946
|
+
, dx
|
|
947
|
+
, dy
|
|
948
|
+
, cols
|
|
949
|
+
, rows);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/*
|
|
953
|
+
* Get image data.
|
|
954
|
+
*
|
|
955
|
+
* - sx, sy, sw, sh
|
|
956
|
+
*
|
|
957
|
+
*/
|
|
958
|
+
|
|
959
|
+
NAN_METHOD(Context2d::GetImageData) {
|
|
960
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
961
|
+
Canvas *canvas = context->canvas();
|
|
962
|
+
|
|
963
|
+
int sx = Nan::To<int32_t>(info[0]).FromMaybe(0);
|
|
964
|
+
int sy = Nan::To<int32_t>(info[1]).FromMaybe(0);
|
|
965
|
+
int sw = Nan::To<int32_t>(info[2]).FromMaybe(0);
|
|
966
|
+
int sh = Nan::To<int32_t>(info[3]).FromMaybe(0);
|
|
967
|
+
|
|
968
|
+
if (!sw)
|
|
969
|
+
return Nan::ThrowError("IndexSizeError: The source width is 0.");
|
|
970
|
+
if (!sh)
|
|
971
|
+
return Nan::ThrowError("IndexSizeError: The source height is 0.");
|
|
972
|
+
|
|
973
|
+
int width = canvas->getWidth();
|
|
974
|
+
int height = canvas->getHeight();
|
|
975
|
+
|
|
976
|
+
if (!width)
|
|
977
|
+
return Nan::ThrowTypeError("Canvas width is 0");
|
|
978
|
+
if (!height)
|
|
979
|
+
return Nan::ThrowTypeError("Canvas height is 0");
|
|
980
|
+
|
|
981
|
+
// WebKit and Firefox have this behavior:
|
|
982
|
+
// Flip the coordinates so the origin is top/left-most:
|
|
983
|
+
if (sw < 0) {
|
|
984
|
+
sx += sw;
|
|
985
|
+
sw = -sw;
|
|
986
|
+
}
|
|
987
|
+
if (sh < 0) {
|
|
988
|
+
sy += sh;
|
|
989
|
+
sh = -sh;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (sx + sw > width) sw = width - sx;
|
|
993
|
+
if (sy + sh > height) sh = height - sy;
|
|
994
|
+
|
|
995
|
+
// WebKit/moz functionality. node-canvas used to return in either case.
|
|
996
|
+
if (sw <= 0) sw = 1;
|
|
997
|
+
if (sh <= 0) sh = 1;
|
|
998
|
+
|
|
999
|
+
// Non-compliant. "Pixels outside the canvas must be returned as transparent
|
|
1000
|
+
// black." This instead clips the returned array to the canvas area.
|
|
1001
|
+
if (sx < 0) {
|
|
1002
|
+
sw += sx;
|
|
1003
|
+
sx = 0;
|
|
1004
|
+
}
|
|
1005
|
+
if (sy < 0) {
|
|
1006
|
+
sh += sy;
|
|
1007
|
+
sy = 0;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
int srcStride = canvas->stride();
|
|
1011
|
+
int bpp = srcStride / width;
|
|
1012
|
+
int size = sw * sh * bpp;
|
|
1013
|
+
int dstStride = sw * bpp;
|
|
1014
|
+
|
|
1015
|
+
uint8_t *src = canvas->data();
|
|
1016
|
+
|
|
1017
|
+
Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
|
|
1018
|
+
Local<TypedArray> dataArray;
|
|
1019
|
+
|
|
1020
|
+
if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) {
|
|
1021
|
+
dataArray = Uint16Array::New(buffer, 0, size >> 1);
|
|
1022
|
+
} else {
|
|
1023
|
+
dataArray = Uint8ClampedArray::New(buffer, 0, size);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
Nan::TypedArrayContents<uint8_t> typedArrayContents(dataArray);
|
|
1027
|
+
uint8_t* dst = *typedArrayContents;
|
|
1028
|
+
|
|
1029
|
+
switch (canvas->backend()->getFormat()) {
|
|
1030
|
+
case CAIRO_FORMAT_ARGB32: {
|
|
1031
|
+
// Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
|
|
1032
|
+
// and store in big-endian format
|
|
1033
|
+
for (int y = 0; y < sh; ++y) {
|
|
1034
|
+
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
|
|
1035
|
+
for (int x = 0; x < sw; ++x) {
|
|
1036
|
+
int bx = x * 4;
|
|
1037
|
+
uint32_t *pixel = row + x + sx;
|
|
1038
|
+
uint8_t a = *pixel >> 24;
|
|
1039
|
+
uint8_t r = *pixel >> 16;
|
|
1040
|
+
uint8_t g = *pixel >> 8;
|
|
1041
|
+
uint8_t b = *pixel;
|
|
1042
|
+
dst[bx + 3] = a;
|
|
1043
|
+
|
|
1044
|
+
// Performance optimization: fully transparent/opaque pixels can be
|
|
1045
|
+
// processed more efficiently.
|
|
1046
|
+
if (a == 0 || a == 255) {
|
|
1047
|
+
dst[bx + 0] = r;
|
|
1048
|
+
dst[bx + 1] = g;
|
|
1049
|
+
dst[bx + 2] = b;
|
|
1050
|
+
} else {
|
|
1051
|
+
// Undo alpha pre-multiplication
|
|
1052
|
+
float alphaR = (float)255 / a;
|
|
1053
|
+
dst[bx + 0] = (int)((float)r * alphaR);
|
|
1054
|
+
dst[bx + 1] = (int)((float)g * alphaR);
|
|
1055
|
+
dst[bx + 2] = (int)((float)b * alphaR);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
}
|
|
1059
|
+
dst += dstStride;
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
case CAIRO_FORMAT_RGB24: {
|
|
1064
|
+
// Rearrange alpha (argb -> rgba) and store in big-endian format
|
|
1065
|
+
for (int y = 0; y < sh; ++y) {
|
|
1066
|
+
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
|
|
1067
|
+
for (int x = 0; x < sw; ++x) {
|
|
1068
|
+
int bx = x * 4;
|
|
1069
|
+
uint32_t *pixel = row + x + sx;
|
|
1070
|
+
uint8_t r = *pixel >> 16;
|
|
1071
|
+
uint8_t g = *pixel >> 8;
|
|
1072
|
+
uint8_t b = *pixel;
|
|
1073
|
+
|
|
1074
|
+
dst[bx + 0] = r;
|
|
1075
|
+
dst[bx + 1] = g;
|
|
1076
|
+
dst[bx + 2] = b;
|
|
1077
|
+
dst[bx + 3] = 255;
|
|
1078
|
+
}
|
|
1079
|
+
dst += dstStride;
|
|
1080
|
+
}
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
case CAIRO_FORMAT_A8: {
|
|
1084
|
+
for (int y = 0; y < sh; ++y) {
|
|
1085
|
+
uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
|
|
1086
|
+
memcpy(dst, row + sx, dstStride);
|
|
1087
|
+
dst += dstStride;
|
|
1088
|
+
}
|
|
1089
|
+
break;
|
|
1090
|
+
}
|
|
1091
|
+
case CAIRO_FORMAT_A1: {
|
|
1092
|
+
// TODO Should this be totally packed, or maintain a stride divisible by 4?
|
|
1093
|
+
Nan::ThrowError("getImageData for CANVAS_FORMAT_A1 is not yet implemented");
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
case CAIRO_FORMAT_RGB16_565: {
|
|
1097
|
+
for (int y = 0; y < sh; ++y) {
|
|
1098
|
+
uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
|
|
1099
|
+
memcpy(dst, row + sx, dstStride);
|
|
1100
|
+
dst += dstStride;
|
|
1101
|
+
}
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
#ifdef CAIRO_FORMAT_RGB30
|
|
1105
|
+
case CAIRO_FORMAT_RGB30: {
|
|
1106
|
+
// TODO
|
|
1107
|
+
Nan::ThrowError("getImageData for CANVAS_FORMAT_RGB30 is not yet implemented");
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
#endif
|
|
1111
|
+
default: {
|
|
1112
|
+
// Unlikely
|
|
1113
|
+
Nan::ThrowError("Invalid pixel format or not an image canvas");
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const int argc = 3;
|
|
1119
|
+
Local<Int32> swHandle = Nan::New(sw);
|
|
1120
|
+
Local<Int32> shHandle = Nan::New(sh);
|
|
1121
|
+
Local<Value> argv[argc] = { dataArray, swHandle, shHandle };
|
|
1122
|
+
|
|
1123
|
+
Local<Function> ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked();
|
|
1124
|
+
Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
|
|
1125
|
+
|
|
1126
|
+
info.GetReturnValue().Set(instance);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Create `ImageData` with the given dimensions or
|
|
1131
|
+
* `ImageData` instance for dimensions.
|
|
1132
|
+
*/
|
|
1133
|
+
|
|
1134
|
+
NAN_METHOD(Context2d::CreateImageData){
|
|
1135
|
+
Isolate *iso = Isolate::GetCurrent();
|
|
1136
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1137
|
+
Canvas *canvas = context->canvas();
|
|
1138
|
+
int32_t width, height;
|
|
1139
|
+
|
|
1140
|
+
if (info[0]->IsObject()) {
|
|
1141
|
+
Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
|
|
1142
|
+
width = Nan::To<int32_t>(Nan::Get(obj, Nan::New("width").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
|
|
1143
|
+
height = Nan::To<int32_t>(Nan::Get(obj, Nan::New("height").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
|
|
1144
|
+
} else {
|
|
1145
|
+
width = Nan::To<int32_t>(info[0]).FromMaybe(0);
|
|
1146
|
+
height = Nan::To<int32_t>(info[1]).FromMaybe(0);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
int stride = canvas->stride();
|
|
1150
|
+
double Bpp = static_cast<double>(stride) / canvas->getWidth();
|
|
1151
|
+
int nBytes = static_cast<int>(Bpp * width * height + .5);
|
|
1152
|
+
|
|
1153
|
+
Local<ArrayBuffer> ab = ArrayBuffer::New(iso, nBytes);
|
|
1154
|
+
Local<Object> arr;
|
|
1155
|
+
|
|
1156
|
+
if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565)
|
|
1157
|
+
arr = Uint16Array::New(ab, 0, nBytes / 2);
|
|
1158
|
+
else
|
|
1159
|
+
arr = Uint8ClampedArray::New(ab, 0, nBytes);
|
|
1160
|
+
|
|
1161
|
+
const int argc = 3;
|
|
1162
|
+
Local<Value> argv[argc] = { arr, Nan::New(width), Nan::New(height) };
|
|
1163
|
+
|
|
1164
|
+
Local<Function> ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked();
|
|
1165
|
+
Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
|
|
1166
|
+
|
|
1167
|
+
info.GetReturnValue().Set(instance);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/*
|
|
1171
|
+
* Take a transform matrix and return its components
|
|
1172
|
+
* 0: angle, 1: scaleX, 2: scaleY, 3: skewX, 4: translateX, 5: translateY
|
|
1173
|
+
*/
|
|
1174
|
+
void decompose_matrix(cairo_matrix_t matrix, double *destination) {
|
|
1175
|
+
double denom = pow(matrix.xx, 2) + pow(matrix.yx, 2);
|
|
1176
|
+
destination[0] = atan2(matrix.yx, matrix.xx);
|
|
1177
|
+
destination[1] = sqrt(denom);
|
|
1178
|
+
destination[2] = (matrix.xx * matrix.yy - matrix.xy * matrix.yx) / destination[1];
|
|
1179
|
+
destination[3] = atan2(matrix.xx * matrix.xy + matrix.yx * matrix.yy, denom);
|
|
1180
|
+
destination[4] = matrix.x0;
|
|
1181
|
+
destination[5] = matrix.y0;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/*
|
|
1185
|
+
* Draw image src image to the destination (context).
|
|
1186
|
+
*
|
|
1187
|
+
* - dx, dy
|
|
1188
|
+
* - dx, dy, dw, dh
|
|
1189
|
+
* - sx, sy, sw, sh, dx, dy, dw, dh
|
|
1190
|
+
*
|
|
1191
|
+
*/
|
|
1192
|
+
|
|
1193
|
+
NAN_METHOD(Context2d::DrawImage) {
|
|
1194
|
+
int infoLen = info.Length();
|
|
1195
|
+
if (infoLen != 3 && infoLen != 5 && infoLen != 9)
|
|
1196
|
+
return Nan::ThrowTypeError("Invalid arguments");
|
|
1197
|
+
|
|
1198
|
+
if (!info[0]->IsObject())
|
|
1199
|
+
return Nan::ThrowTypeError("The first argument must be an object");
|
|
1200
|
+
|
|
1201
|
+
double args[8];
|
|
1202
|
+
if(!checkArgs(info, args, infoLen - 1, 1))
|
|
1203
|
+
return;
|
|
1204
|
+
|
|
1205
|
+
double sx = 0
|
|
1206
|
+
, sy = 0
|
|
1207
|
+
, sw = 0
|
|
1208
|
+
, sh = 0
|
|
1209
|
+
, dx = 0
|
|
1210
|
+
, dy = 0
|
|
1211
|
+
, dw = 0
|
|
1212
|
+
, dh = 0
|
|
1213
|
+
, source_w = 0
|
|
1214
|
+
, source_h = 0;
|
|
1215
|
+
|
|
1216
|
+
cairo_surface_t *surface;
|
|
1217
|
+
|
|
1218
|
+
Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
|
|
1219
|
+
|
|
1220
|
+
// Image
|
|
1221
|
+
if (Nan::New(Image::constructor)->HasInstance(obj)) {
|
|
1222
|
+
Image *img = Nan::ObjectWrap::Unwrap<Image>(obj);
|
|
1223
|
+
if (!img->isComplete()) {
|
|
1224
|
+
return Nan::ThrowError("Image given has not completed loading");
|
|
1225
|
+
}
|
|
1226
|
+
source_w = sw = img->width;
|
|
1227
|
+
source_h = sh = img->height;
|
|
1228
|
+
surface = img->surface();
|
|
1229
|
+
|
|
1230
|
+
// Canvas
|
|
1231
|
+
} else if (Nan::New(Canvas::constructor)->HasInstance(obj)) {
|
|
1232
|
+
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
|
|
1233
|
+
source_w = sw = canvas->getWidth();
|
|
1234
|
+
source_h = sh = canvas->getHeight();
|
|
1235
|
+
surface = canvas->surface();
|
|
1236
|
+
|
|
1237
|
+
// Invalid
|
|
1238
|
+
} else {
|
|
1239
|
+
return Nan::ThrowTypeError("Image or Canvas expected");
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1243
|
+
cairo_t *ctx = context->context();
|
|
1244
|
+
|
|
1245
|
+
// Arguments
|
|
1246
|
+
switch (infoLen) {
|
|
1247
|
+
// img, sx, sy, sw, sh, dx, dy, dw, dh
|
|
1248
|
+
case 9:
|
|
1249
|
+
sx = args[0];
|
|
1250
|
+
sy = args[1];
|
|
1251
|
+
sw = args[2];
|
|
1252
|
+
sh = args[3];
|
|
1253
|
+
dx = args[4];
|
|
1254
|
+
dy = args[5];
|
|
1255
|
+
dw = args[6];
|
|
1256
|
+
dh = args[7];
|
|
1257
|
+
break;
|
|
1258
|
+
// img, dx, dy, dw, dh
|
|
1259
|
+
case 5:
|
|
1260
|
+
dx = args[0];
|
|
1261
|
+
dy = args[1];
|
|
1262
|
+
dw = args[2];
|
|
1263
|
+
dh = args[3];
|
|
1264
|
+
break;
|
|
1265
|
+
// img, dx, dy
|
|
1266
|
+
case 3:
|
|
1267
|
+
dx = args[0];
|
|
1268
|
+
dy = args[1];
|
|
1269
|
+
dw = sw;
|
|
1270
|
+
dh = sh;
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (!(sw && sh && dw && dh))
|
|
1275
|
+
return;
|
|
1276
|
+
|
|
1277
|
+
// Start draw
|
|
1278
|
+
cairo_save(ctx);
|
|
1279
|
+
|
|
1280
|
+
cairo_matrix_t matrix;
|
|
1281
|
+
double transforms[6];
|
|
1282
|
+
cairo_get_matrix(context->context(), &matrix);
|
|
1283
|
+
decompose_matrix(matrix, transforms);
|
|
1284
|
+
// extract the scale value from the current transform so that we know how many pixels we
|
|
1285
|
+
// need for our extra canvas in the drawImage operation.
|
|
1286
|
+
double current_scale_x = std::abs(transforms[1]);
|
|
1287
|
+
double current_scale_y = std::abs(transforms[2]);
|
|
1288
|
+
double extra_dx = 0;
|
|
1289
|
+
double extra_dy = 0;
|
|
1290
|
+
double fx = dw / sw * current_scale_x; // transforms[1] is scale on X
|
|
1291
|
+
double fy = dh / sh * current_scale_y; // transforms[2] is scale on X
|
|
1292
|
+
bool needScale = dw != sw || dh != sh;
|
|
1293
|
+
bool needCut = sw != source_w || sh != source_h || sx < 0 || sy < 0;
|
|
1294
|
+
bool sameCanvas = surface == context->canvas()->surface();
|
|
1295
|
+
bool needsExtraSurface = sameCanvas || needCut || needScale;
|
|
1296
|
+
cairo_surface_t *surfTemp = NULL;
|
|
1297
|
+
cairo_t *ctxTemp = NULL;
|
|
1298
|
+
|
|
1299
|
+
if (needsExtraSurface) {
|
|
1300
|
+
// we want to create the extra surface as small as possible.
|
|
1301
|
+
// fx and fy are the total scaling we need to apply to sw, sh.
|
|
1302
|
+
// from sw and sh we want to remove the part that is outside the source_w and soruce_h
|
|
1303
|
+
double real_w = sw;
|
|
1304
|
+
double real_h = sh;
|
|
1305
|
+
double translate_x = 0;
|
|
1306
|
+
double translate_y = 0;
|
|
1307
|
+
// if sx or sy are negative, a part of the area represented by sw and sh is empty
|
|
1308
|
+
// because there are empty pixels, so we cut it out.
|
|
1309
|
+
// On the other hand if sx or sy are positive, but sw and sh extend outside the real
|
|
1310
|
+
// source pixels, we cut the area in that case too.
|
|
1311
|
+
if (sx < 0) {
|
|
1312
|
+
extra_dx = -sx * fx;
|
|
1313
|
+
real_w = sw + sx;
|
|
1314
|
+
} else if (sx + sw > source_w) {
|
|
1315
|
+
real_w = sw - (sx + sw - source_w);
|
|
1316
|
+
}
|
|
1317
|
+
if (sy < 0) {
|
|
1318
|
+
extra_dy = -sy * fy;
|
|
1319
|
+
real_h = sh + sy;
|
|
1320
|
+
} else if (sy + sh > source_h) {
|
|
1321
|
+
real_h = sh - (sy + sh - source_h);
|
|
1322
|
+
}
|
|
1323
|
+
// if after cutting we are still bigger than source pixels, we restrict again
|
|
1324
|
+
if (real_w > source_w) {
|
|
1325
|
+
real_w = source_w;
|
|
1326
|
+
}
|
|
1327
|
+
if (real_h > source_h) {
|
|
1328
|
+
real_h = source_h;
|
|
1329
|
+
}
|
|
1330
|
+
// TODO: find a way to limit the surfTemp to real_w and real_h if fx and fy are bigger than 1.
|
|
1331
|
+
// there are no more pixel than the one available in the source, no need to create a bigger surface.
|
|
1332
|
+
surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, round(real_w * fx), round(real_h * fy));
|
|
1333
|
+
ctxTemp = cairo_create(surfTemp);
|
|
1334
|
+
cairo_scale(ctxTemp, fx, fy);
|
|
1335
|
+
if (sx > 0) {
|
|
1336
|
+
translate_x = sx;
|
|
1337
|
+
}
|
|
1338
|
+
if (sy > 0) {
|
|
1339
|
+
translate_y = sy;
|
|
1340
|
+
}
|
|
1341
|
+
cairo_set_source_surface(ctxTemp, surface, -translate_x, -translate_y);
|
|
1342
|
+
cairo_pattern_set_filter(cairo_get_source(ctxTemp), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
|
|
1343
|
+
cairo_pattern_set_extend(cairo_get_source(ctxTemp), CAIRO_EXTEND_REFLECT);
|
|
1344
|
+
cairo_paint_with_alpha(ctxTemp, 1);
|
|
1345
|
+
surface = surfTemp;
|
|
1346
|
+
}
|
|
1347
|
+
// apply shadow if there is one
|
|
1348
|
+
if (context->hasShadow()) {
|
|
1349
|
+
if(context->state->shadowBlur) {
|
|
1350
|
+
// we need to create a new surface in order to blur
|
|
1351
|
+
int pad = context->state->shadowBlur * 2;
|
|
1352
|
+
cairo_surface_t *shadow_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dw + 2 * pad, dh + 2 * pad);
|
|
1353
|
+
cairo_t *shadow_context = cairo_create(shadow_surface);
|
|
1354
|
+
|
|
1355
|
+
// mask and blur
|
|
1356
|
+
context->setSourceRGBA(shadow_context, context->state->shadow);
|
|
1357
|
+
cairo_mask_surface(shadow_context, surface, pad, pad);
|
|
1358
|
+
context->blur(shadow_surface, context->state->shadowBlur);
|
|
1359
|
+
|
|
1360
|
+
// paint
|
|
1361
|
+
// @note: ShadowBlur looks different in each browser. This implementation matches chrome as close as possible.
|
|
1362
|
+
// The 1.4 offset comes from visual tests with Chrome. I have read the spec and part of the shadowBlur
|
|
1363
|
+
// implementation, and its not immediately clear why an offset is necessary, but without it, the result
|
|
1364
|
+
// in chrome is different.
|
|
1365
|
+
cairo_set_source_surface(ctx, shadow_surface,
|
|
1366
|
+
dx + context->state->shadowOffsetX - pad + 1.4,
|
|
1367
|
+
dy + context->state->shadowOffsetY - pad + 1.4);
|
|
1368
|
+
cairo_paint(ctx);
|
|
1369
|
+
// cleanup
|
|
1370
|
+
cairo_destroy(shadow_context);
|
|
1371
|
+
cairo_surface_destroy(shadow_surface);
|
|
1372
|
+
} else {
|
|
1373
|
+
context->setSourceRGBA(context->state->shadow);
|
|
1374
|
+
cairo_mask_surface(ctx, surface,
|
|
1375
|
+
dx + (context->state->shadowOffsetX),
|
|
1376
|
+
dy + (context->state->shadowOffsetY));
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
double scaled_dx = dx;
|
|
1381
|
+
double scaled_dy = dy;
|
|
1382
|
+
|
|
1383
|
+
if (needsExtraSurface && (current_scale_x != 1 || current_scale_y != 1)) {
|
|
1384
|
+
// in this case our surface contains already current_scale_x, we need to scale back
|
|
1385
|
+
cairo_scale(ctx, 1 / current_scale_x, 1 / current_scale_y);
|
|
1386
|
+
scaled_dx *= current_scale_x;
|
|
1387
|
+
scaled_dy *= current_scale_y;
|
|
1388
|
+
}
|
|
1389
|
+
// Paint
|
|
1390
|
+
cairo_set_source_surface(ctx, surface, scaled_dx + extra_dx, scaled_dy + extra_dy);
|
|
1391
|
+
cairo_pattern_set_filter(cairo_get_source(ctx), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
|
|
1392
|
+
cairo_pattern_set_extend(cairo_get_source(ctx), CAIRO_EXTEND_NONE);
|
|
1393
|
+
cairo_paint_with_alpha(ctx, context->state->globalAlpha);
|
|
1394
|
+
|
|
1395
|
+
cairo_restore(ctx);
|
|
1396
|
+
|
|
1397
|
+
if (needsExtraSurface) {
|
|
1398
|
+
cairo_destroy(ctxTemp);
|
|
1399
|
+
cairo_surface_destroy(surfTemp);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/*
|
|
1404
|
+
* Get global alpha.
|
|
1405
|
+
*/
|
|
1406
|
+
|
|
1407
|
+
NAN_GETTER(Context2d::GetGlobalAlpha) {
|
|
1408
|
+
CHECK_RECEIVER(Context2d.GetGlobalAlpha);
|
|
1409
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1410
|
+
info.GetReturnValue().Set(Nan::New<Number>(context->state->globalAlpha));
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/*
|
|
1414
|
+
* Set global alpha.
|
|
1415
|
+
*/
|
|
1416
|
+
|
|
1417
|
+
NAN_SETTER(Context2d::SetGlobalAlpha) {
|
|
1418
|
+
CHECK_RECEIVER(Context2d.SetGlobalAlpha);
|
|
1419
|
+
double n = Nan::To<double>(value).FromMaybe(0);
|
|
1420
|
+
if (n >= 0 && n <= 1) {
|
|
1421
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1422
|
+
context->state->globalAlpha = n;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
/*
|
|
1427
|
+
* Get global composite operation.
|
|
1428
|
+
*/
|
|
1429
|
+
|
|
1430
|
+
NAN_GETTER(Context2d::GetGlobalCompositeOperation) {
|
|
1431
|
+
CHECK_RECEIVER(Context2d.GetGlobalCompositeOperation);
|
|
1432
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1433
|
+
cairo_t *ctx = context->context();
|
|
1434
|
+
|
|
1435
|
+
const char *op = "source-over";
|
|
1436
|
+
switch (cairo_get_operator(ctx)) {
|
|
1437
|
+
// composite modes:
|
|
1438
|
+
case CAIRO_OPERATOR_CLEAR: op = "clear"; break;
|
|
1439
|
+
case CAIRO_OPERATOR_SOURCE: op = "copy"; break;
|
|
1440
|
+
case CAIRO_OPERATOR_DEST: op = "destination"; break;
|
|
1441
|
+
case CAIRO_OPERATOR_OVER: op = "source-over"; break;
|
|
1442
|
+
case CAIRO_OPERATOR_DEST_OVER: op = "destination-over"; break;
|
|
1443
|
+
case CAIRO_OPERATOR_IN: op = "source-in"; break;
|
|
1444
|
+
case CAIRO_OPERATOR_DEST_IN: op = "destination-in"; break;
|
|
1445
|
+
case CAIRO_OPERATOR_OUT: op = "source-out"; break;
|
|
1446
|
+
case CAIRO_OPERATOR_DEST_OUT: op = "destination-out"; break;
|
|
1447
|
+
case CAIRO_OPERATOR_ATOP: op = "source-atop"; break;
|
|
1448
|
+
case CAIRO_OPERATOR_DEST_ATOP: op = "destination-atop"; break;
|
|
1449
|
+
case CAIRO_OPERATOR_XOR: op = "xor"; break;
|
|
1450
|
+
case CAIRO_OPERATOR_ADD: op = "lighter"; break;
|
|
1451
|
+
// blend modes:
|
|
1452
|
+
// Note: "source-over" and "normal" are synonyms. Chrome and FF both report
|
|
1453
|
+
// "source-over" after setting gCO to "normal".
|
|
1454
|
+
// case CAIRO_OPERATOR_OVER: op = "normal";
|
|
1455
|
+
case CAIRO_OPERATOR_MULTIPLY: op = "multiply"; break;
|
|
1456
|
+
case CAIRO_OPERATOR_SCREEN: op = "screen"; break;
|
|
1457
|
+
case CAIRO_OPERATOR_OVERLAY: op = "overlay"; break;
|
|
1458
|
+
case CAIRO_OPERATOR_DARKEN: op = "darken"; break;
|
|
1459
|
+
case CAIRO_OPERATOR_LIGHTEN: op = "lighten"; break;
|
|
1460
|
+
case CAIRO_OPERATOR_COLOR_DODGE: op = "color-dodge"; break;
|
|
1461
|
+
case CAIRO_OPERATOR_COLOR_BURN: op = "color-burn"; break;
|
|
1462
|
+
case CAIRO_OPERATOR_HARD_LIGHT: op = "hard-light"; break;
|
|
1463
|
+
case CAIRO_OPERATOR_SOFT_LIGHT: op = "soft-light"; break;
|
|
1464
|
+
case CAIRO_OPERATOR_DIFFERENCE: op = "difference"; break;
|
|
1465
|
+
case CAIRO_OPERATOR_EXCLUSION: op = "exclusion"; break;
|
|
1466
|
+
case CAIRO_OPERATOR_HSL_HUE: op = "hue"; break;
|
|
1467
|
+
case CAIRO_OPERATOR_HSL_SATURATION: op = "saturation"; break;
|
|
1468
|
+
case CAIRO_OPERATOR_HSL_COLOR: op = "color"; break;
|
|
1469
|
+
case CAIRO_OPERATOR_HSL_LUMINOSITY: op = "luminosity"; break;
|
|
1470
|
+
// non-standard:
|
|
1471
|
+
case CAIRO_OPERATOR_SATURATE: op = "saturate"; break;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
info.GetReturnValue().Set(Nan::New(op).ToLocalChecked());
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/*
|
|
1478
|
+
* Set pattern quality.
|
|
1479
|
+
*/
|
|
1480
|
+
|
|
1481
|
+
NAN_SETTER(Context2d::SetPatternQuality) {
|
|
1482
|
+
CHECK_RECEIVER(Context2d.SetPatternQuality);
|
|
1483
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1484
|
+
Nan::Utf8String quality(Nan::To<String>(value).ToLocalChecked());
|
|
1485
|
+
if (0 == strcmp("fast", *quality)) {
|
|
1486
|
+
context->state->patternQuality = CAIRO_FILTER_FAST;
|
|
1487
|
+
} else if (0 == strcmp("good", *quality)) {
|
|
1488
|
+
context->state->patternQuality = CAIRO_FILTER_GOOD;
|
|
1489
|
+
} else if (0 == strcmp("best", *quality)) {
|
|
1490
|
+
context->state->patternQuality = CAIRO_FILTER_BEST;
|
|
1491
|
+
} else if (0 == strcmp("nearest", *quality)) {
|
|
1492
|
+
context->state->patternQuality = CAIRO_FILTER_NEAREST;
|
|
1493
|
+
} else if (0 == strcmp("bilinear", *quality)) {
|
|
1494
|
+
context->state->patternQuality = CAIRO_FILTER_BILINEAR;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/*
|
|
1499
|
+
* Get pattern quality.
|
|
1500
|
+
*/
|
|
1501
|
+
|
|
1502
|
+
NAN_GETTER(Context2d::GetPatternQuality) {
|
|
1503
|
+
CHECK_RECEIVER(Context2d.GetPatternQuality);
|
|
1504
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1505
|
+
const char *quality;
|
|
1506
|
+
switch (context->state->patternQuality) {
|
|
1507
|
+
case CAIRO_FILTER_FAST: quality = "fast"; break;
|
|
1508
|
+
case CAIRO_FILTER_BEST: quality = "best"; break;
|
|
1509
|
+
case CAIRO_FILTER_NEAREST: quality = "nearest"; break;
|
|
1510
|
+
case CAIRO_FILTER_BILINEAR: quality = "bilinear"; break;
|
|
1511
|
+
default: quality = "good";
|
|
1512
|
+
}
|
|
1513
|
+
info.GetReturnValue().Set(Nan::New(quality).ToLocalChecked());
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/*
|
|
1517
|
+
* Set ImageSmoothingEnabled value.
|
|
1518
|
+
*/
|
|
1519
|
+
|
|
1520
|
+
NAN_SETTER(Context2d::SetImageSmoothingEnabled) {
|
|
1521
|
+
CHECK_RECEIVER(Context2d.SetImageSmoothingEnabled);
|
|
1522
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1523
|
+
context->state->imageSmoothingEnabled = Nan::To<bool>(value).FromMaybe(false);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/*
|
|
1527
|
+
* Get pattern quality.
|
|
1528
|
+
*/
|
|
1529
|
+
|
|
1530
|
+
NAN_GETTER(Context2d::GetImageSmoothingEnabled) {
|
|
1531
|
+
CHECK_RECEIVER(Context2d.GetImageSmoothingEnabled);
|
|
1532
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1533
|
+
info.GetReturnValue().Set(Nan::New<Boolean>(context->state->imageSmoothingEnabled));
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/*
|
|
1537
|
+
* Set global composite operation.
|
|
1538
|
+
*/
|
|
1539
|
+
|
|
1540
|
+
NAN_SETTER(Context2d::SetGlobalCompositeOperation) {
|
|
1541
|
+
CHECK_RECEIVER(Context2d.SetGlobalCompositeOperation);
|
|
1542
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1543
|
+
cairo_t *ctx = context->context();
|
|
1544
|
+
Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked()); // Unlike CSS colors, this *is* case-sensitive
|
|
1545
|
+
const std::map<std::string, cairo_operator_t> blendmodes = {
|
|
1546
|
+
// composite modes:
|
|
1547
|
+
{"clear", CAIRO_OPERATOR_CLEAR},
|
|
1548
|
+
{"copy", CAIRO_OPERATOR_SOURCE},
|
|
1549
|
+
{"destination", CAIRO_OPERATOR_DEST}, // this seems to have been omitted from the spec
|
|
1550
|
+
{"source-over", CAIRO_OPERATOR_OVER},
|
|
1551
|
+
{"destination-over", CAIRO_OPERATOR_DEST_OVER},
|
|
1552
|
+
{"source-in", CAIRO_OPERATOR_IN},
|
|
1553
|
+
{"destination-in", CAIRO_OPERATOR_DEST_IN},
|
|
1554
|
+
{"source-out", CAIRO_OPERATOR_OUT},
|
|
1555
|
+
{"destination-out", CAIRO_OPERATOR_DEST_OUT},
|
|
1556
|
+
{"source-atop", CAIRO_OPERATOR_ATOP},
|
|
1557
|
+
{"destination-atop", CAIRO_OPERATOR_DEST_ATOP},
|
|
1558
|
+
{"xor", CAIRO_OPERATOR_XOR},
|
|
1559
|
+
{"lighter", CAIRO_OPERATOR_ADD},
|
|
1560
|
+
// blend modes:
|
|
1561
|
+
{"normal", CAIRO_OPERATOR_OVER},
|
|
1562
|
+
{"multiply", CAIRO_OPERATOR_MULTIPLY},
|
|
1563
|
+
{"screen", CAIRO_OPERATOR_SCREEN},
|
|
1564
|
+
{"overlay", CAIRO_OPERATOR_OVERLAY},
|
|
1565
|
+
{"darken", CAIRO_OPERATOR_DARKEN},
|
|
1566
|
+
{"lighten", CAIRO_OPERATOR_LIGHTEN},
|
|
1567
|
+
{"color-dodge", CAIRO_OPERATOR_COLOR_DODGE},
|
|
1568
|
+
{"color-burn", CAIRO_OPERATOR_COLOR_BURN},
|
|
1569
|
+
{"hard-light", CAIRO_OPERATOR_HARD_LIGHT},
|
|
1570
|
+
{"soft-light", CAIRO_OPERATOR_SOFT_LIGHT},
|
|
1571
|
+
{"difference", CAIRO_OPERATOR_DIFFERENCE},
|
|
1572
|
+
{"exclusion", CAIRO_OPERATOR_EXCLUSION},
|
|
1573
|
+
{"hue", CAIRO_OPERATOR_HSL_HUE},
|
|
1574
|
+
{"saturation", CAIRO_OPERATOR_HSL_SATURATION},
|
|
1575
|
+
{"color", CAIRO_OPERATOR_HSL_COLOR},
|
|
1576
|
+
{"luminosity", CAIRO_OPERATOR_HSL_LUMINOSITY},
|
|
1577
|
+
// non-standard:
|
|
1578
|
+
{"saturate", CAIRO_OPERATOR_SATURATE}
|
|
1579
|
+
};
|
|
1580
|
+
auto op = blendmodes.find(*opStr);
|
|
1581
|
+
if (op != blendmodes.end()) cairo_set_operator(ctx, op->second);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/*
|
|
1585
|
+
* Get shadow offset x.
|
|
1586
|
+
*/
|
|
1587
|
+
|
|
1588
|
+
NAN_GETTER(Context2d::GetShadowOffsetX) {
|
|
1589
|
+
CHECK_RECEIVER(Context2d.GetShadowOffsetX);
|
|
1590
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1591
|
+
info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowOffsetX));
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/*
|
|
1595
|
+
* Set shadow offset x.
|
|
1596
|
+
*/
|
|
1597
|
+
|
|
1598
|
+
NAN_SETTER(Context2d::SetShadowOffsetX) {
|
|
1599
|
+
CHECK_RECEIVER(Context2d.SetShadowOffsetX);
|
|
1600
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1601
|
+
context->state->shadowOffsetX = Nan::To<double>(value).FromMaybe(0);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
/*
|
|
1605
|
+
* Get shadow offset y.
|
|
1606
|
+
*/
|
|
1607
|
+
|
|
1608
|
+
NAN_GETTER(Context2d::GetShadowOffsetY) {
|
|
1609
|
+
CHECK_RECEIVER(Context2d.GetShadowOffsetY);
|
|
1610
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1611
|
+
info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowOffsetY));
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
/*
|
|
1615
|
+
* Set shadow offset y.
|
|
1616
|
+
*/
|
|
1617
|
+
|
|
1618
|
+
NAN_SETTER(Context2d::SetShadowOffsetY) {
|
|
1619
|
+
CHECK_RECEIVER(Context2d.SetShadowOffsetY);
|
|
1620
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1621
|
+
context->state->shadowOffsetY = Nan::To<double>(value).FromMaybe(0);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
/*
|
|
1625
|
+
* Get shadow blur.
|
|
1626
|
+
*/
|
|
1627
|
+
|
|
1628
|
+
NAN_GETTER(Context2d::GetShadowBlur) {
|
|
1629
|
+
CHECK_RECEIVER(Context2d.GetShadowBlur);
|
|
1630
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1631
|
+
info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowBlur));
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/*
|
|
1635
|
+
* Set shadow blur.
|
|
1636
|
+
*/
|
|
1637
|
+
|
|
1638
|
+
NAN_SETTER(Context2d::SetShadowBlur) {
|
|
1639
|
+
CHECK_RECEIVER(Context2d.SetShadowBlur);
|
|
1640
|
+
int n = Nan::To<double>(value).FromMaybe(0);
|
|
1641
|
+
if (n >= 0) {
|
|
1642
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1643
|
+
context->state->shadowBlur = n;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/*
|
|
1648
|
+
* Get current antialiasing setting.
|
|
1649
|
+
*/
|
|
1650
|
+
|
|
1651
|
+
NAN_GETTER(Context2d::GetAntiAlias) {
|
|
1652
|
+
CHECK_RECEIVER(Context2d.GetAntiAlias);
|
|
1653
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1654
|
+
const char *aa;
|
|
1655
|
+
switch (cairo_get_antialias(context->context())) {
|
|
1656
|
+
case CAIRO_ANTIALIAS_NONE: aa = "none"; break;
|
|
1657
|
+
case CAIRO_ANTIALIAS_GRAY: aa = "gray"; break;
|
|
1658
|
+
case CAIRO_ANTIALIAS_SUBPIXEL: aa = "subpixel"; break;
|
|
1659
|
+
default: aa = "default";
|
|
1660
|
+
}
|
|
1661
|
+
info.GetReturnValue().Set(Nan::New(aa).ToLocalChecked());
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
/*
|
|
1665
|
+
* Set antialiasing.
|
|
1666
|
+
*/
|
|
1667
|
+
|
|
1668
|
+
NAN_SETTER(Context2d::SetAntiAlias) {
|
|
1669
|
+
CHECK_RECEIVER(Context2d.SetAntiAlias);
|
|
1670
|
+
Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
|
|
1671
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1672
|
+
cairo_t *ctx = context->context();
|
|
1673
|
+
cairo_antialias_t a;
|
|
1674
|
+
if (0 == strcmp("none", *str)) {
|
|
1675
|
+
a = CAIRO_ANTIALIAS_NONE;
|
|
1676
|
+
} else if (0 == strcmp("default", *str)) {
|
|
1677
|
+
a = CAIRO_ANTIALIAS_DEFAULT;
|
|
1678
|
+
} else if (0 == strcmp("gray", *str)) {
|
|
1679
|
+
a = CAIRO_ANTIALIAS_GRAY;
|
|
1680
|
+
} else if (0 == strcmp("subpixel", *str)) {
|
|
1681
|
+
a = CAIRO_ANTIALIAS_SUBPIXEL;
|
|
1682
|
+
} else {
|
|
1683
|
+
a = cairo_get_antialias(ctx);
|
|
1684
|
+
}
|
|
1685
|
+
cairo_set_antialias(ctx, a);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/*
|
|
1689
|
+
* Get text drawing mode.
|
|
1690
|
+
*/
|
|
1691
|
+
|
|
1692
|
+
NAN_GETTER(Context2d::GetTextDrawingMode) {
|
|
1693
|
+
CHECK_RECEIVER(Context2d.GetTextDrawingMode);
|
|
1694
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1695
|
+
const char *mode;
|
|
1696
|
+
if (context->state->textDrawingMode == TEXT_DRAW_PATHS) {
|
|
1697
|
+
mode = "path";
|
|
1698
|
+
} else if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) {
|
|
1699
|
+
mode = "glyph";
|
|
1700
|
+
} else {
|
|
1701
|
+
mode = "unknown";
|
|
1702
|
+
}
|
|
1703
|
+
info.GetReturnValue().Set(Nan::New(mode).ToLocalChecked());
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/*
|
|
1707
|
+
* Set text drawing mode.
|
|
1708
|
+
*/
|
|
1709
|
+
|
|
1710
|
+
NAN_SETTER(Context2d::SetTextDrawingMode) {
|
|
1711
|
+
CHECK_RECEIVER(Context2d.SetTextDrawingMode);
|
|
1712
|
+
Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
|
|
1713
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1714
|
+
if (0 == strcmp("path", *str)) {
|
|
1715
|
+
context->state->textDrawingMode = TEXT_DRAW_PATHS;
|
|
1716
|
+
} else if (0 == strcmp("glyph", *str)) {
|
|
1717
|
+
context->state->textDrawingMode = TEXT_DRAW_GLYPHS;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/*
|
|
1722
|
+
* Get filter.
|
|
1723
|
+
*/
|
|
1724
|
+
|
|
1725
|
+
NAN_GETTER(Context2d::GetQuality) {
|
|
1726
|
+
CHECK_RECEIVER(Context2d.GetQuality);
|
|
1727
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1728
|
+
const char *filter;
|
|
1729
|
+
switch (cairo_pattern_get_filter(cairo_get_source(context->context()))) {
|
|
1730
|
+
case CAIRO_FILTER_FAST: filter = "fast"; break;
|
|
1731
|
+
case CAIRO_FILTER_BEST: filter = "best"; break;
|
|
1732
|
+
case CAIRO_FILTER_NEAREST: filter = "nearest"; break;
|
|
1733
|
+
case CAIRO_FILTER_BILINEAR: filter = "bilinear"; break;
|
|
1734
|
+
default: filter = "good";
|
|
1735
|
+
}
|
|
1736
|
+
info.GetReturnValue().Set(Nan::New(filter).ToLocalChecked());
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
/*
|
|
1740
|
+
* Set filter.
|
|
1741
|
+
*/
|
|
1742
|
+
|
|
1743
|
+
NAN_SETTER(Context2d::SetQuality) {
|
|
1744
|
+
CHECK_RECEIVER(Context2d.SetQuality);
|
|
1745
|
+
Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
|
|
1746
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1747
|
+
cairo_filter_t filter;
|
|
1748
|
+
if (0 == strcmp("fast", *str)) {
|
|
1749
|
+
filter = CAIRO_FILTER_FAST;
|
|
1750
|
+
} else if (0 == strcmp("best", *str)) {
|
|
1751
|
+
filter = CAIRO_FILTER_BEST;
|
|
1752
|
+
} else if (0 == strcmp("nearest", *str)) {
|
|
1753
|
+
filter = CAIRO_FILTER_NEAREST;
|
|
1754
|
+
} else if (0 == strcmp("bilinear", *str)) {
|
|
1755
|
+
filter = CAIRO_FILTER_BILINEAR;
|
|
1756
|
+
} else {
|
|
1757
|
+
filter = CAIRO_FILTER_GOOD;
|
|
1758
|
+
}
|
|
1759
|
+
cairo_pattern_set_filter(cairo_get_source(context->context()), filter);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/*
|
|
1763
|
+
* Helper for get current transform matrix
|
|
1764
|
+
*/
|
|
1765
|
+
|
|
1766
|
+
Local<Object>
|
|
1767
|
+
get_current_transform(Context2d *context) {
|
|
1768
|
+
Isolate *iso = Isolate::GetCurrent();
|
|
1769
|
+
|
|
1770
|
+
Local<Float64Array> arr = Float64Array::New(ArrayBuffer::New(iso, 48), 0, 6);
|
|
1771
|
+
Nan::TypedArrayContents<double> dest(arr);
|
|
1772
|
+
cairo_matrix_t matrix;
|
|
1773
|
+
cairo_get_matrix(context->context(), &matrix);
|
|
1774
|
+
(*dest)[0] = matrix.xx;
|
|
1775
|
+
(*dest)[1] = matrix.yx;
|
|
1776
|
+
(*dest)[2] = matrix.xy;
|
|
1777
|
+
(*dest)[3] = matrix.yy;
|
|
1778
|
+
(*dest)[4] = matrix.x0;
|
|
1779
|
+
(*dest)[5] = matrix.y0;
|
|
1780
|
+
|
|
1781
|
+
const int argc = 1;
|
|
1782
|
+
Local<Value> argv[argc] = { arr };
|
|
1783
|
+
return Nan::NewInstance(context->_DOMMatrix.Get(iso), argc, argv).ToLocalChecked();
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
/*
|
|
1787
|
+
* Helper for get/set transform.
|
|
1788
|
+
*/
|
|
1789
|
+
|
|
1790
|
+
void parse_matrix_from_object(cairo_matrix_t &matrix, Local<Object> mat) {
|
|
1791
|
+
cairo_matrix_init(&matrix,
|
|
1792
|
+
Nan::To<double>(Nan::Get(mat, Nan::New("a").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
|
|
1793
|
+
Nan::To<double>(Nan::Get(mat, Nan::New("b").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
|
|
1794
|
+
Nan::To<double>(Nan::Get(mat, Nan::New("c").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
|
|
1795
|
+
Nan::To<double>(Nan::Get(mat, Nan::New("d").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
|
|
1796
|
+
Nan::To<double>(Nan::Get(mat, Nan::New("e").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
|
|
1797
|
+
Nan::To<double>(Nan::Get(mat, Nan::New("f").ToLocalChecked()).ToLocalChecked()).FromMaybe(0)
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
|
|
1802
|
+
/*
|
|
1803
|
+
* Get current transform.
|
|
1804
|
+
*/
|
|
1805
|
+
|
|
1806
|
+
NAN_GETTER(Context2d::GetCurrentTransform) {
|
|
1807
|
+
CHECK_RECEIVER(Context2d.GetCurrentTransform);
|
|
1808
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1809
|
+
Local<Object> instance = get_current_transform(context);
|
|
1810
|
+
|
|
1811
|
+
info.GetReturnValue().Set(instance);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
/*
|
|
1815
|
+
* Set current transform.
|
|
1816
|
+
*/
|
|
1817
|
+
|
|
1818
|
+
NAN_SETTER(Context2d::SetCurrentTransform) {
|
|
1819
|
+
CHECK_RECEIVER(Context2d.SetCurrentTransform);
|
|
1820
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1821
|
+
Local<Context> ctx = Nan::GetCurrentContext();
|
|
1822
|
+
Local<Object> mat = Nan::To<Object>(value).ToLocalChecked();
|
|
1823
|
+
|
|
1824
|
+
#if NODE_MAJOR_VERSION >= 8
|
|
1825
|
+
if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
|
|
1826
|
+
return Nan::ThrowTypeError("Expected DOMMatrix");
|
|
1827
|
+
}
|
|
1828
|
+
#endif
|
|
1829
|
+
|
|
1830
|
+
cairo_matrix_t matrix;
|
|
1831
|
+
parse_matrix_from_object(matrix, mat);
|
|
1832
|
+
|
|
1833
|
+
cairo_transform(context->context(), &matrix);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
/*
|
|
1837
|
+
* Get current fill style.
|
|
1838
|
+
*/
|
|
1839
|
+
|
|
1840
|
+
NAN_GETTER(Context2d::GetFillStyle) {
|
|
1841
|
+
CHECK_RECEIVER(Context2d.GetFillStyle);
|
|
1842
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1843
|
+
Isolate *iso = Isolate::GetCurrent();
|
|
1844
|
+
Local<Value> style;
|
|
1845
|
+
|
|
1846
|
+
if (context->_fillStyle.IsEmpty())
|
|
1847
|
+
style = context->_getFillColor();
|
|
1848
|
+
else
|
|
1849
|
+
style = context->_fillStyle.Get(iso);
|
|
1850
|
+
|
|
1851
|
+
info.GetReturnValue().Set(style);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
/*
|
|
1855
|
+
* Set current fill style.
|
|
1856
|
+
*/
|
|
1857
|
+
|
|
1858
|
+
NAN_SETTER(Context2d::SetFillStyle) {
|
|
1859
|
+
CHECK_RECEIVER(Context2d.SetFillStyle);
|
|
1860
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1861
|
+
|
|
1862
|
+
if (value->IsString()) {
|
|
1863
|
+
MaybeLocal<String> mstr = Nan::To<String>(value);
|
|
1864
|
+
if (mstr.IsEmpty()) return;
|
|
1865
|
+
Local<String> str = mstr.ToLocalChecked();
|
|
1866
|
+
context->_fillStyle.Reset();
|
|
1867
|
+
context->_setFillColor(str);
|
|
1868
|
+
} else if (value->IsObject()) {
|
|
1869
|
+
Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
|
|
1870
|
+
if (Nan::New(Gradient::constructor)->HasInstance(obj)) {
|
|
1871
|
+
context->_fillStyle.Reset(value);
|
|
1872
|
+
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
|
|
1873
|
+
context->state->fillGradient = grad->pattern();
|
|
1874
|
+
} else if (Nan::New(Pattern::constructor)->HasInstance(obj)) {
|
|
1875
|
+
context->_fillStyle.Reset(value);
|
|
1876
|
+
Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(obj);
|
|
1877
|
+
context->state->fillPattern = pattern->pattern();
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
/*
|
|
1883
|
+
* Get current stroke style.
|
|
1884
|
+
*/
|
|
1885
|
+
|
|
1886
|
+
NAN_GETTER(Context2d::GetStrokeStyle) {
|
|
1887
|
+
CHECK_RECEIVER(Context2d.GetStrokeStyle);
|
|
1888
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1889
|
+
Local<Value> style;
|
|
1890
|
+
|
|
1891
|
+
if (context->_strokeStyle.IsEmpty())
|
|
1892
|
+
style = context->_getStrokeColor();
|
|
1893
|
+
else
|
|
1894
|
+
style = context->_strokeStyle.Get(Isolate::GetCurrent());
|
|
1895
|
+
|
|
1896
|
+
info.GetReturnValue().Set(style);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/*
|
|
1900
|
+
* Set current stroke style.
|
|
1901
|
+
*/
|
|
1902
|
+
|
|
1903
|
+
NAN_SETTER(Context2d::SetStrokeStyle) {
|
|
1904
|
+
CHECK_RECEIVER(Context2d.SetStrokeStyle);
|
|
1905
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1906
|
+
|
|
1907
|
+
if (value->IsString()) {
|
|
1908
|
+
MaybeLocal<String> mstr = Nan::To<String>(value);
|
|
1909
|
+
if (mstr.IsEmpty()) return;
|
|
1910
|
+
Local<String> str = mstr.ToLocalChecked();
|
|
1911
|
+
context->_strokeStyle.Reset();
|
|
1912
|
+
context->_setStrokeColor(str);
|
|
1913
|
+
} else if (value->IsObject()) {
|
|
1914
|
+
Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
|
|
1915
|
+
if (Nan::New(Gradient::constructor)->HasInstance(obj)) {
|
|
1916
|
+
context->_strokeStyle.Reset(value);
|
|
1917
|
+
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
|
|
1918
|
+
context->state->strokeGradient = grad->pattern();
|
|
1919
|
+
} else if (Nan::New(Pattern::constructor)->HasInstance(obj)) {
|
|
1920
|
+
context->_strokeStyle.Reset(value);
|
|
1921
|
+
Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(obj);
|
|
1922
|
+
context->state->strokePattern = pattern->pattern();
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
/*
|
|
1928
|
+
* Get miter limit.
|
|
1929
|
+
*/
|
|
1930
|
+
|
|
1931
|
+
NAN_GETTER(Context2d::GetMiterLimit) {
|
|
1932
|
+
CHECK_RECEIVER(Context2d.GetMiterLimit);
|
|
1933
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1934
|
+
info.GetReturnValue().Set(Nan::New<Number>(cairo_get_miter_limit(context->context())));
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
/*
|
|
1938
|
+
* Set miter limit.
|
|
1939
|
+
*/
|
|
1940
|
+
|
|
1941
|
+
NAN_SETTER(Context2d::SetMiterLimit) {
|
|
1942
|
+
CHECK_RECEIVER(Context2d.SetMiterLimit);
|
|
1943
|
+
double n = Nan::To<double>(value).FromMaybe(0);
|
|
1944
|
+
if (n > 0) {
|
|
1945
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1946
|
+
cairo_set_miter_limit(context->context(), n);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/*
|
|
1951
|
+
* Get line width.
|
|
1952
|
+
*/
|
|
1953
|
+
|
|
1954
|
+
NAN_GETTER(Context2d::GetLineWidth) {
|
|
1955
|
+
CHECK_RECEIVER(Context2d.GetLineWidth);
|
|
1956
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1957
|
+
info.GetReturnValue().Set(Nan::New<Number>(cairo_get_line_width(context->context())));
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
/*
|
|
1961
|
+
* Set line width.
|
|
1962
|
+
*/
|
|
1963
|
+
|
|
1964
|
+
NAN_SETTER(Context2d::SetLineWidth) {
|
|
1965
|
+
CHECK_RECEIVER(Context2d.SetLineWidth);
|
|
1966
|
+
double n = Nan::To<double>(value).FromMaybe(0);
|
|
1967
|
+
if (n > 0 && n != std::numeric_limits<double>::infinity()) {
|
|
1968
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1969
|
+
cairo_set_line_width(context->context(), n);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/*
|
|
1974
|
+
* Get line join.
|
|
1975
|
+
*/
|
|
1976
|
+
|
|
1977
|
+
NAN_GETTER(Context2d::GetLineJoin) {
|
|
1978
|
+
CHECK_RECEIVER(Context2d.GetLineJoin);
|
|
1979
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1980
|
+
const char *join;
|
|
1981
|
+
switch (cairo_get_line_join(context->context())) {
|
|
1982
|
+
case CAIRO_LINE_JOIN_BEVEL: join = "bevel"; break;
|
|
1983
|
+
case CAIRO_LINE_JOIN_ROUND: join = "round"; break;
|
|
1984
|
+
default: join = "miter";
|
|
1985
|
+
}
|
|
1986
|
+
info.GetReturnValue().Set(Nan::New(join).ToLocalChecked());
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
/*
|
|
1990
|
+
* Set line join.
|
|
1991
|
+
*/
|
|
1992
|
+
|
|
1993
|
+
NAN_SETTER(Context2d::SetLineJoin) {
|
|
1994
|
+
CHECK_RECEIVER(Context2d.SetLineJoin);
|
|
1995
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
1996
|
+
cairo_t *ctx = context->context();
|
|
1997
|
+
Nan::Utf8String type(Nan::To<String>(value).ToLocalChecked());
|
|
1998
|
+
if (0 == strcmp("round", *type)) {
|
|
1999
|
+
cairo_set_line_join(ctx, CAIRO_LINE_JOIN_ROUND);
|
|
2000
|
+
} else if (0 == strcmp("bevel", *type)) {
|
|
2001
|
+
cairo_set_line_join(ctx, CAIRO_LINE_JOIN_BEVEL);
|
|
2002
|
+
} else {
|
|
2003
|
+
cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
/*
|
|
2008
|
+
* Get line cap.
|
|
2009
|
+
*/
|
|
2010
|
+
|
|
2011
|
+
NAN_GETTER(Context2d::GetLineCap) {
|
|
2012
|
+
CHECK_RECEIVER(Context2d.GetLineCap);
|
|
2013
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2014
|
+
const char *cap;
|
|
2015
|
+
switch (cairo_get_line_cap(context->context())) {
|
|
2016
|
+
case CAIRO_LINE_CAP_ROUND: cap = "round"; break;
|
|
2017
|
+
case CAIRO_LINE_CAP_SQUARE: cap = "square"; break;
|
|
2018
|
+
default: cap = "butt";
|
|
2019
|
+
}
|
|
2020
|
+
info.GetReturnValue().Set(Nan::New(cap).ToLocalChecked());
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/*
|
|
2024
|
+
* Set line cap.
|
|
2025
|
+
*/
|
|
2026
|
+
|
|
2027
|
+
NAN_SETTER(Context2d::SetLineCap) {
|
|
2028
|
+
CHECK_RECEIVER(Context2d.SetLineCap);
|
|
2029
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2030
|
+
cairo_t *ctx = context->context();
|
|
2031
|
+
Nan::Utf8String type(Nan::To<String>(value).ToLocalChecked());
|
|
2032
|
+
if (0 == strcmp("round", *type)) {
|
|
2033
|
+
cairo_set_line_cap(ctx, CAIRO_LINE_CAP_ROUND);
|
|
2034
|
+
} else if (0 == strcmp("square", *type)) {
|
|
2035
|
+
cairo_set_line_cap(ctx, CAIRO_LINE_CAP_SQUARE);
|
|
2036
|
+
} else {
|
|
2037
|
+
cairo_set_line_cap(ctx, CAIRO_LINE_CAP_BUTT);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
/*
|
|
2042
|
+
* Check if the given point is within the current path.
|
|
2043
|
+
*/
|
|
2044
|
+
|
|
2045
|
+
NAN_METHOD(Context2d::IsPointInPath) {
|
|
2046
|
+
if (info[0]->IsNumber() && info[1]->IsNumber()) {
|
|
2047
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2048
|
+
cairo_t *ctx = context->context();
|
|
2049
|
+
double x = Nan::To<double>(info[0]).FromMaybe(0)
|
|
2050
|
+
, y = Nan::To<double>(info[1]).FromMaybe(0);
|
|
2051
|
+
context->setFillRule(info[2]);
|
|
2052
|
+
info.GetReturnValue().Set(Nan::New<Boolean>(cairo_in_fill(ctx, x, y) || cairo_in_stroke(ctx, x, y)));
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
info.GetReturnValue().Set(Nan::False());
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
/*
|
|
2059
|
+
* Set shadow color.
|
|
2060
|
+
*/
|
|
2061
|
+
|
|
2062
|
+
NAN_SETTER(Context2d::SetShadowColor) {
|
|
2063
|
+
CHECK_RECEIVER(Context2d.SetShadowColor);
|
|
2064
|
+
short ok;
|
|
2065
|
+
Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
|
|
2066
|
+
uint32_t rgba = rgba_from_string(*str, &ok);
|
|
2067
|
+
if (ok) {
|
|
2068
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2069
|
+
context->state->shadow = rgba_create(rgba);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
/*
|
|
2074
|
+
* Get shadow color.
|
|
2075
|
+
*/
|
|
2076
|
+
|
|
2077
|
+
NAN_GETTER(Context2d::GetShadowColor) {
|
|
2078
|
+
CHECK_RECEIVER(Context2d.GetShadowColor);
|
|
2079
|
+
char buf[64];
|
|
2080
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2081
|
+
rgba_to_string(context->state->shadow, buf, sizeof(buf));
|
|
2082
|
+
info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
/*
|
|
2086
|
+
* Set fill color, used internally for fillStyle=
|
|
2087
|
+
*/
|
|
2088
|
+
|
|
2089
|
+
void Context2d::_setFillColor(Local<Value> arg) {
|
|
2090
|
+
short ok;
|
|
2091
|
+
Nan::Utf8String str(arg);
|
|
2092
|
+
uint32_t rgba = rgba_from_string(*str, &ok);
|
|
2093
|
+
if (!ok) return;
|
|
2094
|
+
state->fillPattern = state->fillGradient = NULL;
|
|
2095
|
+
state->fill = rgba_create(rgba);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
/*
|
|
2099
|
+
* Get fill color.
|
|
2100
|
+
*/
|
|
2101
|
+
|
|
2102
|
+
Local<Value> Context2d::_getFillColor() {
|
|
2103
|
+
char buf[64];
|
|
2104
|
+
rgba_to_string(state->fill, buf, sizeof(buf));
|
|
2105
|
+
return Nan::New<String>(buf).ToLocalChecked();
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
/*
|
|
2109
|
+
* Set stroke color, used internally for strokeStyle=
|
|
2110
|
+
*/
|
|
2111
|
+
|
|
2112
|
+
void Context2d::_setStrokeColor(Local<Value> arg) {
|
|
2113
|
+
short ok;
|
|
2114
|
+
Nan::Utf8String str(arg);
|
|
2115
|
+
uint32_t rgba = rgba_from_string(*str, &ok);
|
|
2116
|
+
if (!ok) return;
|
|
2117
|
+
state->strokePattern = state->strokeGradient = NULL;
|
|
2118
|
+
state->stroke = rgba_create(rgba);
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
/*
|
|
2122
|
+
* Get stroke color.
|
|
2123
|
+
*/
|
|
2124
|
+
|
|
2125
|
+
Local<Value> Context2d::_getStrokeColor() {
|
|
2126
|
+
char buf[64];
|
|
2127
|
+
rgba_to_string(state->stroke, buf, sizeof(buf));
|
|
2128
|
+
return Nan::New<String>(buf).ToLocalChecked();
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
NAN_METHOD(Context2d::CreatePattern) {
|
|
2132
|
+
Local<Value> image = info[0];
|
|
2133
|
+
Local<Value> repetition = info[1];
|
|
2134
|
+
|
|
2135
|
+
if (!Nan::To<bool>(repetition).FromMaybe(false))
|
|
2136
|
+
repetition = Nan::New("repeat").ToLocalChecked();
|
|
2137
|
+
|
|
2138
|
+
const int argc = 2;
|
|
2139
|
+
Local<Value> argv[argc] = { image, repetition };
|
|
2140
|
+
|
|
2141
|
+
Local<Function> ctor = Nan::GetFunction(Nan::New(Pattern::constructor)).ToLocalChecked();
|
|
2142
|
+
Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
|
|
2143
|
+
|
|
2144
|
+
info.GetReturnValue().Set(instance);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
NAN_METHOD(Context2d::CreateLinearGradient) {
|
|
2148
|
+
const int argc = 4;
|
|
2149
|
+
Local<Value> argv[argc] = { info[0], info[1], info[2], info[3] };
|
|
2150
|
+
|
|
2151
|
+
Local<Function> ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked();
|
|
2152
|
+
Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
|
|
2153
|
+
|
|
2154
|
+
info.GetReturnValue().Set(instance);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
NAN_METHOD(Context2d::CreateRadialGradient) {
|
|
2158
|
+
const int argc = 6;
|
|
2159
|
+
Local<Value> argv[argc] = { info[0], info[1], info[2], info[3], info[4], info[5] };
|
|
2160
|
+
|
|
2161
|
+
Local<Function> ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked();
|
|
2162
|
+
Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
|
|
2163
|
+
|
|
2164
|
+
info.GetReturnValue().Set(instance);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
/*
|
|
2168
|
+
* Bezier curve.
|
|
2169
|
+
*/
|
|
2170
|
+
|
|
2171
|
+
NAN_METHOD(Context2d::BezierCurveTo) {
|
|
2172
|
+
double args[6];
|
|
2173
|
+
if(!checkArgs(info, args, 6))
|
|
2174
|
+
return;
|
|
2175
|
+
|
|
2176
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2177
|
+
cairo_curve_to(context->context()
|
|
2178
|
+
, args[0]
|
|
2179
|
+
, args[1]
|
|
2180
|
+
, args[2]
|
|
2181
|
+
, args[3]
|
|
2182
|
+
, args[4]
|
|
2183
|
+
, args[5]);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
/*
|
|
2187
|
+
* Quadratic curve approximation from libsvg-cairo.
|
|
2188
|
+
*/
|
|
2189
|
+
|
|
2190
|
+
NAN_METHOD(Context2d::QuadraticCurveTo) {
|
|
2191
|
+
double args[4];
|
|
2192
|
+
if(!checkArgs(info, args, 4))
|
|
2193
|
+
return;
|
|
2194
|
+
|
|
2195
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2196
|
+
cairo_t *ctx = context->context();
|
|
2197
|
+
|
|
2198
|
+
double x, y
|
|
2199
|
+
, x1 = args[0]
|
|
2200
|
+
, y1 = args[1]
|
|
2201
|
+
, x2 = args[2]
|
|
2202
|
+
, y2 = args[3];
|
|
2203
|
+
|
|
2204
|
+
cairo_get_current_point(ctx, &x, &y);
|
|
2205
|
+
|
|
2206
|
+
if (0 == x && 0 == y) {
|
|
2207
|
+
x = x1;
|
|
2208
|
+
y = y1;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
cairo_curve_to(ctx
|
|
2212
|
+
, x + 2.0 / 3.0 * (x1 - x), y + 2.0 / 3.0 * (y1 - y)
|
|
2213
|
+
, x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2)
|
|
2214
|
+
, x2
|
|
2215
|
+
, y2);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/*
|
|
2219
|
+
* Save state.
|
|
2220
|
+
*/
|
|
2221
|
+
|
|
2222
|
+
NAN_METHOD(Context2d::Save) {
|
|
2223
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2224
|
+
context->save();
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
/*
|
|
2228
|
+
* Restore state.
|
|
2229
|
+
*/
|
|
2230
|
+
|
|
2231
|
+
NAN_METHOD(Context2d::Restore) {
|
|
2232
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2233
|
+
context->restore();
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
/*
|
|
2237
|
+
* Creates a new subpath.
|
|
2238
|
+
*/
|
|
2239
|
+
|
|
2240
|
+
NAN_METHOD(Context2d::BeginPath) {
|
|
2241
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2242
|
+
cairo_new_path(context->context());
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
/*
|
|
2246
|
+
* Marks the subpath as closed.
|
|
2247
|
+
*/
|
|
2248
|
+
|
|
2249
|
+
NAN_METHOD(Context2d::ClosePath) {
|
|
2250
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2251
|
+
cairo_close_path(context->context());
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
/*
|
|
2255
|
+
* Rotate transformation.
|
|
2256
|
+
*/
|
|
2257
|
+
|
|
2258
|
+
NAN_METHOD(Context2d::Rotate) {
|
|
2259
|
+
double args[1];
|
|
2260
|
+
if(!checkArgs(info, args, 1))
|
|
2261
|
+
return;
|
|
2262
|
+
|
|
2263
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2264
|
+
cairo_rotate(context->context(), args[0]);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
/*
|
|
2268
|
+
* Modify the CTM.
|
|
2269
|
+
*/
|
|
2270
|
+
|
|
2271
|
+
NAN_METHOD(Context2d::Transform) {
|
|
2272
|
+
double args[6];
|
|
2273
|
+
if(!checkArgs(info, args, 6))
|
|
2274
|
+
return;
|
|
2275
|
+
|
|
2276
|
+
cairo_matrix_t matrix;
|
|
2277
|
+
cairo_matrix_init(&matrix
|
|
2278
|
+
, args[0]
|
|
2279
|
+
, args[1]
|
|
2280
|
+
, args[2]
|
|
2281
|
+
, args[3]
|
|
2282
|
+
, args[4]
|
|
2283
|
+
, args[5]);
|
|
2284
|
+
|
|
2285
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2286
|
+
cairo_transform(context->context(), &matrix);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
/*
|
|
2290
|
+
* Get the CTM
|
|
2291
|
+
*/
|
|
2292
|
+
|
|
2293
|
+
NAN_METHOD(Context2d::GetTransform) {
|
|
2294
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2295
|
+
Local<Object> instance = get_current_transform(context);
|
|
2296
|
+
|
|
2297
|
+
info.GetReturnValue().Set(instance);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
/*
|
|
2301
|
+
* Reset the CTM, used internally by setTransform().
|
|
2302
|
+
*/
|
|
2303
|
+
|
|
2304
|
+
NAN_METHOD(Context2d::ResetTransform) {
|
|
2305
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2306
|
+
cairo_identity_matrix(context->context());
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/*
|
|
2310
|
+
* Reset transform matrix to identity, then apply the given args.
|
|
2311
|
+
*/
|
|
2312
|
+
|
|
2313
|
+
NAN_METHOD(Context2d::SetTransform) {
|
|
2314
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2315
|
+
if (info.Length() == 1) {
|
|
2316
|
+
Local<Object> mat = Nan::To<Object>(info[0]).ToLocalChecked();
|
|
2317
|
+
|
|
2318
|
+
#if NODE_MAJOR_VERSION >= 8
|
|
2319
|
+
Local<Context> ctx = Nan::GetCurrentContext();
|
|
2320
|
+
if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
|
|
2321
|
+
return Nan::ThrowTypeError("Expected DOMMatrix");
|
|
2322
|
+
}
|
|
2323
|
+
#endif
|
|
2324
|
+
|
|
2325
|
+
cairo_matrix_t matrix;
|
|
2326
|
+
parse_matrix_from_object(matrix, mat);
|
|
2327
|
+
|
|
2328
|
+
cairo_set_matrix(context->context(), &matrix);
|
|
2329
|
+
} else {
|
|
2330
|
+
cairo_identity_matrix(context->context());
|
|
2331
|
+
Context2d::Transform(info);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/*
|
|
2336
|
+
* Translate transformation.
|
|
2337
|
+
*/
|
|
2338
|
+
|
|
2339
|
+
NAN_METHOD(Context2d::Translate) {
|
|
2340
|
+
double args[2];
|
|
2341
|
+
if(!checkArgs(info, args, 2))
|
|
2342
|
+
return;
|
|
2343
|
+
|
|
2344
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2345
|
+
cairo_translate(context->context(), args[0], args[1]);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
/*
|
|
2349
|
+
* Scale transformation.
|
|
2350
|
+
*/
|
|
2351
|
+
|
|
2352
|
+
NAN_METHOD(Context2d::Scale) {
|
|
2353
|
+
double args[2];
|
|
2354
|
+
if(!checkArgs(info, args, 2))
|
|
2355
|
+
return;
|
|
2356
|
+
|
|
2357
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2358
|
+
cairo_scale(context->context(), args[0], args[1]);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
/*
|
|
2362
|
+
* Use path as clipping region.
|
|
2363
|
+
*/
|
|
2364
|
+
|
|
2365
|
+
NAN_METHOD(Context2d::Clip) {
|
|
2366
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2367
|
+
context->setFillRule(info[0]);
|
|
2368
|
+
cairo_t *ctx = context->context();
|
|
2369
|
+
cairo_clip_preserve(ctx);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
/*
|
|
2373
|
+
* Fill the path.
|
|
2374
|
+
*/
|
|
2375
|
+
|
|
2376
|
+
NAN_METHOD(Context2d::Fill) {
|
|
2377
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2378
|
+
context->setFillRule(info[0]);
|
|
2379
|
+
context->fill(true);
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
/*
|
|
2383
|
+
* Stroke the path.
|
|
2384
|
+
*/
|
|
2385
|
+
|
|
2386
|
+
NAN_METHOD(Context2d::Stroke) {
|
|
2387
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2388
|
+
context->stroke(true);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
/*
|
|
2392
|
+
* Helper for fillText/strokeText
|
|
2393
|
+
*/
|
|
2394
|
+
|
|
2395
|
+
double
|
|
2396
|
+
get_text_scale(PangoLayout *layout, double maxWidth) {
|
|
2397
|
+
|
|
2398
|
+
PangoRectangle logical_rect;
|
|
2399
|
+
pango_layout_get_pixel_extents(layout, NULL, &logical_rect);
|
|
2400
|
+
|
|
2401
|
+
if (logical_rect.width > maxWidth) {
|
|
2402
|
+
return maxWidth / logical_rect.width;
|
|
2403
|
+
} else {
|
|
2404
|
+
return 1.0;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
void
|
|
2409
|
+
paintText(const Nan::FunctionCallbackInfo<Value> &info, bool stroke) {
|
|
2410
|
+
int argsNum = info.Length() >= 4 ? 3 : 2;
|
|
2411
|
+
|
|
2412
|
+
if (argsNum == 3 && info[3]->IsUndefined())
|
|
2413
|
+
argsNum = 2;
|
|
2414
|
+
|
|
2415
|
+
double args[3];
|
|
2416
|
+
if(!checkArgs(info, args, argsNum, 1))
|
|
2417
|
+
return;
|
|
2418
|
+
|
|
2419
|
+
Nan::Utf8String str(Nan::To<String>(info[0]).ToLocalChecked());
|
|
2420
|
+
double x = args[0];
|
|
2421
|
+
double y = args[1];
|
|
2422
|
+
double scaled_by = 1;
|
|
2423
|
+
|
|
2424
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2425
|
+
PangoLayout *layout = context->layout();
|
|
2426
|
+
|
|
2427
|
+
pango_layout_set_text(layout, *str, -1);
|
|
2428
|
+
pango_cairo_update_layout(context->context(), layout);
|
|
2429
|
+
|
|
2430
|
+
if (argsNum == 3) {
|
|
2431
|
+
scaled_by = get_text_scale(layout, args[2]);
|
|
2432
|
+
cairo_save(context->context());
|
|
2433
|
+
cairo_scale(context->context(), scaled_by, 1);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
context->savePath();
|
|
2437
|
+
if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) {
|
|
2438
|
+
if (stroke == true) { context->stroke(); } else { context->fill(); }
|
|
2439
|
+
context->setTextPath(x / scaled_by, y);
|
|
2440
|
+
} else if (context->state->textDrawingMode == TEXT_DRAW_PATHS) {
|
|
2441
|
+
context->setTextPath(x / scaled_by, y);
|
|
2442
|
+
if (stroke == true) { context->stroke(); } else { context->fill(); }
|
|
2443
|
+
}
|
|
2444
|
+
context->restorePath();
|
|
2445
|
+
if (argsNum == 3) {
|
|
2446
|
+
cairo_restore(context->context());
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
/*
|
|
2451
|
+
* Fill text at (x, y).
|
|
2452
|
+
*/
|
|
2453
|
+
|
|
2454
|
+
NAN_METHOD(Context2d::FillText) {
|
|
2455
|
+
paintText(info, false);
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
/*
|
|
2459
|
+
* Stroke text at (x ,y).
|
|
2460
|
+
*/
|
|
2461
|
+
|
|
2462
|
+
NAN_METHOD(Context2d::StrokeText) {
|
|
2463
|
+
paintText(info, true);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
/*
|
|
2467
|
+
* Gets the baseline adjustment in device pixels
|
|
2468
|
+
*/
|
|
2469
|
+
inline double getBaselineAdjustment(PangoLayout* layout, short baseline) {
|
|
2470
|
+
PangoRectangle logical_rect;
|
|
2471
|
+
pango_layout_line_get_extents(pango_layout_get_line(layout, 0), NULL, &logical_rect);
|
|
2472
|
+
|
|
2473
|
+
double scale = 1.0 / PANGO_SCALE;
|
|
2474
|
+
double ascent = scale * pango_layout_get_baseline(layout);
|
|
2475
|
+
double descent = scale * logical_rect.height - ascent;
|
|
2476
|
+
|
|
2477
|
+
switch (baseline) {
|
|
2478
|
+
case TEXT_BASELINE_ALPHABETIC:
|
|
2479
|
+
return ascent;
|
|
2480
|
+
case TEXT_BASELINE_MIDDLE:
|
|
2481
|
+
return (ascent + descent) / 2.0;
|
|
2482
|
+
case TEXT_BASELINE_BOTTOM:
|
|
2483
|
+
return ascent + descent;
|
|
2484
|
+
default:
|
|
2485
|
+
return 0;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
/*
|
|
2490
|
+
* Set text path for the string in the layout at (x, y).
|
|
2491
|
+
* This function is called by paintText and won't behave correctly
|
|
2492
|
+
* if is not called from there.
|
|
2493
|
+
* it needs pango_layout_set_text and pango_cairo_update_layout to be called before
|
|
2494
|
+
*/
|
|
2495
|
+
|
|
2496
|
+
void
|
|
2497
|
+
Context2d::setTextPath(double x, double y) {
|
|
2498
|
+
PangoRectangle logical_rect;
|
|
2499
|
+
|
|
2500
|
+
switch (state->textAlignment) {
|
|
2501
|
+
case TEXT_ALIGNMENT_CENTER:
|
|
2502
|
+
pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
|
|
2503
|
+
x -= logical_rect.width / 2;
|
|
2504
|
+
break;
|
|
2505
|
+
case TEXT_ALIGNMENT_END:
|
|
2506
|
+
case TEXT_ALIGNMENT_RIGHT:
|
|
2507
|
+
pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
|
|
2508
|
+
x -= logical_rect.width;
|
|
2509
|
+
break;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
y -= getBaselineAdjustment(_layout, state->textBaseline);
|
|
2513
|
+
|
|
2514
|
+
cairo_move_to(_context, x, y);
|
|
2515
|
+
if (state->textDrawingMode == TEXT_DRAW_PATHS) {
|
|
2516
|
+
pango_cairo_layout_path(_context, _layout);
|
|
2517
|
+
} else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
|
|
2518
|
+
pango_cairo_show_layout(_context, _layout);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
/*
|
|
2523
|
+
* Adds a point to the current subpath.
|
|
2524
|
+
*/
|
|
2525
|
+
|
|
2526
|
+
NAN_METHOD(Context2d::LineTo) {
|
|
2527
|
+
double args[2];
|
|
2528
|
+
if(!checkArgs(info, args, 2))
|
|
2529
|
+
return;
|
|
2530
|
+
|
|
2531
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2532
|
+
cairo_line_to(context->context(), args[0], args[1]);
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
/*
|
|
2536
|
+
* Creates a new subpath at the given point.
|
|
2537
|
+
*/
|
|
2538
|
+
|
|
2539
|
+
NAN_METHOD(Context2d::MoveTo) {
|
|
2540
|
+
double args[2];
|
|
2541
|
+
if(!checkArgs(info, args, 2))
|
|
2542
|
+
return;
|
|
2543
|
+
|
|
2544
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2545
|
+
cairo_move_to(context->context(), args[0], args[1]);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
/*
|
|
2549
|
+
* Get font.
|
|
2550
|
+
*/
|
|
2551
|
+
|
|
2552
|
+
NAN_GETTER(Context2d::GetFont) {
|
|
2553
|
+
CHECK_RECEIVER(Context2d.GetFont);
|
|
2554
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2555
|
+
|
|
2556
|
+
info.GetReturnValue().Set(Nan::New(context->state->font).ToLocalChecked());
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
/*
|
|
2560
|
+
* Set font:
|
|
2561
|
+
* - weight
|
|
2562
|
+
* - style
|
|
2563
|
+
* - size
|
|
2564
|
+
* - unit
|
|
2565
|
+
* - family
|
|
2566
|
+
*/
|
|
2567
|
+
|
|
2568
|
+
NAN_SETTER(Context2d::SetFont) {
|
|
2569
|
+
CHECK_RECEIVER(Context2d.SetFont);
|
|
2570
|
+
if (!value->IsString()) return;
|
|
2571
|
+
|
|
2572
|
+
Isolate *iso = Isolate::GetCurrent();
|
|
2573
|
+
Local<Context> ctx = Nan::GetCurrentContext();
|
|
2574
|
+
|
|
2575
|
+
Local<String> str = Nan::To<String>(value).ToLocalChecked();
|
|
2576
|
+
if (!str->Length()) return;
|
|
2577
|
+
|
|
2578
|
+
const int argc = 1;
|
|
2579
|
+
Local<Value> argv[argc] = { value };
|
|
2580
|
+
|
|
2581
|
+
Local<Value> mparsed = Nan::Call(_parseFont.Get(iso), ctx->Global(), argc, argv).ToLocalChecked();
|
|
2582
|
+
// parseFont returns undefined for invalid CSS font strings
|
|
2583
|
+
if (mparsed->IsUndefined()) return;
|
|
2584
|
+
Local<Object> font = Nan::To<Object>(mparsed).ToLocalChecked();
|
|
2585
|
+
|
|
2586
|
+
Nan::Utf8String weight(Nan::Get(font, Nan::New("weight").ToLocalChecked()).ToLocalChecked());
|
|
2587
|
+
Nan::Utf8String style(Nan::Get(font, Nan::New("style").ToLocalChecked()).ToLocalChecked());
|
|
2588
|
+
double size = Nan::To<double>(Nan::Get(font, Nan::New("size").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
|
|
2589
|
+
Nan::Utf8String unit(Nan::Get(font, Nan::New("unit").ToLocalChecked()).ToLocalChecked());
|
|
2590
|
+
Nan::Utf8String family(Nan::Get(font, Nan::New("family").ToLocalChecked()).ToLocalChecked());
|
|
2591
|
+
|
|
2592
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2593
|
+
|
|
2594
|
+
PangoFontDescription *desc = pango_font_description_copy(context->state->fontDescription);
|
|
2595
|
+
pango_font_description_free(context->state->fontDescription);
|
|
2596
|
+
|
|
2597
|
+
pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(*style));
|
|
2598
|
+
pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(*weight));
|
|
2599
|
+
|
|
2600
|
+
if (strlen(*family) > 0) {
|
|
2601
|
+
// See #1643 - Pango understands "sans" whereas CSS uses "sans-serif"
|
|
2602
|
+
std::string s1(*family);
|
|
2603
|
+
std::string s2("sans-serif");
|
|
2604
|
+
if (streq_casein(s1, s2)) {
|
|
2605
|
+
pango_font_description_set_family(desc, "sans");
|
|
2606
|
+
} else {
|
|
2607
|
+
pango_font_description_set_family(desc, *family);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
PangoFontDescription *sys_desc = Canvas::ResolveFontDescription(desc);
|
|
2612
|
+
pango_font_description_free(desc);
|
|
2613
|
+
|
|
2614
|
+
if (size > 0) pango_font_description_set_absolute_size(sys_desc, size * PANGO_SCALE);
|
|
2615
|
+
|
|
2616
|
+
context->state->fontDescription = sys_desc;
|
|
2617
|
+
pango_layout_set_font_description(context->_layout, sys_desc);
|
|
2618
|
+
|
|
2619
|
+
context->state->font = *Nan::Utf8String(value);
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
/*
|
|
2623
|
+
* Get text baseline.
|
|
2624
|
+
*/
|
|
2625
|
+
|
|
2626
|
+
NAN_GETTER(Context2d::GetTextBaseline) {
|
|
2627
|
+
CHECK_RECEIVER(Context2d.GetTextBaseline);
|
|
2628
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2629
|
+
const char* baseline;
|
|
2630
|
+
switch (context->state->textBaseline) {
|
|
2631
|
+
default:
|
|
2632
|
+
case TEXT_BASELINE_ALPHABETIC: baseline = "alphabetic"; break;
|
|
2633
|
+
case TEXT_BASELINE_TOP: baseline = "top"; break;
|
|
2634
|
+
case TEXT_BASELINE_BOTTOM: baseline = "bottom"; break;
|
|
2635
|
+
case TEXT_BASELINE_MIDDLE: baseline = "middle"; break;
|
|
2636
|
+
case TEXT_BASELINE_IDEOGRAPHIC: baseline = "ideographic"; break;
|
|
2637
|
+
case TEXT_BASELINE_HANGING: baseline = "hanging"; break;
|
|
2638
|
+
}
|
|
2639
|
+
info.GetReturnValue().Set(Nan::New(baseline).ToLocalChecked());
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
/*
|
|
2643
|
+
* Set text baseline.
|
|
2644
|
+
*/
|
|
2645
|
+
|
|
2646
|
+
NAN_SETTER(Context2d::SetTextBaseline) {
|
|
2647
|
+
CHECK_RECEIVER(Context2d.SetTextBaseline);
|
|
2648
|
+
if (!value->IsString()) return;
|
|
2649
|
+
|
|
2650
|
+
Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked());
|
|
2651
|
+
const std::map<std::string, text_baseline_t> modes = {
|
|
2652
|
+
{"alphabetic", TEXT_BASELINE_ALPHABETIC},
|
|
2653
|
+
{"top", TEXT_BASELINE_TOP},
|
|
2654
|
+
{"bottom", TEXT_BASELINE_BOTTOM},
|
|
2655
|
+
{"middle", TEXT_BASELINE_MIDDLE},
|
|
2656
|
+
{"ideographic", TEXT_BASELINE_IDEOGRAPHIC},
|
|
2657
|
+
{"hanging", TEXT_BASELINE_HANGING}
|
|
2658
|
+
};
|
|
2659
|
+
auto op = modes.find(*opStr);
|
|
2660
|
+
if (op == modes.end()) return;
|
|
2661
|
+
|
|
2662
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2663
|
+
context->state->textBaseline = op->second;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
/*
|
|
2667
|
+
* Get text align.
|
|
2668
|
+
*/
|
|
2669
|
+
|
|
2670
|
+
NAN_GETTER(Context2d::GetTextAlign) {
|
|
2671
|
+
CHECK_RECEIVER(Context2d.GetTextAlign);
|
|
2672
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2673
|
+
const char* align;
|
|
2674
|
+
switch (context->state->textAlignment) {
|
|
2675
|
+
default:
|
|
2676
|
+
// TODO the default is supposed to be "start"
|
|
2677
|
+
case TEXT_ALIGNMENT_LEFT: align = "left"; break;
|
|
2678
|
+
case TEXT_ALIGNMENT_START: align = "start"; break;
|
|
2679
|
+
case TEXT_ALIGNMENT_CENTER: align = "center"; break;
|
|
2680
|
+
case TEXT_ALIGNMENT_RIGHT: align = "right"; break;
|
|
2681
|
+
case TEXT_ALIGNMENT_END: align = "end"; break;
|
|
2682
|
+
}
|
|
2683
|
+
info.GetReturnValue().Set(Nan::New(align).ToLocalChecked());
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
/*
|
|
2687
|
+
* Set text align.
|
|
2688
|
+
*/
|
|
2689
|
+
|
|
2690
|
+
NAN_SETTER(Context2d::SetTextAlign) {
|
|
2691
|
+
CHECK_RECEIVER(Context2d.SetTextAlign);
|
|
2692
|
+
if (!value->IsString()) return;
|
|
2693
|
+
|
|
2694
|
+
Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked());
|
|
2695
|
+
const std::map<std::string, text_align_t> modes = {
|
|
2696
|
+
{"center", TEXT_ALIGNMENT_CENTER},
|
|
2697
|
+
{"left", TEXT_ALIGNMENT_LEFT},
|
|
2698
|
+
{"start", TEXT_ALIGNMENT_START},
|
|
2699
|
+
{"right", TEXT_ALIGNMENT_RIGHT},
|
|
2700
|
+
{"end", TEXT_ALIGNMENT_END}
|
|
2701
|
+
};
|
|
2702
|
+
auto op = modes.find(*opStr);
|
|
2703
|
+
if (op == modes.end()) return;
|
|
2704
|
+
|
|
2705
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2706
|
+
context->state->textAlignment = op->second;
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
/*
|
|
2710
|
+
* Return the given text extents.
|
|
2711
|
+
* TODO: Support for:
|
|
2712
|
+
* hangingBaseline, ideographicBaseline,
|
|
2713
|
+
* fontBoundingBoxAscent, fontBoundingBoxDescent
|
|
2714
|
+
*/
|
|
2715
|
+
|
|
2716
|
+
NAN_METHOD(Context2d::MeasureText) {
|
|
2717
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2718
|
+
cairo_t *ctx = context->context();
|
|
2719
|
+
|
|
2720
|
+
Nan::Utf8String str(Nan::To<String>(info[0]).ToLocalChecked());
|
|
2721
|
+
Local<Object> obj = Nan::New<Object>();
|
|
2722
|
+
|
|
2723
|
+
PangoRectangle _ink_rect, _logical_rect;
|
|
2724
|
+
float_rectangle ink_rect, logical_rect;
|
|
2725
|
+
PangoFontMetrics *metrics;
|
|
2726
|
+
PangoLayout *layout = context->layout();
|
|
2727
|
+
|
|
2728
|
+
pango_layout_set_text(layout, *str, -1);
|
|
2729
|
+
pango_cairo_update_layout(ctx, layout);
|
|
2730
|
+
|
|
2731
|
+
// Normally you could use pango_layout_get_pixel_extents and be done, or use
|
|
2732
|
+
// pango_extents_to_pixels, but both of those round the pixels, so we have to
|
|
2733
|
+
// divide by PANGO_SCALE manually
|
|
2734
|
+
pango_layout_get_extents(layout, &_ink_rect, &_logical_rect);
|
|
2735
|
+
|
|
2736
|
+
float inverse_pango_scale = 1. / PANGO_SCALE;
|
|
2737
|
+
|
|
2738
|
+
logical_rect.x = _logical_rect.x * inverse_pango_scale;
|
|
2739
|
+
logical_rect.y = _logical_rect.y * inverse_pango_scale;
|
|
2740
|
+
logical_rect.width = _logical_rect.width * inverse_pango_scale;
|
|
2741
|
+
logical_rect.height = _logical_rect.height * inverse_pango_scale;
|
|
2742
|
+
|
|
2743
|
+
ink_rect.x = _ink_rect.x * inverse_pango_scale;
|
|
2744
|
+
ink_rect.y = _ink_rect.y * inverse_pango_scale;
|
|
2745
|
+
ink_rect.width = _ink_rect.width * inverse_pango_scale;
|
|
2746
|
+
ink_rect.height = _ink_rect.height * inverse_pango_scale;
|
|
2747
|
+
|
|
2748
|
+
metrics = PANGO_LAYOUT_GET_METRICS(layout);
|
|
2749
|
+
|
|
2750
|
+
double x_offset;
|
|
2751
|
+
switch (context->state->textAlignment) {
|
|
2752
|
+
case TEXT_ALIGNMENT_CENTER:
|
|
2753
|
+
x_offset = logical_rect.width / 2.;
|
|
2754
|
+
break;
|
|
2755
|
+
case TEXT_ALIGNMENT_END:
|
|
2756
|
+
case TEXT_ALIGNMENT_RIGHT:
|
|
2757
|
+
x_offset = logical_rect.width;
|
|
2758
|
+
break;
|
|
2759
|
+
case TEXT_ALIGNMENT_START:
|
|
2760
|
+
case TEXT_ALIGNMENT_LEFT:
|
|
2761
|
+
default:
|
|
2762
|
+
x_offset = 0.0;
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
cairo_matrix_t matrix;
|
|
2766
|
+
cairo_get_matrix(ctx, &matrix);
|
|
2767
|
+
double y_offset = getBaselineAdjustment(layout, context->state->textBaseline);
|
|
2768
|
+
|
|
2769
|
+
Nan::Set(obj,
|
|
2770
|
+
Nan::New<String>("width").ToLocalChecked(),
|
|
2771
|
+
Nan::New<Number>(logical_rect.width)).Check();
|
|
2772
|
+
Nan::Set(obj,
|
|
2773
|
+
Nan::New<String>("actualBoundingBoxLeft").ToLocalChecked(),
|
|
2774
|
+
Nan::New<Number>(PANGO_LBEARING(ink_rect) + x_offset)).Check();
|
|
2775
|
+
Nan::Set(obj,
|
|
2776
|
+
Nan::New<String>("actualBoundingBoxRight").ToLocalChecked(),
|
|
2777
|
+
Nan::New<Number>(PANGO_RBEARING(ink_rect) - x_offset)).Check();
|
|
2778
|
+
Nan::Set(obj,
|
|
2779
|
+
Nan::New<String>("actualBoundingBoxAscent").ToLocalChecked(),
|
|
2780
|
+
Nan::New<Number>(y_offset + PANGO_ASCENT(ink_rect))).Check();
|
|
2781
|
+
Nan::Set(obj,
|
|
2782
|
+
Nan::New<String>("actualBoundingBoxDescent").ToLocalChecked(),
|
|
2783
|
+
Nan::New<Number>(PANGO_DESCENT(ink_rect) - y_offset)).Check();
|
|
2784
|
+
Nan::Set(obj,
|
|
2785
|
+
Nan::New<String>("emHeightAscent").ToLocalChecked(),
|
|
2786
|
+
Nan::New<Number>(-(PANGO_ASCENT(logical_rect) - y_offset))).Check();
|
|
2787
|
+
Nan::Set(obj,
|
|
2788
|
+
Nan::New<String>("emHeightDescent").ToLocalChecked(),
|
|
2789
|
+
Nan::New<Number>(PANGO_DESCENT(logical_rect) - y_offset)).Check();
|
|
2790
|
+
Nan::Set(obj,
|
|
2791
|
+
Nan::New<String>("alphabeticBaseline").ToLocalChecked(),
|
|
2792
|
+
Nan::New<Number>(-(pango_font_metrics_get_ascent(metrics) * inverse_pango_scale - y_offset))).Check();
|
|
2793
|
+
|
|
2794
|
+
pango_font_metrics_unref(metrics);
|
|
2795
|
+
|
|
2796
|
+
info.GetReturnValue().Set(obj);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
/*
|
|
2800
|
+
* Set line dash
|
|
2801
|
+
* ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
|
|
2802
|
+
*/
|
|
2803
|
+
|
|
2804
|
+
NAN_METHOD(Context2d::SetLineDash) {
|
|
2805
|
+
if (!info[0]->IsArray()) return;
|
|
2806
|
+
Local<Array> dash = Local<Array>::Cast(info[0]);
|
|
2807
|
+
uint32_t dashes = dash->Length() & 1 ? dash->Length() * 2 : dash->Length();
|
|
2808
|
+
uint32_t zero_dashes = 0;
|
|
2809
|
+
std::vector<double> a(dashes);
|
|
2810
|
+
for (uint32_t i=0; i<dashes; i++) {
|
|
2811
|
+
Local<Value> d = Nan::Get(dash, i % dash->Length()).ToLocalChecked();
|
|
2812
|
+
if (!d->IsNumber()) return;
|
|
2813
|
+
a[i] = Nan::To<double>(d).FromMaybe(0);
|
|
2814
|
+
if (a[i] == 0) zero_dashes++;
|
|
2815
|
+
if (a[i] < 0 || !std::isfinite(a[i])) return;
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2819
|
+
cairo_t *ctx = context->context();
|
|
2820
|
+
double offset;
|
|
2821
|
+
cairo_get_dash(ctx, NULL, &offset);
|
|
2822
|
+
if (zero_dashes == dashes) {
|
|
2823
|
+
std::vector<double> b(0);
|
|
2824
|
+
cairo_set_dash(ctx, b.data(), 0, offset);
|
|
2825
|
+
} else {
|
|
2826
|
+
cairo_set_dash(ctx, a.data(), dashes, offset);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
/*
|
|
2831
|
+
* Get line dash
|
|
2832
|
+
* ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
|
|
2833
|
+
*/
|
|
2834
|
+
NAN_METHOD(Context2d::GetLineDash) {
|
|
2835
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2836
|
+
cairo_t *ctx = context->context();
|
|
2837
|
+
int dashes = cairo_get_dash_count(ctx);
|
|
2838
|
+
std::vector<double> a(dashes);
|
|
2839
|
+
cairo_get_dash(ctx, a.data(), NULL);
|
|
2840
|
+
|
|
2841
|
+
Local<Array> dash = Nan::New<Array>(dashes);
|
|
2842
|
+
for (int i=0; i<dashes; i++) {
|
|
2843
|
+
Nan::Set(dash, Nan::New<Number>(i), Nan::New<Number>(a[i])).Check();
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
info.GetReturnValue().Set(dash);
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
/*
|
|
2850
|
+
* Set line dash offset
|
|
2851
|
+
* ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
|
|
2852
|
+
*/
|
|
2853
|
+
NAN_SETTER(Context2d::SetLineDashOffset) {
|
|
2854
|
+
CHECK_RECEIVER(Context2d.SetLineDashOffset);
|
|
2855
|
+
double offset = Nan::To<double>(value).FromMaybe(0);
|
|
2856
|
+
if (!std::isfinite(offset)) return;
|
|
2857
|
+
|
|
2858
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2859
|
+
cairo_t *ctx = context->context();
|
|
2860
|
+
|
|
2861
|
+
int dashes = cairo_get_dash_count(ctx);
|
|
2862
|
+
std::vector<double> a(dashes);
|
|
2863
|
+
cairo_get_dash(ctx, a.data(), NULL);
|
|
2864
|
+
cairo_set_dash(ctx, a.data(), dashes, offset);
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
/*
|
|
2868
|
+
* Get line dash offset
|
|
2869
|
+
* ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
|
|
2870
|
+
*/
|
|
2871
|
+
NAN_GETTER(Context2d::GetLineDashOffset) {
|
|
2872
|
+
CHECK_RECEIVER(Context2d.GetLineDashOffset);
|
|
2873
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2874
|
+
cairo_t *ctx = context->context();
|
|
2875
|
+
double offset;
|
|
2876
|
+
cairo_get_dash(ctx, NULL, &offset);
|
|
2877
|
+
|
|
2878
|
+
info.GetReturnValue().Set(Nan::New<Number>(offset));
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
/*
|
|
2882
|
+
* Fill the rectangle defined by x, y, width and height.
|
|
2883
|
+
*/
|
|
2884
|
+
|
|
2885
|
+
NAN_METHOD(Context2d::FillRect) {
|
|
2886
|
+
RECT_ARGS;
|
|
2887
|
+
if (0 == width || 0 == height) return;
|
|
2888
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2889
|
+
cairo_t *ctx = context->context();
|
|
2890
|
+
context->savePath();
|
|
2891
|
+
cairo_rectangle(ctx, x, y, width, height);
|
|
2892
|
+
context->fill();
|
|
2893
|
+
context->restorePath();
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
/*
|
|
2897
|
+
* Stroke the rectangle defined by x, y, width and height.
|
|
2898
|
+
*/
|
|
2899
|
+
|
|
2900
|
+
NAN_METHOD(Context2d::StrokeRect) {
|
|
2901
|
+
RECT_ARGS;
|
|
2902
|
+
if (0 == width && 0 == height) return;
|
|
2903
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2904
|
+
cairo_t *ctx = context->context();
|
|
2905
|
+
context->savePath();
|
|
2906
|
+
cairo_rectangle(ctx, x, y, width, height);
|
|
2907
|
+
context->stroke();
|
|
2908
|
+
context->restorePath();
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
/*
|
|
2912
|
+
* Clears all pixels defined by x, y, width and height.
|
|
2913
|
+
*/
|
|
2914
|
+
|
|
2915
|
+
NAN_METHOD(Context2d::ClearRect) {
|
|
2916
|
+
RECT_ARGS;
|
|
2917
|
+
if (0 == width || 0 == height) return;
|
|
2918
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2919
|
+
cairo_t *ctx = context->context();
|
|
2920
|
+
cairo_save(ctx);
|
|
2921
|
+
context->savePath();
|
|
2922
|
+
cairo_rectangle(ctx, x, y, width, height);
|
|
2923
|
+
cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
|
|
2924
|
+
cairo_fill(ctx);
|
|
2925
|
+
context->restorePath();
|
|
2926
|
+
cairo_restore(ctx);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
/*
|
|
2930
|
+
* Adds a rectangle subpath.
|
|
2931
|
+
*/
|
|
2932
|
+
|
|
2933
|
+
NAN_METHOD(Context2d::Rect) {
|
|
2934
|
+
RECT_ARGS;
|
|
2935
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
2936
|
+
cairo_t *ctx = context->context();
|
|
2937
|
+
if (width == 0) {
|
|
2938
|
+
cairo_move_to(ctx, x, y);
|
|
2939
|
+
cairo_line_to(ctx, x, y + height);
|
|
2940
|
+
} else if (height == 0) {
|
|
2941
|
+
cairo_move_to(ctx, x, y);
|
|
2942
|
+
cairo_line_to(ctx, x + width, y);
|
|
2943
|
+
} else {
|
|
2944
|
+
cairo_rectangle(ctx, x, y, width, height);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// Draws an arc with two potentially different radii.
|
|
2949
|
+
inline static
|
|
2950
|
+
void elli_arc(cairo_t* ctx, double xc, double yc, double rx, double ry, double a1, double a2, bool clockwise=true) {
|
|
2951
|
+
if (rx == 0. || ry == 0.) {
|
|
2952
|
+
cairo_line_to(ctx, xc + rx, yc + ry);
|
|
2953
|
+
} else {
|
|
2954
|
+
cairo_save(ctx);
|
|
2955
|
+
cairo_translate(ctx, xc, yc);
|
|
2956
|
+
cairo_scale(ctx, rx, ry);
|
|
2957
|
+
if (clockwise)
|
|
2958
|
+
cairo_arc(ctx, 0., 0., 1., a1, a2);
|
|
2959
|
+
else
|
|
2960
|
+
cairo_arc_negative(ctx, 0., 0., 1., a2, a1);
|
|
2961
|
+
cairo_restore(ctx);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
inline static
|
|
2966
|
+
bool getRadius(Point<double>& p, const Local<Value>& v) {
|
|
2967
|
+
if (v->IsObject()) { // 5.1 DOMPointInit
|
|
2968
|
+
auto rx = Nan::Get(v.As<Object>(), Nan::New("x").ToLocalChecked()).ToLocalChecked();
|
|
2969
|
+
auto ry = Nan::Get(v.As<Object>(), Nan::New("y").ToLocalChecked()).ToLocalChecked();
|
|
2970
|
+
if (rx->IsNumber() && ry->IsNumber()) {
|
|
2971
|
+
auto rxv = Nan::To<double>(rx).FromJust();
|
|
2972
|
+
auto ryv = Nan::To<double>(ry).FromJust();
|
|
2973
|
+
if (!std::isfinite(rxv) || !std::isfinite(ryv))
|
|
2974
|
+
return true;
|
|
2975
|
+
if (rxv < 0 || ryv < 0) {
|
|
2976
|
+
Nan::ThrowRangeError("radii must be positive.");
|
|
2977
|
+
return true;
|
|
2978
|
+
}
|
|
2979
|
+
p.x = rxv;
|
|
2980
|
+
p.y = ryv;
|
|
2981
|
+
return false;
|
|
2982
|
+
}
|
|
2983
|
+
} else if (v->IsNumber()) { // 5.2 unrestricted double
|
|
2984
|
+
auto rv = Nan::To<double>(v).FromJust();
|
|
2985
|
+
if (!std::isfinite(rv))
|
|
2986
|
+
return true;
|
|
2987
|
+
if (rv < 0) {
|
|
2988
|
+
Nan::ThrowRangeError("radii must be positive.");
|
|
2989
|
+
return true;
|
|
2990
|
+
}
|
|
2991
|
+
p.x = p.y = rv;
|
|
2992
|
+
return false;
|
|
2993
|
+
}
|
|
2994
|
+
return true;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
/**
|
|
2998
|
+
* https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
|
|
2999
|
+
* x, y, w, h, [radius|[radii]]
|
|
3000
|
+
*/
|
|
3001
|
+
NAN_METHOD(Context2d::RoundRect) {
|
|
3002
|
+
RECT_ARGS;
|
|
3003
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
3004
|
+
cairo_t *ctx = context->context();
|
|
3005
|
+
|
|
3006
|
+
// 4. Let normalizedRadii be an empty list
|
|
3007
|
+
Point<double> normalizedRadii[4];
|
|
3008
|
+
size_t nRadii = 4;
|
|
3009
|
+
|
|
3010
|
+
if (info[4]->IsUndefined()) {
|
|
3011
|
+
for (size_t i = 0; i < 4; i++)
|
|
3012
|
+
normalizedRadii[i].x = normalizedRadii[i].y = 0.;
|
|
3013
|
+
|
|
3014
|
+
} else if (info[4]->IsArray()) {
|
|
3015
|
+
auto radiiList = info[4].As<v8::Array>();
|
|
3016
|
+
nRadii = radiiList->Length();
|
|
3017
|
+
if (!(nRadii >= 1 && nRadii <= 4)) {
|
|
3018
|
+
Nan::ThrowRangeError("radii must be a list of one, two, three or four radii.");
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
// 5. For each radius of radii
|
|
3022
|
+
for (size_t i = 0; i < nRadii; i++) {
|
|
3023
|
+
auto r = Nan::Get(radiiList, i).ToLocalChecked();
|
|
3024
|
+
if (getRadius(normalizedRadii[i], r))
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
} else {
|
|
3029
|
+
// 2. If radii is a double, then set radii to <<radii>>
|
|
3030
|
+
if (getRadius(normalizedRadii[0], info[4]))
|
|
3031
|
+
return;
|
|
3032
|
+
for (size_t i = 1; i < 4; i++) {
|
|
3033
|
+
normalizedRadii[i].x = normalizedRadii[0].x;
|
|
3034
|
+
normalizedRadii[i].y = normalizedRadii[0].y;
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
Point<double> upperLeft, upperRight, lowerRight, lowerLeft;
|
|
3039
|
+
if (nRadii == 4) {
|
|
3040
|
+
upperLeft = normalizedRadii[0];
|
|
3041
|
+
upperRight = normalizedRadii[1];
|
|
3042
|
+
lowerRight = normalizedRadii[2];
|
|
3043
|
+
lowerLeft = normalizedRadii[3];
|
|
3044
|
+
} else if (nRadii == 3) {
|
|
3045
|
+
upperLeft = normalizedRadii[0];
|
|
3046
|
+
upperRight = normalizedRadii[1];
|
|
3047
|
+
lowerLeft = normalizedRadii[1];
|
|
3048
|
+
lowerRight = normalizedRadii[2];
|
|
3049
|
+
} else if (nRadii == 2) {
|
|
3050
|
+
upperLeft = normalizedRadii[0];
|
|
3051
|
+
lowerRight = normalizedRadii[0];
|
|
3052
|
+
upperRight = normalizedRadii[1];
|
|
3053
|
+
lowerLeft = normalizedRadii[1];
|
|
3054
|
+
} else {
|
|
3055
|
+
upperLeft = normalizedRadii[0];
|
|
3056
|
+
upperRight = normalizedRadii[0];
|
|
3057
|
+
lowerRight = normalizedRadii[0];
|
|
3058
|
+
lowerLeft = normalizedRadii[0];
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
bool clockwise = true;
|
|
3062
|
+
if (width < 0) {
|
|
3063
|
+
clockwise = false;
|
|
3064
|
+
x += width;
|
|
3065
|
+
width = -width;
|
|
3066
|
+
std::swap(upperLeft, upperRight);
|
|
3067
|
+
std::swap(lowerLeft, lowerRight);
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
if (height < 0) {
|
|
3071
|
+
clockwise = !clockwise;
|
|
3072
|
+
y += height;
|
|
3073
|
+
height = -height;
|
|
3074
|
+
std::swap(upperLeft, lowerLeft);
|
|
3075
|
+
std::swap(upperRight, lowerRight);
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
// 11. Corner curves must not overlap. Scale radii to prevent this.
|
|
3079
|
+
{
|
|
3080
|
+
auto top = upperLeft.x + upperRight.x;
|
|
3081
|
+
auto right = upperRight.y + lowerRight.y;
|
|
3082
|
+
auto bottom = lowerRight.x + lowerLeft.x;
|
|
3083
|
+
auto left = upperLeft.y + lowerLeft.y;
|
|
3084
|
+
auto scale = std::min({ width / top, height / right, width / bottom, height / left });
|
|
3085
|
+
if (scale < 1.) {
|
|
3086
|
+
upperLeft.x *= scale;
|
|
3087
|
+
upperLeft.y *= scale;
|
|
3088
|
+
upperRight.x *= scale;
|
|
3089
|
+
upperRight.x *= scale;
|
|
3090
|
+
lowerLeft.y *= scale;
|
|
3091
|
+
lowerLeft.y *= scale;
|
|
3092
|
+
lowerRight.y *= scale;
|
|
3093
|
+
lowerRight.y *= scale;
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// 12. Draw
|
|
3098
|
+
cairo_move_to(ctx, x + upperLeft.x, y);
|
|
3099
|
+
if (clockwise) {
|
|
3100
|
+
cairo_line_to(ctx, x + width - upperRight.x, y);
|
|
3101
|
+
elli_arc(ctx, x + width - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 3. * M_PI / 2., 0.);
|
|
3102
|
+
cairo_line_to(ctx, x + width, y + height - lowerRight.y);
|
|
3103
|
+
elli_arc(ctx, x + width - lowerRight.x, y + height - lowerRight.y, lowerRight.x, lowerRight.y, 0, M_PI / 2.);
|
|
3104
|
+
cairo_line_to(ctx, x + lowerLeft.x, y + height);
|
|
3105
|
+
elli_arc(ctx, x + lowerLeft.x, y + height - lowerLeft.y, lowerLeft.x, lowerLeft.y, M_PI / 2., M_PI);
|
|
3106
|
+
cairo_line_to(ctx, x, y + upperLeft.y);
|
|
3107
|
+
elli_arc(ctx, x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, M_PI, 3. * M_PI / 2.);
|
|
3108
|
+
} else {
|
|
3109
|
+
elli_arc(ctx, x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, M_PI, 3. * M_PI / 2., false);
|
|
3110
|
+
cairo_line_to(ctx, x, y + upperLeft.y);
|
|
3111
|
+
elli_arc(ctx, x + lowerLeft.x, y + height - lowerLeft.y, lowerLeft.x, lowerLeft.y, M_PI / 2., M_PI, false);
|
|
3112
|
+
cairo_line_to(ctx, x + lowerLeft.x, y + height);
|
|
3113
|
+
elli_arc(ctx, x + width - lowerRight.x, y + height - lowerRight.y, lowerRight.x, lowerRight.y, 0, M_PI / 2., false);
|
|
3114
|
+
cairo_line_to(ctx, x + width, y + height - lowerRight.y);
|
|
3115
|
+
elli_arc(ctx, x + width - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 3. * M_PI / 2., 0., false);
|
|
3116
|
+
cairo_line_to(ctx, x + width - upperRight.x, y);
|
|
3117
|
+
}
|
|
3118
|
+
cairo_close_path(ctx);
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// Adapted from https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/modules/canvas2d/CanvasPathMethods.cpp
|
|
3122
|
+
static void canonicalizeAngle(double& startAngle, double& endAngle) {
|
|
3123
|
+
// Make 0 <= startAngle < 2*PI
|
|
3124
|
+
double newStartAngle = std::fmod(startAngle, twoPi);
|
|
3125
|
+
if (newStartAngle < 0) {
|
|
3126
|
+
newStartAngle += twoPi;
|
|
3127
|
+
// Check for possible catastrophic cancellation in cases where
|
|
3128
|
+
// newStartAngle was a tiny negative number (c.f. crbug.com/503422)
|
|
3129
|
+
if (newStartAngle >= twoPi)
|
|
3130
|
+
newStartAngle -= twoPi;
|
|
3131
|
+
}
|
|
3132
|
+
double delta = newStartAngle - startAngle;
|
|
3133
|
+
startAngle = newStartAngle;
|
|
3134
|
+
endAngle = endAngle + delta;
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// Adapted from https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/modules/canvas2d/CanvasPathMethods.cpp
|
|
3138
|
+
static double adjustEndAngle(double startAngle, double endAngle, bool counterclockwise) {
|
|
3139
|
+
double newEndAngle = endAngle;
|
|
3140
|
+
/* http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arc
|
|
3141
|
+
* If the counterclockwise argument is false and endAngle-startAngle is equal to or greater than 2pi, or,
|
|
3142
|
+
* if the counterclockwise argument is true and startAngle-endAngle is equal to or greater than 2pi,
|
|
3143
|
+
* then the arc is the whole circumference of this ellipse, and the point at startAngle along this circle's circumference,
|
|
3144
|
+
* measured in radians clockwise from the ellipse's semi-major axis, acts as both the start point and the end point.
|
|
3145
|
+
*/
|
|
3146
|
+
if (!counterclockwise && endAngle - startAngle >= twoPi)
|
|
3147
|
+
newEndAngle = startAngle + twoPi;
|
|
3148
|
+
else if (counterclockwise && startAngle - endAngle >= twoPi)
|
|
3149
|
+
newEndAngle = startAngle - twoPi;
|
|
3150
|
+
/*
|
|
3151
|
+
* Otherwise, the arc is the path along the circumference of this ellipse from the start point to the end point,
|
|
3152
|
+
* going anti-clockwise if the counterclockwise argument is true, and clockwise otherwise.
|
|
3153
|
+
* Since the points are on the ellipse, as opposed to being simply angles from zero,
|
|
3154
|
+
* the arc can never cover an angle greater than 2pi radians.
|
|
3155
|
+
*/
|
|
3156
|
+
/* NOTE: When startAngle = 0, endAngle = 2Pi and counterclockwise = true, the spec does not indicate clearly.
|
|
3157
|
+
* We draw the entire circle, because some web sites use arc(x, y, r, 0, 2*Math.PI, true) to draw circle.
|
|
3158
|
+
* We preserve backward-compatibility.
|
|
3159
|
+
*/
|
|
3160
|
+
else if (!counterclockwise && startAngle > endAngle)
|
|
3161
|
+
newEndAngle = startAngle + (twoPi - std::fmod(startAngle - endAngle, twoPi));
|
|
3162
|
+
else if (counterclockwise && startAngle < endAngle)
|
|
3163
|
+
newEndAngle = startAngle - (twoPi - std::fmod(endAngle - startAngle, twoPi));
|
|
3164
|
+
return newEndAngle;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
/*
|
|
3168
|
+
* Adds an arc at x, y with the given radii and start/end angles.
|
|
3169
|
+
*/
|
|
3170
|
+
|
|
3171
|
+
NAN_METHOD(Context2d::Arc) {
|
|
3172
|
+
double args[5];
|
|
3173
|
+
if(!checkArgs(info, args, 5))
|
|
3174
|
+
return;
|
|
3175
|
+
|
|
3176
|
+
auto x = args[0];
|
|
3177
|
+
auto y = args[1];
|
|
3178
|
+
auto radius = args[2];
|
|
3179
|
+
auto startAngle = args[3];
|
|
3180
|
+
auto endAngle = args[4];
|
|
3181
|
+
|
|
3182
|
+
if (radius < 0) {
|
|
3183
|
+
Nan::ThrowRangeError("The radius provided is negative.");
|
|
3184
|
+
return;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
bool counterclockwise = Nan::To<bool>(info[5]).FromMaybe(false);
|
|
3188
|
+
|
|
3189
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
3190
|
+
cairo_t *ctx = context->context();
|
|
3191
|
+
|
|
3192
|
+
canonicalizeAngle(startAngle, endAngle);
|
|
3193
|
+
endAngle = adjustEndAngle(startAngle, endAngle, counterclockwise);
|
|
3194
|
+
|
|
3195
|
+
if (counterclockwise) {
|
|
3196
|
+
cairo_arc_negative(ctx, x, y, radius, startAngle, endAngle);
|
|
3197
|
+
} else {
|
|
3198
|
+
cairo_arc(ctx, x, y, radius, startAngle, endAngle);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
/*
|
|
3203
|
+
* Adds an arcTo point (x0,y0) to (x1,y1) with the given radius.
|
|
3204
|
+
*
|
|
3205
|
+
* Implementation influenced by WebKit.
|
|
3206
|
+
*/
|
|
3207
|
+
|
|
3208
|
+
NAN_METHOD(Context2d::ArcTo) {
|
|
3209
|
+
double args[5];
|
|
3210
|
+
if(!checkArgs(info, args, 5))
|
|
3211
|
+
return;
|
|
3212
|
+
|
|
3213
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
3214
|
+
cairo_t *ctx = context->context();
|
|
3215
|
+
|
|
3216
|
+
// Current path point
|
|
3217
|
+
double x, y;
|
|
3218
|
+
cairo_get_current_point(ctx, &x, &y);
|
|
3219
|
+
Point<float> p0(x, y);
|
|
3220
|
+
|
|
3221
|
+
// Point (x0,y0)
|
|
3222
|
+
Point<float> p1(args[0], args[1]);
|
|
3223
|
+
|
|
3224
|
+
// Point (x1,y1)
|
|
3225
|
+
Point<float> p2(args[2], args[3]);
|
|
3226
|
+
|
|
3227
|
+
float radius = args[4];
|
|
3228
|
+
|
|
3229
|
+
if ((p1.x == p0.x && p1.y == p0.y)
|
|
3230
|
+
|| (p1.x == p2.x && p1.y == p2.y)
|
|
3231
|
+
|| radius == 0.f) {
|
|
3232
|
+
cairo_line_to(ctx, p1.x, p1.y);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
Point<float> p1p0((p0.x - p1.x),(p0.y - p1.y));
|
|
3237
|
+
Point<float> p1p2((p2.x - p1.x),(p2.y - p1.y));
|
|
3238
|
+
float p1p0_length = sqrtf(p1p0.x * p1p0.x + p1p0.y * p1p0.y);
|
|
3239
|
+
float p1p2_length = sqrtf(p1p2.x * p1p2.x + p1p2.y * p1p2.y);
|
|
3240
|
+
|
|
3241
|
+
double cos_phi = (p1p0.x * p1p2.x + p1p0.y * p1p2.y) / (p1p0_length * p1p2_length);
|
|
3242
|
+
// all points on a line logic
|
|
3243
|
+
if (-1 == cos_phi) {
|
|
3244
|
+
cairo_line_to(ctx, p1.x, p1.y);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
if (1 == cos_phi) {
|
|
3249
|
+
// add infinite far away point
|
|
3250
|
+
unsigned int max_length = 65535;
|
|
3251
|
+
double factor_max = max_length / p1p0_length;
|
|
3252
|
+
Point<float> ep((p0.x + factor_max * p1p0.x), (p0.y + factor_max * p1p0.y));
|
|
3253
|
+
cairo_line_to(ctx, ep.x, ep.y);
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
float tangent = radius / tan(acos(cos_phi) / 2);
|
|
3258
|
+
float factor_p1p0 = tangent / p1p0_length;
|
|
3259
|
+
Point<float> t_p1p0((p1.x + factor_p1p0 * p1p0.x), (p1.y + factor_p1p0 * p1p0.y));
|
|
3260
|
+
|
|
3261
|
+
Point<float> orth_p1p0(p1p0.y, -p1p0.x);
|
|
3262
|
+
float orth_p1p0_length = sqrt(orth_p1p0.x * orth_p1p0.x + orth_p1p0.y * orth_p1p0.y);
|
|
3263
|
+
float factor_ra = radius / orth_p1p0_length;
|
|
3264
|
+
|
|
3265
|
+
double cos_alpha = (orth_p1p0.x * p1p2.x + orth_p1p0.y * p1p2.y) / (orth_p1p0_length * p1p2_length);
|
|
3266
|
+
if (cos_alpha < 0.f)
|
|
3267
|
+
orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
|
|
3268
|
+
|
|
3269
|
+
Point<float> p((t_p1p0.x + factor_ra * orth_p1p0.x), (t_p1p0.y + factor_ra * orth_p1p0.y));
|
|
3270
|
+
|
|
3271
|
+
orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
|
|
3272
|
+
float sa = acos(orth_p1p0.x / orth_p1p0_length);
|
|
3273
|
+
if (orth_p1p0.y < 0.f)
|
|
3274
|
+
sa = 2 * M_PI - sa;
|
|
3275
|
+
|
|
3276
|
+
bool anticlockwise = false;
|
|
3277
|
+
|
|
3278
|
+
float factor_p1p2 = tangent / p1p2_length;
|
|
3279
|
+
Point<float> t_p1p2((p1.x + factor_p1p2 * p1p2.x), (p1.y + factor_p1p2 * p1p2.y));
|
|
3280
|
+
Point<float> orth_p1p2((t_p1p2.x - p.x),(t_p1p2.y - p.y));
|
|
3281
|
+
float orth_p1p2_length = sqrtf(orth_p1p2.x * orth_p1p2.x + orth_p1p2.y * orth_p1p2.y);
|
|
3282
|
+
float ea = acos(orth_p1p2.x / orth_p1p2_length);
|
|
3283
|
+
|
|
3284
|
+
if (orth_p1p2.y < 0) ea = 2 * M_PI - ea;
|
|
3285
|
+
if ((sa > ea) && ((sa - ea) < M_PI)) anticlockwise = true;
|
|
3286
|
+
if ((sa < ea) && ((ea - sa) > M_PI)) anticlockwise = true;
|
|
3287
|
+
|
|
3288
|
+
cairo_line_to(ctx, t_p1p0.x, t_p1p0.y);
|
|
3289
|
+
|
|
3290
|
+
if (anticlockwise && M_PI * 2 != radius) {
|
|
3291
|
+
cairo_arc_negative(ctx
|
|
3292
|
+
, p.x
|
|
3293
|
+
, p.y
|
|
3294
|
+
, radius
|
|
3295
|
+
, sa
|
|
3296
|
+
, ea);
|
|
3297
|
+
} else {
|
|
3298
|
+
cairo_arc(ctx
|
|
3299
|
+
, p.x
|
|
3300
|
+
, p.y
|
|
3301
|
+
, radius
|
|
3302
|
+
, sa
|
|
3303
|
+
, ea);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
/*
|
|
3308
|
+
* Adds an ellipse to the path which is centered at (x, y) position with the
|
|
3309
|
+
* radii radiusX and radiusY starting at startAngle and ending at endAngle
|
|
3310
|
+
* going in the given direction by anticlockwise (defaulting to clockwise).
|
|
3311
|
+
*/
|
|
3312
|
+
|
|
3313
|
+
NAN_METHOD(Context2d::Ellipse) {
|
|
3314
|
+
double args[7];
|
|
3315
|
+
if(!checkArgs(info, args, 7))
|
|
3316
|
+
return;
|
|
3317
|
+
|
|
3318
|
+
double radiusX = args[2];
|
|
3319
|
+
double radiusY = args[3];
|
|
3320
|
+
|
|
3321
|
+
if (radiusX == 0 || radiusY == 0) return;
|
|
3322
|
+
|
|
3323
|
+
double x = args[0];
|
|
3324
|
+
double y = args[1];
|
|
3325
|
+
double rotation = args[4];
|
|
3326
|
+
double startAngle = args[5];
|
|
3327
|
+
double endAngle = args[6];
|
|
3328
|
+
bool anticlockwise = Nan::To<bool>(info[7]).FromMaybe(false);
|
|
3329
|
+
|
|
3330
|
+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
|
|
3331
|
+
cairo_t *ctx = context->context();
|
|
3332
|
+
|
|
3333
|
+
// See https://www.cairographics.org/cookbook/ellipses/
|
|
3334
|
+
double xRatio = radiusX / radiusY;
|
|
3335
|
+
|
|
3336
|
+
cairo_matrix_t save_matrix;
|
|
3337
|
+
cairo_get_matrix(ctx, &save_matrix);
|
|
3338
|
+
cairo_translate(ctx, x, y);
|
|
3339
|
+
cairo_rotate(ctx, rotation);
|
|
3340
|
+
cairo_scale(ctx, xRatio, 1.0);
|
|
3341
|
+
cairo_translate(ctx, -x, -y);
|
|
3342
|
+
if (anticlockwise && M_PI * 2 != args[4]) {
|
|
3343
|
+
cairo_arc_negative(ctx,
|
|
3344
|
+
x,
|
|
3345
|
+
y,
|
|
3346
|
+
radiusY,
|
|
3347
|
+
startAngle,
|
|
3348
|
+
endAngle);
|
|
3349
|
+
} else {
|
|
3350
|
+
cairo_arc(ctx,
|
|
3351
|
+
x,
|
|
3352
|
+
y,
|
|
3353
|
+
radiusY,
|
|
3354
|
+
startAngle,
|
|
3355
|
+
endAngle);
|
|
3356
|
+
}
|
|
3357
|
+
cairo_set_matrix(ctx, &save_matrix);
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
#undef CHECK_RECEIVER
|