3 const RP_INIT_CONNECTION
= 1;
4 const RP_UPDATE_DISPLAY_MODE
= 2;
5 const RP_CLOSE_CONNECTION
= 3;
6 const RP_GET_SYSTEM_PALETTE
= 4;
7 const RP_GET_SYSTEM_PALETTE_RESULT
= 5;
9 const RP_CREATE_STATE
= 20;
10 const RP_DELETE_STATE
= 21;
11 const RP_ENABLE_SYNC_DRAWING
= 22;
12 const RP_DISABLE_SYNC_DRAWING
= 23;
13 const RP_INVALIDATE_RECT
= 24;
14 const RP_INVALIDATE_REGION
= 25;
16 const RP_SET_OFFSETS
= 40;
17 const RP_SET_HIGH_COLOR
= 41;
18 const RP_SET_LOW_COLOR
= 42;
19 const RP_SET_PEN_SIZE
= 43;
20 const RP_SET_STROKE_MODE
= 44;
21 const RP_SET_BLENDING_MODE
= 45;
22 const RP_SET_PATTERN
= 46;
23 const RP_SET_DRAWING_MODE
= 47;
24 const RP_SET_FONT
= 48;
25 const RP_SET_TRANSFORM
= 49;
27 const RP_CONSTRAIN_CLIPPING_REGION
= 60;
28 const RP_COPY_RECT_NO_CLIPPING
= 61;
29 const RP_INVERT_RECT
= 62;
30 const RP_DRAW_BITMAP
= 63;
31 const RP_DRAW_BITMAP_RECTS
= 64;
33 const RP_STROKE_ARC
= 80;
34 const RP_STROKE_BEZIER
= 81;
35 const RP_STROKE_ELLIPSE
= 82;
36 const RP_STROKE_POLYGON
= 83;
37 const RP_STROKE_RECT
= 84;
38 const RP_STROKE_ROUND_RECT
= 85;
39 const RP_STROKE_SHAPE
= 86;
40 const RP_STROKE_TRIANGLE
= 87;
41 const RP_STROKE_LINE
= 88;
42 const RP_STROKE_LINE_ARRAY
= 89;
44 const RP_FILL_ARC
= 100;
45 const RP_FILL_BEZIER
= 101;
46 const RP_FILL_ELLIPSE
= 102;
47 const RP_FILL_POLYGON
= 103;
48 const RP_FILL_RECT
= 104;
49 const RP_FILL_ROUND_RECT
= 105;
50 const RP_FILL_SHAPE
= 106;
51 const RP_FILL_TRIANGLE
= 107;
52 const RP_FILL_REGION
= 108;
54 const RP_FILL_ARC_GRADIENT
= 120;
55 const RP_FILL_BEZIER_GRADIENT
= 121;
56 const RP_FILL_ELLIPSE_GRADIENT
= 122;
57 const RP_FILL_POLYGON_GRADIENT
= 123;
58 const RP_FILL_RECT_GRADIENT
= 124;
59 const RP_FILL_ROUND_RECT_GRADIENT
= 125;
60 const RP_FILL_SHAPE_GRADIENT
= 126;
61 const RP_FILL_TRIANGLE_GRADIENT
= 127;
62 const RP_FILL_REGION_GRADIENT
= 128;
64 const RP_STROKE_POINT_COLOR
= 140;
65 const RP_STROKE_LINE_1PX_COLOR
= 141;
66 const RP_STROKE_RECT_1PX_COLOR
= 142;
68 const RP_FILL_RECT_COLOR
= 160;
69 const RP_FILL_REGION_COLOR_NO_CLIPPING
= 161;
71 const RP_DRAW_STRING
= 180;
72 const RP_DRAW_STRING_WITH_OFFSETS
= 181;
73 const RP_DRAW_STRING_RESULT
= 182;
74 const RP_STRING_WIDTH
= 183;
75 const RP_STRING_WIDTH_RESULT
= 184;
76 const RP_READ_BITMAP
= 185;
77 const RP_READ_BITMAP_RESULT
= 186;
79 const RP_SET_CURSOR
= 200;
80 const RP_SET_CURSOR_VISIBLE
= 201;
81 const RP_MOVE_CURSOR_TO
= 202;
83 const RP_MOUSE_MOVED
= 220;
84 const RP_MOUSE_DOWN
= 221;
85 const RP_MOUSE_UP
= 222;
86 const RP_MOUSE_WHEEL_CHANGED
= 223;
88 const RP_KEY_DOWN
= 240;
89 const RP_KEY_UP
= 241;
90 const RP_UNMAPPED_KEY_DOWN
= 242;
91 const RP_UNMAPPED_KEY_UP
= 243;
92 const RP_MODIFIERS_CHANGED
= 244;
99 const B_OP_INVERT
= 3;
101 const B_OP_SUBTRACT
= 5;
102 const B_OP_BLEND
= 6;
105 const B_OP_SELECT
= 9;
106 const B_OP_ALPHA
= 10;
110 const B_NO_COLOR_SPACE
= 0x0000;
111 const B_RGB32
= 0x0008; // BGR- 8:8:8:8
112 const B_RGBA32
= 0x2008; // BGRA 8:8:8:8
113 const B_RGB24
= 0x0003; // BGR 8:8:8
114 const B_RGB16
= 0x0005; // BGR 5:6:5
115 const B_RGB15
= 0x0010; // BGR- 5:5:5:1
116 const B_RGBA15
= 0x2010; // BGRA 5:5:5:1
117 const B_CMAP8
= 0x0004; // 256 color index table
118 const B_GRAY8
= 0x0002; // 256 greyscale table
119 const B_GRAY1
= 0x0001; // Each bit represents a single pixel
120 const B_RGB32_BIG
= 0x1008; // -RGB 8:8:8:8
121 const B_RGBA32_BIG
= 0x3008; // ARGB 8:8:8:8
122 const B_RGB24_BIG
= 0x1003; // RGB 8:8:8
123 const B_RGB16_BIG
= 0x1005; // RGB 5:6:5
124 const B_RGB15_BIG
= 0x1010; // -RGB 1:5:5:5
125 const B_RGBA15_BIG
= 0x3010; // ARGB 1:5:5:5
127 const B_TRANSPARENT_MAGIC_CMAP8
= 0xff;
128 const B_TRANSPARENT_MAGIC_RGBA15
= 0x39ce;
129 const B_TRANSPARENT_MAGIC_RGBA15_BIG
= 0xce39;
130 const B_TRANSPARENT_MAGIC_RGBA32
= 0xff777477;
131 const B_TRANSPARENT_MAGIC_RGBA32_BIG
= 0x777477ff;
135 const B_PIXEL_ALPHA
= 0;
136 const B_CONSTANT_ALPHA
= 1;
140 const B_ALPHA_OVERLAY
= 0;
141 const B_ALPHA_COMPOSITE
= 1;
145 const B_GRADIENT_TYPE_LINEAR
= 0;
146 const B_GRADIENT_TYPE_RADIAL
= 1;
147 const B_GRADIENT_TYPE_RADIAL_FOCUS
= 2;
148 const B_GRADIENT_TYPE_DIAMOND
= 3;
149 const B_GRADIENT_TYPE_CONIC
= 4;
150 const B_GRADIENT_TYPE_NONE
= 5;
154 const B_SHAPE_OP_MOVE_TO
= 0x80000000;
155 const B_SHAPE_OP_CLOSE
= 0x40000000;
156 const B_SHAPE_OP_BEZIER_TO
= 0x20000000;
157 const B_SHAPE_OP_LINE_TO
= 0x10000000;
158 const B_SHAPE_OP_SMALL_ARC_TO_CCW
= 0x08000000;
159 const B_SHAPE_OP_SMALL_ARC_TO_CW
= 0x04000000;
160 const B_SHAPE_OP_LARGE_ARC_TO_CCW
= 0x02000000;
161 const B_SHAPE_OP_LARGE_ARC_TO_CW
= 0x01000000;
165 const B_ROUND_JOIN
= 0;
166 const B_MITER_JOIN
= 1;
167 const B_BEVEL_JOIN
= 2;
168 const B_BUTT_JOIN
= 3;
169 const B_SQUARE_JOIN
= 4;
173 const B_ROUND_CAP
= B_ROUND_JOIN
;
174 const B_BUTT_CAP
= B_BUTT_JOIN
;
175 const B_SQUARE_CAP
= B_SQUARE_JOIN
;
178 const B_DEFAULT_MITER_LIMIT
= 10;
182 const B_SHIFT_KEY
= 0x00000001;
183 const B_COMMAND_KEY
= 0x00000002;
184 const B_CONTROL_KEY
= 0x00000004;
185 const B_CAPS_LOCK
= 0x00000008;
186 const B_SCROLL_LOCK
= 0x00000010;
187 const B_NUM_LOCK
= 0x00000020;
188 const B_OPTION_KEY
= 0x00000040;
189 const B_MENU_KEY
= 0x00000080;
190 const B_LEFT_SHIFT_KEY
= 0x00000100;
191 const B_RIGHT_SHIFT_KEY
= 0x00000200;
192 const B_LEFT_COMMAND_KEY
= 0x00000400;
193 const B_RIGHT_COMMAND_KEY
= 0x00000800;
194 const B_LEFT_CONTROL_KEY
= 0x00001000;
195 const B_RIGHT_CONTROL_KEY
= 0x00002000;
196 const B_LEFT_OPTION_KEY
= 0x00004000;
197 const B_RIGHT_OPTION_KEY
= 0x00008000;
205 function StreamingDataView(buffer
, littleEndian
, byteOffset
, byteLength
)
207 this.buffer
= buffer
;
208 this.dataView
= new DataView(buffer
.buffer
, byteOffset
, byteLength
);
210 this.littleEndian
= littleEndian
;
211 this.textDecoder
= new TextDecoder('utf-8');
212 this.textEncoder
= new TextEncoder();
216 StreamingDataView
.prototype.rewind = function()
222 StreamingDataView
.prototype.readInt8 = function()
224 return this.dataView
.getInt8(this.position
++);
228 StreamingDataView
.prototype.readUint8 = function()
230 return this.dataView
.getUint8(this.position
++);
234 StreamingDataView
.prototype.readInt16 = function()
236 var result
= this.dataView
.getInt16(this.position
, this.littleEndian
);
242 StreamingDataView
.prototype.readUint16 = function()
244 var result
= this.dataView
.getUint16(this.position
, this.littleEndian
);
250 StreamingDataView
.prototype.readInt32 = function()
252 var result
= this.dataView
.getInt32(this.position
, this.littleEndian
);
258 StreamingDataView
.prototype.readUint32 = function()
260 var result
= this.dataView
.getUint32(this.position
, this.littleEndian
);
266 StreamingDataView
.prototype.readFloat32 = function()
268 var result
= this.dataView
.getFloat32(this.position
, this.littleEndian
);
274 StreamingDataView
.prototype.readFloat64 = function()
276 var result
= this.dataView
.getFloat64(this.position
, this.littleEndian
);
282 StreamingDataView
.prototype.readString = function(length
)
284 var where
= this.dataView
.byteOffset
+ this.position
;
285 var part
= this.buffer
.slice(where
, where
+ length
);
286 var result
= this.textDecoder
.decode(part
);
287 this.position
+= length
;
292 StreamingDataView
.prototype.readInto = function(typedArray
)
294 var where
= this.dataView
.byteOffset
+ this.position
;
295 typedArray
.set(this.buffer
.slice(where
, where
+ typedArray
.byteLength
));
296 this.position
+= typedArray
.byteLength
;
300 StreamingDataView
.prototype.writeInt8 = function(value
)
302 this.dataView
.setInt8(this.position
++, value
);
306 StreamingDataView
.prototype.writeUint8 = function(value
)
308 this.dataView
.setUint8(this.position
++, value
);
312 StreamingDataView
.prototype.writeInt16 = function(value
)
314 this.dataView
.setInt16(this.position
, value
, this.littleEndian
);
319 StreamingDataView
.prototype.writeUint16 = function(value
)
321 this.dataView
.setUint16(this.position
, value
, this.littleEndian
);
326 StreamingDataView
.prototype.writeInt32 = function(value
)
328 this.dataView
.setInt32(this.position
, value
, this.littleEndian
);
333 StreamingDataView
.prototype.writeUint32 = function(value
)
335 this.dataView
.setUint32(this.position
, value
, this.littleEndian
);
340 StreamingDataView
.prototype.writeFloat32 = function(value
)
342 this.dataView
.setFloat32(this.position
, value
, this.littleEndian
);
347 StreamingDataView
.prototype.writeFloat64 = function(value
)
349 this.dataView
.setFloat64(this.position
, value
, this.littleEndian
);
354 StreamingDataView
.prototype.writeString = function(string
)
356 var encoded
= this.textEncoder
.encode(string
);
357 this.writeUint32(encoded
.length
);
358 this.buffer
.set(encoded
, this.position
);
359 this.position
+= encoded
.length
;
363 StreamingDataView
.prototype.setUint32 = function(byteOffset
, value
)
365 this.dataView
.setUint32(byteOffset
, value
, this.littleEndian
);
369 StreamingDataView
.prototype.pad = function(length
)
371 this.buffer
.fill(0, this.position
, this.position
+ length
);
372 this.position
+= length
;
376 function RemotePoint(remoteMessage
)
379 this.readFrom(remoteMessage
);
388 RemotePoint
.prototype.readFrom = function(remoteMessage
)
390 this.x
= remoteMessage
.dataView
.readFloat32();
391 this.y
= remoteMessage
.dataView
.readFloat32();
396 RemotePoint
.prototype.writeTo = function(remoteMessage
)
398 remoteMessage
.dataView
.writeFloat32(this.x
);
399 remoteMessage
.dataView
.writeFloat32(this.y
);
404 function RemoteRect(remoteMessage
)
407 this.readFrom(remoteMessage
);
418 RemoteRect
.prototype.readFrom = function(remoteMessage
)
420 this.left
= remoteMessage
.dataView
.readFloat32();
421 this.top
= remoteMessage
.dataView
.readFloat32();
422 this.right
= remoteMessage
.dataView
.readFloat32();
423 this.bottom
= remoteMessage
.dataView
.readFloat32();
428 RemoteRect
.prototype.width = function()
430 return this.right
- this.left
+ 1;
434 RemoteRect
.prototype.height = function()
436 return this.bottom
- this.top
+ 1;
440 RemoteRect
.prototype.integerWidth = function()
442 return Math
.ceil(this.right
- this.left
);
446 RemoteRect
.prototype.integerHeight = function()
448 return Math
.ceil(this.bottom
- this.top
);
452 RemoteRect
.prototype.centerX = function()
454 return this.left
+ this.width() / 2;
458 RemoteRect
.prototype.centerY = function()
460 return this.top
+ this.height() / 2;
464 RemoteRect
.prototype.apply = function(apply
)
466 var left
= Math
.floor(this.left
);
467 var top
= Math
.floor(this.top
);
468 var right
= Math
.ceil(this.right
);
469 var bottom
= Math
.ceil(this.bottom
);
470 apply(left
, top
, right
- left
+ 1, bottom
- top
+ 1);
474 RemoteRect
.prototype.applyAsEllipse = function(context
, which
)
477 context
.ellipse(this.centerX(), this.centerY(), this.width() / 2,
478 this.height() / 2, 0, Math
.PI
* 2, false);
483 function RemoteColor(remoteMessage
)
486 this.readFrom(remoteMessage
);
497 RemoteColor
.prototype.readFrom = function(remoteMessage
)
499 this.red
= remoteMessage
.dataView
.readUint8();
500 this.green
= remoteMessage
.dataView
.readUint8();
501 this.blue
= remoteMessage
.dataView
.readUint8();
502 this.alpha
= remoteMessage
.dataView
.readUint8();
507 RemoteColor
.prototype.fromUint32 = function(value
)
509 this.red
= value
& 0xff;
510 this.green
= value
>> 8 & 0xff;
511 this.blue
= value
>> 16 & 0xff;
512 this.alpha
= value
>> 24 & 0xff;
517 RemoteColor
.prototype.toColor = function(unsetAlpha
)
519 return 'rgba(' + this.red
+ ', ' + this.green
+ ', ' + this.blue
+ ', '
520 + (unsetAlpha
? 1 : this.alpha
/ 255) + ')';
524 RemoteColor
.prototype.toUint32 = function(unsetAlpha
)
526 return this.red
| this.green
<< 8 | this.blue
<< 16
527 | (unsetAlpha
? 255 : this.alpha
) << 24;
531 function RemoteFont(remoteMessage
)
534 this.readFrom(remoteMessage
);
544 this.falseBoldWidth
= 0;
552 RemoteFont
.prototype.readFrom = function(remoteMessage
)
554 this.direction
= remoteMessage
.dataView
.readUint8();
555 this.encoding
= remoteMessage
.dataView
.readUint8();
556 this.flags
= remoteMessage
.dataView
.readUint32();
557 this.spacing
= remoteMessage
.dataView
.readUint8();
558 this.shear
= remoteMessage
.dataView
.readFloat32();
559 this.rotation
= remoteMessage
.dataView
.readFloat32();
560 this.falseBoldWidth
= remoteMessage
.dataView
.readFloat32();
561 this.size
= remoteMessage
.dataView
.readFloat32();
562 this.face
= remoteMessage
.dataView
.readUint16();
563 this.family
= remoteMessage
.dataView
.readUint16();
564 this.style
= remoteMessage
.dataView
.readUint16();
569 function RemoteTransform(remoteMessage
)
572 this.readFrom(remoteMessage
);
580 RemoteTransform
.prototype.readFrom = function(remoteMessage
)
582 var isIdentity
= remoteMessage
.dataView
.readUint8();
588 this.sx
= remoteMessage
.dataView
.readFloat64();
589 this.shy
= remoteMessage
.dataView
.readFloat64();
590 this.shx
= remoteMessage
.dataView
.readFloat64();
591 this.sy
= remoteMessage
.dataView
.readFloat64();
592 this.tx
= remoteMessage
.dataView
.readFloat64();
593 this.ty
= remoteMessage
.dataView
.readFloat64();
598 RemoteTransform
.prototype.setIdentity = function()
610 RemoteTransform
.prototype.isIdentity = function()
612 return this.sx
== 1 && this.shy
== 0 && this.shx
== 0 && this.sy
== 1
613 && this.tx
== 0 && this.ty
== 0;
617 RemoteTransform
.prototype.apply = function(context
)
619 context
.transform(this.sx
, this.shy
, this.shx
, this.sy
, this.tx
,
624 function RemoteBitmap(remoteMessage
, unsetAlpha
, colorSpace
, flags
)
627 this.readFrom(remoteMessage
, unsetAlpha
, colorSpace
, flags
);
633 RemoteBitmap
.prototype.readFrom = function(remoteMessage
, unsetAlpha
,
636 this.width
= remoteMessage
.dataView
.readUint32();
637 this.height
= remoteMessage
.dataView
.readUint32();
638 this.bytesPerRow
= remoteMessage
.dataView
.readUint32();
640 if (colorSpace
!= undefined) {
641 this.colorSpace
= colorSpace
;
644 this.colorSpace
= remoteMessage
.dataView
.readUint32();
645 this.flags
= remoteMessage
.dataView
.readUint32();
648 this.bitsLength
= remoteMessage
.dataView
.readUint32();
650 this.canvas
= document
.createElement('canvas');
651 this.canvas
.width
= this.width
;
652 this.canvas
.height
= this.height
;
654 if (this.width
== 0 || this.height
== 0)
657 var context
= this.canvas
.getContext('2d');
658 var imageData
= context
.createImageData(this.width
, this.height
);
659 switch (this.colorSpace
) {
661 remoteMessage
.dataView
.readInto(imageData
.data
);
662 var output
= new Uint32Array(imageData
.data
.buffer
);
664 for (var i
= 0; i
< imageData
.data
.length
/ 4; i
++) {
665 output
[i
] = (output
[i
] & 0xff) << 16 | (output
[i
] >> 16 & 0xff)
666 | (output
[i
] & 0xff00ff00);
670 for (var i
= 0; i
< imageData
.data
.length
/ 4; i
++)
671 output
[i
] |= 0xff000000;
677 remoteMessage
.dataView
.readInto(imageData
.data
);
678 var output
= new Uint32Array(imageData
.data
.buffer
);
680 for (var i
= 0; i
< imageData
.data
.length
/ 4; i
++) {
681 output
[i
] = (output
[i
] & 0xff) << 16 | (output
[i
] >> 16 & 0xff)
682 | (output
[i
] & 0xff00) | 0xff000000;
684 if (!unsetAlpha
&& output
[i
] == B_TRANSPARENT_MAGIC_RGBA32
)
685 output
[i
] &= 0x00ffffff;
690 var line
= new Uint8Array(this.bytesPerRow
);
693 for (var y
= 0; y
< this.height
; y
++) {
694 remoteMessage
.dataView
.readInto(line
);
696 for (var x
= 0; x
< this.width
; x
++) {
697 imageData
.data
[position
++] = line
[x
* 3 + 2];
698 imageData
.data
[position
++] = line
[x
* 3 + 1];
699 imageData
.data
[position
++] = line
[x
* 3 + 0];
700 imageData
.data
[position
++] = 255;
707 var lineBuffer
= new Uint8Array(this.bytesPerRow
);
708 var line
= new Uint16Array(lineBuffer
.buffer
);
711 for (var y
= 0; y
< this.height
; y
++) {
712 remoteMessage
.dataView
.readInto(lineBuffer
);
714 for (var x
= 0; x
< this.width
; x
++) {
715 imageData
.data
[position
++] = (line
[x
] & 0xf800) >> 8;
716 imageData
.data
[position
++] = (line
[x
] & 0x07e0) >> 3;
717 imageData
.data
[position
++] = (line
[x
] & 0x001f) << 3;
718 imageData
.data
[position
++] = 255;
725 var line
= new Uint8Array(this.bytesPerRow
);
726 var output
= new Uint32Array(imageData
.data
.buffer
);
729 for (var y
= 0; y
< this.height
; y
++) {
730 remoteMessage
.dataView
.readInto(line
);
732 for (var x
= 0; x
< this.width
; x
++)
733 output
[position
++] = gSystemPalette
[line
[x
]];
739 var source
= new Uint8Array(this.bitsLength
);
740 remoteMessage
.dataView
.readInto(source
);
741 for (var i
= 0; i
< imageData
.data
.length
/ 4; i
++) {
742 imageData
.data
[i
* 4 + 0] = source
[i
];
743 imageData
.data
[i
* 4 + 1] = source
[i
];
744 imageData
.data
[i
* 4 + 2] = source
[i
];
745 imageData
.data
[i
* 4 + 3] = 255;
750 var source
= new Uint8Array(this.bitsLength
);
751 remoteMessage
.dataView
.readInto(source
);
752 for (var i
= 0; i
< imageData
.data
.length
/ 4; i
++) {
753 var value
= (source
[Math
.floor(i
/ 8)] >> i
% 8) & 1 ? 255 : 0;
754 imageData
.data
[i
* 4 + 0] = value
;
755 imageData
.data
[i
* 4 + 1] = value
;
756 imageData
.data
[i
* 4 + 2] = value
;
757 imageData
.data
[i
* 4 + 3] = 255;
762 console
.warn('color space not implemented: ' + this.colorSpace
);
766 context
.putImageData(imageData
, 0, 0);
771 function RemotePattern(remoteMessage
)
773 this.data
= new Uint8Array(8);
776 this.readFrom(remoteMessage
);
782 RemotePattern
.staticCanvas
= document
.createElement('canvas');
783 RemotePattern
.staticCanvas
.width
= RemotePattern
.staticCanvas
.height
= 8;
784 RemotePattern
.staticContext
= RemotePattern
.staticCanvas
.getContext('2d');
785 RemotePattern
.staticImageData
786 = RemotePattern
.staticContext
.createImageData(8, 8);
787 RemotePattern
.staticPixels
788 = new Uint32Array(RemotePattern
.staticImageData
.data
.buffer
);
791 RemotePattern
.prototype.readFrom = function(remoteMessage
)
793 remoteMessage
.dataView
.readInto(this.data
);
798 RemotePattern
.prototype.isSolid = function()
800 var common
= this.data
[0];
801 return this.data
.every(function(value
) { return value
== common
; });
805 RemotePattern
.prototype.toPattern = function(context
, lowColor
, highColor
)
807 for (var i
= 0; i
< this.data
.length
* 8; i
++) {
808 RemotePattern
.staticPixels
[i
]
809 = (this.data
[i
/ 8 | 0] & 1 << 7 - i
% 8) == 0
810 ? lowColor
: highColor
;
813 // Apparently supplying ImageData to createPattern fails in Chrome.
814 RemotePattern
.staticContext
.putImageData(RemotePattern
.staticImageData
, 0,
816 return context
.createPattern(RemotePattern
.staticCanvas
, 'repeat');
820 function RemoteGradient(remoteMessage
, context
, unsetAlpha
)
823 this.readFrom(remoteMessage
, context
, unsetAlpha
);
827 this.gradient
= '#00000000';
831 RemoteGradient
.prototype.readFrom = function(remoteMessage
, context
, unsetAlpha
)
833 this.type
= remoteMessage
.dataView
.readUint32();
835 case B_GRADIENT_TYPE_LINEAR
:
836 var start
= new RemotePoint(remoteMessage
);
837 var end
= new RemotePoint(remoteMessage
);
839 this.gradient
= context
.createLinearGradient(start
.x
, start
.y
,
843 case B_GRADIENT_TYPE_RADIAL
:
844 var center
= new RemotePoint(remoteMessage
);
845 var radius
= remoteMessage
.dataView
.readFloat32();
847 this.gradient
= context
.createRadialGradient(center
.x
, center
.y
, 0,
848 center
.x
, center
.y
, radius
);
852 console
.warn('gradient type not implemented: ' + this.type
);
853 this.gradient
= 'black';
857 var stopCount
= remoteMessage
.dataView
.readUint32();
858 for (var i
= 0; i
< stopCount
; i
++) {
859 var color
= remoteMessage
.readColor(unsetAlpha
);
860 var offset
= remoteMessage
.dataView
.readFloat32() / 255;
861 this.gradient
.addColorStop(offset
, color
);
868 function RemoteShape(remoteMessage
)
871 this.readFrom(remoteMessage
);
882 RemoteShape
.prototype.readFrom = function(remoteMessage
)
884 this.bounds
= new RemoteRect(remoteMessage
);
886 this.opCount
= remoteMessage
.dataView
.readUint32();
887 this.ops
= new Array(this.opCount
);
888 for (var i
= 0; i
< this.opCount
; i
++)
889 this.ops
[i
] = remoteMessage
.dataView
.readUint32();
891 this.pointCount
= remoteMessage
.dataView
.readUint32();
892 this.points
= new Array(this.pointCount
);
893 for (var i
= 0; i
< this.pointCount
; i
++)
894 this.points
[i
] = new RemotePoint(remoteMessage
);
900 RemoteShape
.prototype.play = function(context
)
903 for (var i
= 0; i
< this.opCount
; i
++) {
904 var op
= this.ops
[i
] & 0xff000000;
905 var count
= this.ops
[i
] & 0x00ffffff;
907 if (op
& B_SHAPE_OP_MOVE_TO
) {
908 var point
= this.points
[pointIndex
++];
909 context
.moveTo(point
.x
, point
.y
);
912 if (op
& B_SHAPE_OP_LINE_TO
) {
913 for (var j
= 0; j
< count
; j
++) {
914 var point
= this.points
[pointIndex
++];
915 context
.lineTo(point
.x
, point
.y
);
919 if (op
& B_SHAPE_OP_BEZIER_TO
) {
920 for (var j
= 0; j
< count
/ 3; j
++) {
921 var control1
= this.points
[pointIndex
++];
922 var control2
= this.points
[pointIndex
++];
923 var to
= this.points
[pointIndex
++];
924 context
.bezierCurveTo(control1
.x
, control1
.y
, control2
.x
,
925 control2
.y
, to
.x
, to
.y
);
929 if (op
& (B_SHAPE_OP_LARGE_ARC_TO_CW
| B_SHAPE_OP_LARGE_ARC_TO_CCW
930 | B_SHAPE_OP_SMALL_ARC_TO_CW
| B_SHAPE_OP_SMALL_ARC_TO_CCW
)) {
932 console
.warn('shape op arc to not implemented');
933 for (var j
= 0; j
< count
/ 3; j
++)
937 if (op
& B_SHAPE_OP_CLOSE
)
943 function RemoteMessage(socket
)
945 this.socket
= socket
;
949 RemoteMessage
.staticRemoteColor
= new RemoteColor();
952 RemoteMessage
.prototype.allocate = function(bufferSize
)
954 this.buffer
= new Uint8Array(bufferSize
);
955 this.dataView
= new StreamingDataView(this.buffer
, true);
959 RemoteMessage
.prototype.ensureBufferSize = function(bufferSize
)
961 if (this.buffer
.byteLength
< bufferSize
)
962 this.allocate(bufferSize
);
966 RemoteMessage
.prototype.attach = function(buffer
, byteOffset
)
968 var bytesLeft
= buffer
.byteLength
- byteOffset
;
972 this.buffer
= buffer
;
973 this.dataView
= new StreamingDataView(this.buffer
, true, byteOffset
);
974 this.messageCode
= this.dataView
.readUint16();
975 this.messageSize
= this.dataView
.readUint32();
976 if (this.messageSize
< 6)
979 return this.messageSize
<= bytesLeft
;
983 RemoteMessage
.prototype.code = function()
985 return this.messageCode
;
989 RemoteMessage
.prototype.size = function()
991 return this.messageSize
;
995 RemoteMessage
.prototype.start = function(code
)
997 this.dataView
.rewind();
998 this.dataView
.writeUint16(code
);
999 this.dataView
.writeUint32(0);
1000 // Placeholder for size field.
1004 RemoteMessage
.prototype.flush = function()
1006 this.dataView
.setUint32(2, this.dataView
.position
);
1007 this.socket
.send(this.buffer
.slice(0, this.dataView
.position
));
1011 RemoteMessage
.prototype.readColor = function(unsetAlpha
)
1013 return RemoteMessage
.staticRemoteColor
.readFrom(this).toColor(unsetAlpha
);
1017 function RemoteState(session
, token
)
1019 this.session
= session
;
1022 this.lowColor
= new RemoteColor().fromUint32(0xffffffff);
1023 this.highColor
= new RemoteColor().fromUint32(0xff000000);
1026 this.lineCap
= 'butt';
1027 this.lineJoin
= 'miter';
1028 this.miterLimit
= B_DEFAULT_MITER_LIMIT
;
1029 this.drawingMode
= 'source-over';
1031 this.pattern
= new RemotePattern();
1032 this.font
= new RemoteFont();
1033 this.transform
= new RemoteTransform();
1037 RemoteState
.prototype.applyContext = function()
1039 var context
= this.session
.context
;
1040 if (!this.invalidated
&& context
.currentToken
== this.token
)
1043 this.session
.removeClipping();
1045 if (this.blendModesEnabled
&& this.constantAlpha
)
1046 context
.globalAlpha
= this.highColor
.alpha
/ 255;
1048 context
.globalAlpha
= 1;
1051 if (this.pattern
.isSolid()) {
1052 style
= this.pattern
.data
[0] == 0 ? this.lowColor
: this.highColor
;
1054 style
= style
== this.lowColor
? 'transparent' : 'white';
1056 style
= style
.toColor(this.unsetAlpha
);
1058 style
= this.pattern
.toPattern(context
,
1059 this.invert
? 0x00000000 : this.lowColor
.toUint32(this.unsetAlpha
),
1061 ? 0xffffffff : this.highColor
.toUint32(this.unsetAlpha
));
1064 context
.fillStyle
= context
.strokeStyle
= style
;
1066 context
.font
= this.font
.size
+ 'px sans';
1067 context
.globalCompositeOperation
= this.drawingMode
;
1068 context
.lineWidth
= this.penSize
;
1069 context
.lineCap
= this.lineCap
;
1070 context
.lineJoin
= this.lineJoin
;
1071 context
.miterLimit
= this.miterLimit
;
1073 context
.resetTransform();
1075 this.session
.applyClipping(this.clipRects
);
1077 if (!this.transform
.isIdentity()) {
1078 context
.translate(this.xOffset
, this.yOffset
);
1079 this.transform
.apply(context
);
1080 context
.translate(-this.xOffset
, -this.yOffset
);
1083 context
.currentToken
= this.token
;
1084 this.invalidated
= false;
1088 RemoteState
.prototype.prepareForRect = function()
1090 this.session
.context
.lineJoin
= 'miter';
1091 this.session
.context
.miterLimit
= 10;
1095 RemoteState
.prototype.messageReceived = function(remoteMessage
, reply
)
1097 var context
= this.session
.context
;
1099 switch (remoteMessage
.code()) {
1100 case RP_ENABLE_SYNC_DRAWING
:
1101 case RP_DISABLE_SYNC_DRAWING
:
1102 console
.warn('sync drawing en-/disable not implemented');
1105 case RP_SET_LOW_COLOR
:
1106 this.lowColor
.readFrom(remoteMessage
);
1107 this.invalidated
= true;
1110 case RP_SET_HIGH_COLOR
:
1111 this.highColor
.readFrom(remoteMessage
);
1112 this.invalidated
= true;
1115 case RP_SET_OFFSETS
:
1116 this.xOffset
= remoteMessage
.dataView
.readInt32();
1117 this.yOffset
= remoteMessage
.dataView
.readInt32();
1118 this.invalidated
= true;
1122 this.font
= new RemoteFont(remoteMessage
);
1123 this.invalidated
= true;
1126 case RP_SET_TRANSFORM
:
1127 this.transform
= new RemoteTransform(remoteMessage
);
1128 this.invalidated
= true;
1131 case RP_SET_PATTERN
:
1132 this.pattern
= new RemotePattern(remoteMessage
);
1133 this.invalidated
= true;
1136 case RP_SET_PEN_SIZE
:
1137 this.penSize
= remoteMessage
.dataView
.readFloat32();
1138 this.invalidated
= true;
1141 case RP_SET_STROKE_MODE
:
1142 switch (remoteMessage
.dataView
.readUint32()) {
1144 this.lineCap
= 'round';
1148 this.lineCap
= 'butt';
1152 this.lineCap
= 'square';
1156 var lineJoin
= remoteMessage
.dataView
.readUint32();
1159 this.lineJoin
= 'round';
1163 this.lineJoin
= 'miter';
1167 this.lineJoin
= 'bevel';
1171 console
.warn('line join not implemented: ' + join
);
1175 this.miterLimit
= remoteMessage
.dataView
.readFloat32();
1176 this.invalidated
= true;
1179 case RP_SET_BLENDING_MODE
:
1180 var sourceAlpha
= remoteMessage
.dataView
.readUint32();
1181 this.constantAlpha
= sourceAlpha
== B_CONSTANT_ALPHA
;
1182 if (this.blendModesEnabled
)
1183 this.unsetAlpha
= this.constantAlpha
;
1185 var alphaFunction
= remoteMessage
.dataView
.readUint32();
1186 if (alphaFunction
!= B_ALPHA_OVERLAY
)
1187 console
.warn('alpha function not supported: ' + alphaFunction
);
1189 this.invalidated
= true;
1192 case RP_SET_DRAWING_MODE
:
1193 var drawingMode
= remoteMessage
.dataView
.readUint32();
1195 this.unsetAlpha
= false;
1196 this.blendModesEnabled
= false;
1197 this.invert
= false;
1199 switch (drawingMode
) {
1201 this.unsetAlpha
= true;
1202 this.drawingMode
= 'source-over';
1206 this.drawingMode
= 'source-over';
1210 this.blendModesEnabled
= true;
1211 this.unsetAlpha
= this.constantAlpha
;
1212 this.drawingMode
= 'source-over';
1216 this.drawingMode
= 'lighter';
1220 this.drawingMode
= 'darken';
1224 this.drawingMode
= 'ligthen';
1228 this.drawingMode
= 'difference';
1233 this.drawingMode
= 'lighter';
1238 this.drawingMode = 'destination-out';
1242 this.drawingMode = 'difference';
1247 console
.warn('drawing mode not implemented: '
1249 this.drawingMode
= 'source-over';
1253 this.invalidated
= true;
1256 case RP_CONSTRAIN_CLIPPING_REGION
:
1257 var rectCount
= remoteMessage
.dataView
.readUint32();
1258 this.clipRects
= new Array(rectCount
);
1259 for (var i
= 0; i
< rectCount
; i
++)
1260 this.clipRects
[i
] = new RemoteRect(remoteMessage
);
1262 this.invalidated
= true;
1265 case RP_INVERT_RECT
:
1266 this.applyContext();
1268 var rect
= new RemoteRect(remoteMessage
);
1271 context
.globalCompositeOperation
= 'difference';
1272 context
.fillStyle
= 'white';
1273 this.prepareForRect();
1275 rect
.apply(context
.fillRect
.bind(context
));
1280 case RP_DRAW_BITMAP
:
1281 this.applyContext();
1283 var bitmapRect
= new RemoteRect(remoteMessage
);
1284 var viewRect
= new RemoteRect(remoteMessage
);
1285 var options
= remoteMessage
.dataView
.readUint32();
1286 // TODO: Implement options.
1289 console
.warn('bitmap options not supported: ' + options
);
1291 var bitmap
= new RemoteBitmap(remoteMessage
, this.unsetAlpha
);
1292 context
.drawImage(bitmap
.canvas
, bitmapRect
.left
, bitmapRect
.top
,
1293 bitmapRect
.width(), bitmapRect
.height(), viewRect
.left
,
1294 viewRect
.top
, viewRect
.width(), viewRect
.height());
1297 case RP_DRAW_BITMAP_RECTS
:
1298 this.applyContext();
1300 var options
= remoteMessage
.dataView
.readUint32();
1301 // TODO: Implement options.
1302 var colorSpace
= remoteMessage
.dataView
.readUint32();
1303 var flags
= remoteMessage
.dataView
.readUint32();
1306 console
.warn('bitmap options not supported: ' + options
);
1308 var rectCount
= remoteMessage
.dataView
.readUint32();
1309 for (var i
= 0; i
< rectCount
; i
++) {
1310 var rect
= new RemoteRect(remoteMessage
);
1311 var bitmap
= new RemoteBitmap(remoteMessage
, this.unsetAlpha
,
1314 context
.drawImage(bitmap
.canvas
, 0, 0, bitmap
.width
,
1315 bitmap
.height
, rect
.left
, rect
.top
, rect
.width(),
1320 case RP_DRAW_STRING
:
1321 this.applyContext();
1323 var where
= new RemotePoint(remoteMessage
);
1324 var length
= remoteMessage
.dataView
.readUint32();
1325 var string
= remoteMessage
.dataView
.readString(length
);
1328 context
.fillStyle
= this.highColor
.toColor(this.unsetAlpha
);
1329 context
.fillText(string
, where
.x
, where
.y
);
1331 var textMetric
= context
.measureText(string
);
1332 where
.x
+= textMetric
.width
;
1336 reply
.start(RP_DRAW_STRING_RESULT
);
1337 reply
.dataView
.writeInt32(this.token
);
1338 where
.writeTo(reply
);
1342 case RP_DRAW_STRING_WITH_OFFSETS
:
1343 this.applyContext();
1345 var length
= remoteMessage
.dataView
.readUint32();
1346 var string
= remoteMessage
.dataView
.readString(length
);
1349 context
.fillStyle
= this.highColor
.toColor(this.unsetAlpha
);
1352 for (var i
= 0; i
< string
.length
; i
++) {
1353 where
= new RemotePoint(remoteMessage
);
1354 context
.fillText(string
[i
], where
.x
, where
.y
);
1357 var textMetric
= context
.measureText(string
[string
.length
- 1]);
1358 where
.x
+= textMetric
.width
;
1362 reply
.start(RP_DRAW_STRING_RESULT
);
1363 reply
.dataView
.writeInt32(this.token
);
1364 where
.writeTo(reply
);
1368 case RP_STRING_WIDTH
:
1369 this.applyContext();
1371 var length
= remoteMessage
.dataView
.readUint32();
1372 var string
= remoteMessage
.dataView
.readString(length
);
1373 var textMetric
= context
.measureText(string
);
1375 reply
.start(RP_STRING_WIDTH_RESULT
);
1376 reply
.dataView
.writeInt32(this.token
);
1377 where
.writeFloat32(textMetric
.width
);
1383 this.applyContext();
1385 var rect
= new RemoteRect(remoteMessage
);
1387 = remoteMessage
.dataView
.readFloat32() * Math
.PI
/ 180;
1388 var invertStart
= Math
.PI
* 2 - startAngle
;
1389 startAngle
+= Math
.PI
/ 2;
1391 var span
= remoteMessage
.dataView
.readFloat32() * Math
.PI
/ 180;
1392 var centerX
= Math
.round(rect
.centerX());
1393 var centerY
= Math
.round(rect
.centerY());
1394 var radius
= rect
.width() / 2;
1396 = remoteMessage
.code() != RP_STROKE_ARC
? Math
.PI
/ 2 : span
;
1398 var arcStep = function(max
) {
1399 max
= Math
.min(max
, span
);
1401 context
.beginPath();
1402 context
.arc(centerX
, centerY
, radius
, invertStart
,
1403 invertStart
- max
, true);
1405 switch (remoteMessage
.code()) {
1411 context
.moveTo(centerX
, centerY
);
1412 var endAngle
= startAngle
+ max
;
1414 centerX
+ radius
* Math
.sin(startAngle
),
1415 centerY
+ radius
* Math
.cos(startAngle
));
1417 centerX
+ radius
* Math
.sin(endAngle
),
1418 centerY
+ radius
* Math
.cos(endAngle
));
1433 case RP_STROKE_RECT
:
1434 case RP_STROKE_ELLIPSE
:
1436 case RP_FILL_ELLIPSE
:
1437 this.applyContext();
1440 this.prepareForRect();
1442 var rect
= new RemoteRect(remoteMessage
);
1444 switch (remoteMessage
.code()) {
1445 case RP_STROKE_RECT
:
1446 rect
.apply(context
.strokeRect
.bind(context
));
1448 case RP_STROKE_ELLIPSE
:
1449 rect
.applyAsEllipse(context
, context
.stroke
);
1452 rect
.apply(context
.fillRect
.bind(context
));
1454 case RP_FILL_ELLIPSE
:
1455 rect
.applyAsEllipse(context
, context
.fill
);
1462 case RP_STROKE_ROUND_RECT
:
1463 case RP_FILL_ROUND_RECT
:
1464 case RP_FILL_ROUND_RECT_GRADIENT
:
1465 this.applyContext();
1468 this.prepareForRect();
1470 var rect
= new RemoteRect(remoteMessage
);
1471 var xRadius
= remoteMessage
.dataView
.readFloat32();
1472 var yRadius
= remoteMessage
.dataView
.readFloat32();
1474 if (remoteMessage
.code() == RP_FILL_ROUND_RECT_GRADIENT
) {
1476 var gradient
= new RemoteGradient(remoteMessage
, context
,
1478 context
.fillStyle
= gradient
.gradient
;
1481 console
.warn('round rects not implemented, falling back to rect');
1482 if (remoteMessage
.code() == RP_STROKE_ROUND_RECT
)
1483 rect
.apply(context
.strokeRect
.bind(context
));
1485 rect
.apply(context
.fillRect
.bind(context
));
1487 if (remoteMessage
.code() == RP_FILL_ROUND_RECT_GRADIENT
)
1493 case RP_STROKE_LINE
:
1494 this.applyContext();
1496 var from = new RemotePoint(remoteMessage
);
1497 var to
= new RemotePoint(remoteMessage
);
1499 context
.beginPath();
1500 context
.moveTo(from.x
, from.y
);
1501 context
.lineTo(to
.x
, to
.y
);
1505 case RP_STROKE_LINE_ARRAY
:
1506 this.applyContext();
1509 context
.lineCap
= 'square';
1511 var numLines
= remoteMessage
.dataView
.readUint32();
1512 for (var i
= 0; i
< numLines
; i
++) {
1513 var from = new RemotePoint(remoteMessage
);
1514 var to
= new RemotePoint(remoteMessage
);
1515 context
.strokeStyle
= remoteMessage
.readColor(this.unsetAlpha
);
1516 context
.beginPath();
1517 context
.moveTo(from.x
+ 0.5, from.y
+ 0.5);
1518 context
.lineTo(to
.x
+ 0.5, to
.y
+ 0.5);
1525 case RP_STROKE_POINT_COLOR
:
1526 this.applyContext();
1528 var point
= new RemotePoint(remoteMessage
);
1531 context
.fillStyle
= remoteMessage
.readColor(this.unsetAlpha
);
1533 context
.fillRect(point
.x
, point
.y
, 1, 1);
1537 case RP_STROKE_LINE_1PX_COLOR
:
1538 this.applyContext();
1540 var from = new RemotePoint(remoteMessage
);
1541 var to
= new RemotePoint(remoteMessage
);
1544 context
.strokeStyle
= remoteMessage
.readColor(this.unsetAlpha
);
1545 context
.lineWidth
= 1;
1546 context
.lineCap
= 'square';
1548 context
.beginPath();
1549 context
.moveTo(from.x
+ 0.5, from.y
+ 0.5);
1550 context
.lineTo(to
.x
+ 0.5, to
.y
+ 0.5);
1556 case RP_STROKE_RECT_1PX_COLOR
:
1557 this.applyContext();
1559 var rect
= new RemoteRect(remoteMessage
);
1562 this.prepareForRect();
1564 context
.strokeStyle
= remoteMessage
.readColor(this.unsetAlpha
);
1565 context
.lineWidth
= 1;
1567 rect
.apply(context
.strokeRect
.bind(context
));
1572 case RP_STROKE_SHAPE
:
1574 case RP_FILL_SHAPE_GRADIENT
:
1575 this.applyContext();
1577 var shape
= new RemoteShape(remoteMessage
);
1578 var offset
= new RemotePoint(remoteMessage
);
1579 var scale
= remoteMessage
.dataView
.readFloat32();
1582 if (remoteMessage
.code() == RP_FILL_SHAPE_GRADIENT
) {
1583 var gradient
= new RemoteGradient(remoteMessage
, context
,
1585 context
.fillStyle
= gradient
.gradient
;
1588 context
.translate(offset
.x
+ 0.5, offset
.y
+ 0.5);
1589 context
.scale(scale
, scale
);
1591 context
.beginPath();
1593 shape
.play(context
);
1595 if (remoteMessage
.code() == RP_STROKE_SHAPE
)
1603 case RP_STROKE_TRIANGLE
:
1604 case RP_FILL_TRIANGLE
:
1605 case RP_FILL_TRIANGLE_GRADIENT
:
1606 this.applyContext();
1608 if (remoteMessage
.code() == RP_FILL_TRIANGLE_GRADIENT
)
1611 context
.beginPath();
1612 var point
= new RemotePoint(remoteMessage
);
1613 context
.moveTo(point
.x
+ 0.5, point
.y
+ 0.5);
1615 for (var i
= 0; i
< 2; i
++) {
1616 point
= new RemotePoint(remoteMessage
);
1617 context
.lineTo(point
.x
+ 0.5, point
.y
+ 0.5);
1620 if (remoteMessage
.code() == RP_FILL_TRIANGLE_GRADIENT
) {
1621 var unusedBounds
= new RemoteRect(remoteMessage
);
1622 var gradient
= new RemoteGradient(remoteMessage
, context
,
1624 context
.fillStyle
= gradient
.gradient
;
1627 switch (remoteMessage
.code()) {
1628 case RP_STROKE_TRIANGLE
:
1629 context
.closePath();
1633 case RP_FILL_TRIANGLE
:
1637 case RP_FILL_TRIANGLE_GRADIENT
:
1645 case RP_FILL_RECT_COLOR
:
1646 this.applyContext();
1648 var rect
= new RemoteRect(remoteMessage
);
1651 this.prepareForRect();
1652 context
.fillStyle
= remoteMessage
.readColor(this.unsetAlpha
);
1654 rect
.apply(context
.fillRect
.bind(context
));
1659 case RP_FILL_RECT_GRADIENT
:
1660 case RP_FILL_ELLIPSE_GRADIENT
:
1661 this.applyContext();
1663 var rect
= new RemoteRect(remoteMessage
);
1666 this.prepareForRect();
1668 var gradient
= new RemoteGradient(remoteMessage
, context
,
1670 context
.fillStyle
= gradient
.gradient
;
1672 if (remoteMessage
.code() == RP_FILL_RECT_GRADIENT
)
1673 rect
.apply(context
.fillRect
.bind(context
));
1675 rect
.applyAsEllipse(context
, context
.fill
);
1680 case RP_FILL_REGION
:
1681 case RP_FILL_REGION_GRADIENT
:
1682 this.applyContext();
1684 var rectCount
= remoteMessage
.dataView
.readUint32();
1685 var rects
= new Array(rectCount
);
1686 for (var i
= 0; i
< rectCount
; i
++)
1687 rects
[i
] = new RemoteRect(remoteMessage
);
1689 if (remoteMessage
.code() == RP_FILL_REGION_GRADIENT
) {
1691 var gradient
= new RemoteGradient(remoteMessage
, context
,
1693 context
.fillStyle
= gradient
.gradient
;
1696 for (var i
= 0; i
< rectCount
; i
++)
1697 rects
[i
].apply(context
.fillRect
.bind(context
));
1699 if (remoteMessage
.code() == RP_FILL_REGION_GRADIENT
)
1704 case RP_READ_BITMAP
:
1705 var bounds
= new RemoteRect(remoteMessage
);
1706 var drawCursor
= remoteMessage
.dataView
.readUint8();
1707 // TODO: Support the drawCursor flag.
1710 console
.warn('draw cursor in read bitmap not supported');
1712 var width
= bounds
.integerWidth() + 1;
1713 var height
= bounds
.integerHeight() + 1;
1714 var bytesPerPixel
= 3;
1715 var bytesPerRow
= (width
* bytesPerPixel
+ 3) & ~7;
1716 var padding
= bytesPerRow
- width
* bytesPerPixel
;
1717 var bitsLength
= height
* bytesPerRow
;
1719 reply
.ensureBufferSize(bitsLength
+ 1024);
1721 reply
.start(RP_READ_BITMAP_RESULT
);
1722 reply
.dataView
.writeInt32(this.token
);
1724 reply
.dataView
.writeInt32(width
);
1725 reply
.dataView
.writeInt32(height
);
1726 reply
.dataView
.writeInt32(bytesPerRow
);
1727 reply
.dataView
.writeUint32(B_RGB24
);
1728 reply
.dataView
.writeUint32(0); // Flags
1729 reply
.dataView
.writeUint32(bitsLength
);
1733 = context
.getImageData(bounds
.left
, bounds
.top
, width
, height
);
1734 for (var y
= 0; y
< height
; y
++) {
1735 for (var x
= 0; x
< width
; x
++, position
+= 4) {
1736 reply
.dataView
.writeUint8(imageData
.data
[position
+ 2]);
1737 reply
.dataView
.writeUint8(imageData
.data
[position
+ 1]);
1738 reply
.dataView
.writeUint8(imageData
.data
[position
+ 0]);
1741 reply
.dataView
.pad(padding
);
1748 console
.warn('unhandled message: code: ' + remoteMessage
.code()
1749 + '; size: ' + remoteMessage
.size());
1755 function RemoteDesktopSession(targetElement
, width
, height
, targetAddress
,
1758 this.websocket
= new WebSocket(targetAddress
, 'binary');
1759 this.websocket
.binaryType
= 'arraybuffer';
1760 this.websocket
.onopen
= this.onOpen
.bind(this);
1761 this.websocket
.onmessage
= this.onMessage
.bind(this);
1762 this.websocket
.onerror
= this.onError
.bind(this);
1763 this.websocket
.onclose
= this.onClose
.bind(this);
1765 this.disconnectCallback
= disconnectCallback
;
1767 this.sendMessage
= new RemoteMessage(this.websocket
);
1768 this.sendMessage
.allocate(1024);
1770 this.receiveMessage
= new RemoteMessage();
1772 this.container
= document
.createElement('div');
1773 this.container
.className
= 'session';
1774 this.container
.style
.position
= 'relative';
1775 targetElement
.appendChild(this.container
);
1777 this.canvas
= document
.createElement('canvas');
1778 this.canvas
.className
= 'session';
1779 this.canvas
.width
= width
;
1780 this.canvas
.height
= height
;
1781 this.container
.appendChild(this.canvas
);
1783 this.canvas
.tabIndex
= 0;
1784 this.canvas
.focus();
1786 this.context
= this.canvas
.getContext('2d', { alpha
: false });
1787 this.context
.imageSmoothingEnabled
= false;
1789 this.cursorVisible
= true;
1790 this.cursorPosition
= { x
: 0, y
: 0 };
1791 this.cursorHotspot
= { x
: 0, y
: 0 };
1793 this.states
= new Object();
1796 this.canvas
.onmousemove
= this.onMouseMove
.bind(this);
1797 this.canvas
.onmousedown
= this.onMouseDown
.bind(this);
1798 this.canvas
.onmouseup
= this.onMouseUp
.bind(this);
1799 this.canvas
.onwheel
= this.onWheel
.bind(this);
1801 this.canvas
.onkeydown
= this.onKeyDownUp
.bind(this);
1802 this.canvas
.onkeyup
= this.onKeyDownUp
.bind(this);
1803 this.canvas
.onkeypress
= this.onKeyPress
.bind(this);
1805 this.canvas
.oncontextmenu = function(event
) {
1806 event
.preventDefault();
1809 this.canvas
.onblur = function(event
) {
1810 event
.target
.focus();
1815 RemoteDesktopSession
.prototype.onOpen = function(open
)
1817 console
.log('open:', open
);
1822 RemoteDesktopSession
.prototype.onMessage = function(message
)
1824 var data
= message
.data
;
1825 if (this.messageRemainder
) {
1826 var combined
= new Uint8Array(this.messageRemainder
.byteLength
1828 combined
.set(new Uint8Array(this.messageRemainder
), 0);
1829 combined
.set(new Uint8Array(data
), this.messageRemainder
.byteLength
);
1832 this.messageRemainder
= null;
1834 data
= new Uint8Array(data
);
1839 if (!this.receiveMessage
.attach(data
, byteOffset
))
1841 } catch (exception
) {
1842 // Discard everything and hope for the best.
1843 console
.error('stream invalid, discarding everything', exception
,
1844 this.receiveMessage
, data
, byteOffset
);
1849 this.messageReceived(this.receiveMessage
, this.sendMessage
);
1850 } catch (exception
) {
1851 console
.error('exception during message processing:', exception
);
1854 byteOffset
+= this.receiveMessage
.size();
1857 if (data
.byteLength
> byteOffset
)
1858 this.messageRemainder
= data
.slice(byteOffset
);
1862 RemoteDesktopSession
.prototype.messageReceived = function(remoteMessage
, reply
)
1864 switch (remoteMessage
.code()) {
1865 case RP_INIT_CONNECTION
:
1866 console
.log('init connection reply');
1867 this.sendMessage
.start(RP_UPDATE_DISPLAY_MODE
);
1868 this.sendMessage
.dataView
.writeUint32(this.canvas
.width
);
1869 this.sendMessage
.dataView
.writeUint32(this.canvas
.height
);
1870 this.sendMessage
.flush();
1872 this.sendMessage
.start(RP_GET_SYSTEM_PALETTE
);
1873 this.sendMessage
.flush();
1876 case RP_GET_SYSTEM_PALETTE_RESULT
:
1877 var count
= remoteMessage
.dataView
.readUint32();
1878 gSystemPalette
= new Uint32Array(count
);
1880 var color
= new RemoteColor();
1881 for (var i
= 0; i
< gSystemPalette
.length
; i
++)
1882 gSystemPalette
[i
] = color
.readFrom(remoteMessage
).toUint32();
1886 case RP_CREATE_STATE
:
1887 var token
= remoteMessage
.dataView
.readInt32();
1888 console
.log('create state: ' + token
);
1890 if (this.states
.hasOwnProperty(token
))
1891 console
.error('create state for existing token: ' + token
);
1893 this.states
[token
] = new RemoteState(this, token
);
1896 case RP_DELETE_STATE
:
1897 var token
= remoteMessage
.dataView
.readInt32();
1898 console
.log('delete state: ' + token
);
1900 if (!this.states
.hasOwnProperty(token
)) {
1901 console
.error('delete state for unknown token: ' + token
);
1905 delete this.states
[token
];
1908 case RP_INVALIDATE_RECT
:
1909 case RP_INVALIDATE_REGION
:
1913 this.cursorHotspot
= new RemotePoint(remoteMessage
);
1914 var bitmap
= new RemoteBitmap(remoteMessage
);
1916 bitmap
.canvas
.style
.position
= 'absolute';
1917 if (this.cursorCanvas
)
1918 this.cursorCanvas
.remove();
1920 this.cursorCanvas
= bitmap
.canvas
;
1921 this.cursorCanvas
.style
.pointerEvents
= 'none';
1922 this.container
.appendChild(this.cursorCanvas
);
1923 this.container
.style
.cursor
= 'none';
1924 this.updateCursor();
1927 case RP_MOVE_CURSOR_TO
:
1928 this.cursorPosition
.x
= remoteMessage
.dataView
.readFloat32();
1929 this.cursorPosition
.y
= remoteMessage
.dataView
.readFloat32();
1930 this.updateCursor();
1933 case RP_SET_CURSOR_VISIBLE
:
1934 this.cursorVisible
= remoteMessage
.dataView
.readUint8();
1935 if (this.cursorCanvas
) {
1936 this.cursorCanvas
.style
.visibility
1937 = this.cursorVisible
? 'visible' : 'hidden';
1941 case RP_COPY_RECT_NO_CLIPPING
:
1942 var xOffset
= remoteMessage
.dataView
.readInt32();
1943 var yOffset
= remoteMessage
.dataView
.readInt32();
1944 var rect
= new RemoteRect(remoteMessage
);
1946 var imageData
= this.context
.getImageData(rect
.left
, rect
.top
,
1947 rect
.width(), rect
.height());
1948 this.context
.putImageData(imageData
, rect
.left
+ xOffset
,
1949 rect
.top
+ yOffset
);
1952 case RP_FILL_REGION_COLOR_NO_CLIPPING
:
1953 this.removeClipping();
1954 this.context
.currentToken
= -1;
1955 this.context
.resetTransform();
1956 this.context
.globalCompositeOperation
= 'source-over';
1958 var rectCount
= remoteMessage
.dataView
.readUint32();
1959 var rects
= new Array(rectCount
);
1960 for (var i
= 0; i
< rectCount
; i
++)
1961 rects
[i
] = new RemoteRect(remoteMessage
);
1963 this.context
.fillStyle
= remoteMessage
.readColor();
1965 for (var i
= 0; i
< rectCount
; i
++)
1966 rects
[i
].apply(this.context
.fillRect
.bind(this.context
));
1971 var token
= remoteMessage
.dataView
.readInt32();
1972 if (!this.states
.hasOwnProperty(token
)) {
1973 console
.warn('no state for token: ' + token
);
1974 this.states
[token
] = new RemoteState(this, token
);
1977 this.states
[token
].messageReceived(remoteMessage
, reply
);
1983 RemoteDesktopSession
.prototype.onError = function(error
)
1985 console
.log('websocket error:', error
);
1986 this.onDisconnect(error
);
1990 RemoteDesktopSession
.prototype.onClose = function(close
)
1992 console
.log('websocket close:', close
);
1993 this.onDisconnect(close
);
1997 RemoteDesktopSession
.prototype.onDisconnect = function(reason
)
1999 this.container
.remove();
2000 if (this.disconnectCallback
)
2001 this.disconnectCallback(reason
);
2005 RemoteDesktopSession
.prototype.applyClipping = function(clipRects
)
2007 this.removeClipping();
2009 if (!clipRects
|| clipRects
.length
== 0)
2012 this.context
.save();
2013 this.context
.beginPath();
2015 this.context
.save();
2016 this.context
.lineJoin
= 'miter';
2017 this.context
.miterLimit
= 10;
2019 for (var i
= 0; i
< clipRects
.length
; i
++)
2020 clipRects
[i
].apply(this.context
.rect
.bind(this.context
));
2022 this.context
.restore();
2024 this.context
.clip();
2025 this.clippingApplied
= true;
2029 RemoteDesktopSession
.prototype.removeClipping = function()
2031 if (!this.clippingApplied
)
2034 this.context
.restore();
2038 RemoteDesktopSession
.prototype.init = function()
2040 this.sendMessage
.start(RP_INIT_CONNECTION
);
2041 this.sendMessage
.flush();
2045 RemoteDesktopSession
.prototype.updateCursor = function()
2047 if (!this.cursorVisible
|| !this.cursorCanvas
)
2050 this.cursorCanvas
.style
.left
2051 = (this.cursorPosition
.x
- this.cursorHotspot
.x
) + 'px';
2052 this.cursorCanvas
.style
.top
2053 = (this.cursorPosition
.y
- this.cursorHotspot
.y
) + 'px';
2057 RemoteDesktopSession
.prototype.onMouseMove = function(event
)
2059 this.sendMessage
.start(RP_MOUSE_MOVED
);
2060 this.sendMessage
.dataView
.writeFloat32(event
.offsetX
);
2061 this.sendMessage
.dataView
.writeFloat32(event
.offsetY
);
2062 this.sendMessage
.flush();
2063 event
.preventDefault();
2067 RemoteDesktopSession
.prototype.onMouseDown = function(event
)
2069 this.canvas
.focus();
2070 this.sendMessage
.start(RP_MOUSE_DOWN
);
2071 this.sendMessage
.dataView
.writeFloat32(event
.offsetX
);
2072 this.sendMessage
.dataView
.writeFloat32(event
.offsetY
);
2073 this.sendMessage
.dataView
.writeUint32(event
.buttons
);
2074 this.sendMessage
.dataView
.writeUint32(event
.detail
);
2075 this.sendMessage
.flush();
2076 event
.preventDefault();
2080 RemoteDesktopSession
.prototype.onMouseUp = function(event
)
2082 this.sendMessage
.start(RP_MOUSE_UP
);
2083 this.sendMessage
.dataView
.writeFloat32(event
.offsetX
);
2084 this.sendMessage
.dataView
.writeFloat32(event
.offsetY
);
2085 this.sendMessage
.dataView
.writeUint32(event
.buttons
);
2086 this.sendMessage
.flush();
2087 event
.preventDefault();
2091 RemoteDesktopSession
.prototype.onKeyDownUp = function(event
)
2093 var keyDown
= event
.type
=== 'keydown';
2094 var lockModifier
= false;
2095 var modifiersChanged
= 0;
2096 switch (event
.code
) {
2098 modifiersChanged
|= B_LEFT_SHIFT_KEY
;
2099 if (event
.shiftKey
== keyDown
)
2100 modifiersChanged
|= B_SHIFT_KEY
;
2104 modifiersChanged
|= B_RIGHT_SHIFT_KEY
;
2105 if (event
.shiftKey
== keyDown
)
2106 modifiersChanged
|= B_SHIFT_KEY
;
2110 modifiersChanged
|= B_LEFT_CONTROL_KEY
;
2111 if (event
.ctrlKey
== keyDown
)
2112 modifiersChanged
|= B_CONTROL_KEY
;
2115 case 'ControlRight':
2116 modifiersChanged
|= B_RIGHT_CONTROL_KEY
;
2117 if (event
.ctrlKey
== keyDown
)
2118 modifiersChanged
|= B_CONTROL_KEY
;
2122 modifiersChanged
|= B_LEFT_COMMAND_KEY
;
2123 if (event
.altKey
== keyDown
)
2124 modifiersChanged
|= B_COMMAND_KEY
;
2128 modifiersChanged
|= B_RIGHT_COMMAND_KEY
;
2129 if (event
.altKey
== keyDown
)
2130 modifiersChanged
|= B_COMMAND_KEY
;
2134 modifiersChanged
|= B_MENU_KEY
;
2138 modifiersChanged
|= B_CAPS_LOCK
;
2139 lockModifier
= true;
2143 modifiersChanged
|= B_SCROLL_LOCK
;
2144 lockModifier
= true;
2148 modifiersChanged
|= B_NUM_LOCK
;
2149 lockModifier
= true;
2153 if (modifiersChanged
!= 0) {
2155 if (((this.modifiers
& modifiersChanged
) == 0) == keyDown
)
2156 this.modifiers
^= modifiersChanged
;
2159 this.modifiers
|= modifiersChanged
;
2161 this.modifiers
&= ~modifiersChanged
;
2164 this.sendMessage
.start(RP_MODIFIERS_CHANGED
);
2165 this.sendMessage
.dataView
.writeUint32(this.modifiers
);
2166 this.sendMessage
.flush();
2167 event
.preventDefault();
2171 this.sendMessage
.start(keyDown
? RP_KEY_DOWN
: RP_KEY_UP
);
2172 if (event
.key
.length
== 1)
2173 this.sendMessage
.dataView
.writeString(event
.key
);
2175 this.sendMessage
.dataView
.writeUint32(1);
2176 this.sendMessage
.dataView
.writeUint8(event
.keyCode
);
2179 if (event
.keyCode
) {
2180 this.sendMessage
.dataView
.writeUint32(0);
2181 this.sendMessage
.dataView
.writeUint32(event
.keyCode
);
2184 this.sendMessage
.flush();
2185 event
.preventDefault();
2189 RemoteDesktopSession
.prototype.onKeyPress = function(event
)
2191 this.sendMessage
.start(RP_KEY_DOWN
);
2192 this.sendMessage
.dataView
.writeUint32(1);
2193 this.sendMessage
.dataView
.writeUint8(event
.which
);
2194 this.sendMessage
.flush();
2195 this.sendMessage
.start(RP_KEY_UP
);
2196 this.sendMessage
.dataView
.writeUint32(1);
2197 this.sendMessage
.dataView
.writeUint8(event
.which
);
2198 this.sendMessage
.flush();
2199 event
.preventDefault();
2203 RemoteDesktopSession
.prototype.onWheel = function(event
)
2205 this.sendMessage
.start(RP_MOUSE_WHEEL_CHANGED
);
2206 this.sendMessage
.dataView
.writeFloat32(event
.deltaX
);
2207 this.sendMessage
.dataView
.writeFloat32(event
.deltaY
);
2208 this.sendMessage
.flush();
2209 event
.preventDefault();
2215 var targetAddressInput
= document
.querySelector('#targetAddress');
2216 var widthInput
= document
.querySelector('#width');
2217 var heightInput
= document
.querySelector('#height');
2219 if (localStorage
.targetAddress
)
2220 targetAddressInput
.value
= localStorage
.targetAddress
;
2221 if (localStorage
.width
)
2222 widthInput
.value
= localStorage
.width
;
2223 if (localStorage
.height
)
2224 heightInput
.value
= localStorage
.height
;
2226 var onDisconnect = function(reason
) {
2227 document
.body
.classList
.remove('connect');
2228 gSession
= undefined;
2231 document
.querySelector('#connectButton').onclick = function() {
2232 document
.body
.classList
.add('connect');
2234 localStorage
.width
= widthInput
.value
;
2235 localStorage
.height
= heightInput
.value
;
2236 localStorage
.targetAddress
= targetAddressInput
.value
;
2238 gSession
= new RemoteDesktopSession(document
.body
, widthInput
.value
,
2239 heightInput
.value
, targetAddressInput
.value
, onDisconnect
);