@breakside/jskit 2026.11.0 → 2026.27.0

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.
Files changed (87) hide show
  1. package/Frameworks/DOM.jsframework/Info.json +2 -2
  2. package/Frameworks/DOM.jsframework/io.breakside.JSKit.DOM-bundle.js +2 -2
  3. package/Frameworks/FontKit.jsframework/Info.json +2 -2
  4. package/Frameworks/FontKit.jsframework/JS/FNTOpenTypeFont.js +331 -22
  5. package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
  6. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  7. package/Frameworks/Foundation.jsframework/JS/JSAttributedString.js +56 -1
  8. package/Frameworks/Foundation.jsframework/JS/JSContext.js +44 -17
  9. package/Frameworks/Foundation.jsframework/JS/JSCubicBezier.js +54 -1
  10. package/Frameworks/Foundation.jsframework/JS/JSFont.js +8 -9
  11. package/Frameworks/Foundation.jsframework/JS/JSGradient.js +28 -2
  12. package/Frameworks/Foundation.jsframework/JS/JSImage+HTML.js +27 -1
  13. package/Frameworks/Foundation.jsframework/JS/JSNumberFormatter.js +23 -1
  14. package/Frameworks/Foundation.jsframework/JS/JSPath.js +194 -6
  15. package/Frameworks/Foundation.jsframework/JS/JSPoint.js +56 -0
  16. package/Frameworks/Foundation.jsframework/JS/JSTextRun.js +21 -4
  17. package/Frameworks/Foundation.jsframework/JS/JSTextStorage.js +5 -0
  18. package/Frameworks/Foundation.jsframework/JS/JSTextTypesetter.js +6 -1
  19. package/Frameworks/Foundation.jsframework/JS/JSXMLParser.js +1 -1
  20. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  21. package/Frameworks/Foundation.jsframework/sources.json +1 -0
  22. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  23. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  24. package/Info.json +2 -2
  25. package/Node/io.breakside.jskit-bundle.js +2 -2
  26. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  27. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  28. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  29. package/Root/Frameworks/CSSOM/CSSTokenizer.js +633 -470
  30. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  31. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  32. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  33. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  34. package/Root/Frameworks/DOM/Info.yaml +1 -1
  35. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  36. package/Root/Frameworks/FontKit/FNTOpenTypeFont.js +331 -22
  37. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  38. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  39. package/Root/Frameworks/Foundation/JSAttributedString.js +56 -1
  40. package/Root/Frameworks/Foundation/JSContext.js +44 -17
  41. package/Root/Frameworks/Foundation/JSCubicBezier.js +54 -1
  42. package/Root/Frameworks/Foundation/JSFont.js +8 -9
  43. package/Root/Frameworks/Foundation/JSGradient.js +28 -2
  44. package/Root/Frameworks/Foundation/JSImage+HTML.js +27 -1
  45. package/Root/Frameworks/Foundation/JSNumberFormatter.js +23 -1
  46. package/Root/Frameworks/Foundation/JSPath.js +194 -6
  47. package/Root/Frameworks/Foundation/JSPoint.js +56 -0
  48. package/Root/Frameworks/Foundation/JSTextRun.js +21 -4
  49. package/Root/Frameworks/Foundation/JSTextStorage.js +5 -0
  50. package/Root/Frameworks/Foundation/JSTextTypesetter.js +6 -1
  51. package/Root/Frameworks/Foundation/JSXMLParser.js +1 -1
  52. package/Root/Frameworks/ImageKit/IKHTMLCanvasBitmapContext.js +187 -101
  53. package/Root/Frameworks/ImageKit/IKNodeBitmapContext.js +4 -4
  54. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  55. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  56. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  57. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  58. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  59. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  60. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  61. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  62. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  63. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  64. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  65. package/Root/Frameworks/TestKit/TKAssert.js +4 -4
  66. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  67. package/Root/Frameworks/UIKit/UIApplication.js +9 -1
  68. package/Root/Frameworks/UIKit/UICollectionView.js +4 -0
  69. package/Root/Frameworks/UIKit/UIColorPanel.js +46 -0
  70. package/Root/Frameworks/UIKit/UIColorWell.js +8 -0
  71. package/Root/Frameworks/UIKit/UIHTMLDisplayServer.js +21 -2
  72. package/Root/Frameworks/UIKit/UIHTMLDisplayServerCanvasContext.js +150 -69
  73. package/Root/Frameworks/UIKit/UIHTMLTextRun.js +5 -0
  74. package/Root/Frameworks/UIKit/UIHTMLTextTypesetter.js +18 -3
  75. package/Root/Frameworks/UIKit/UIListView.js +7 -5
  76. package/Root/Frameworks/UIKit/UIMenu.js +50 -32
  77. package/Root/Frameworks/UIKit/UIMenuItem.js +12 -0
  78. package/Root/Frameworks/UIKit/UIMenuView.js +27 -5
  79. package/Root/Frameworks/UIKit/UIPlatform.js +7 -0
  80. package/Root/Frameworks/UIKit/UIPopupButton.js +7 -1
  81. package/Root/Frameworks/UIKit/UIResponder.js +8 -0
  82. package/Root/Frameworks/UIKit/UITextEditor.js +31 -7
  83. package/Root/Frameworks/UIKit/UITextField.js +7 -0
  84. package/Root/Frameworks/UIKit/UITokenField.js +65 -0
  85. package/Root/Frameworks/UIKit/UIToolbar.js +1 -1
  86. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  87. package/package.json +1 -1
@@ -21,6 +21,8 @@
21
21
 
22
22
  JSProtocol("UIColorPanelDelegate", JSProtocol, {
23
23
 
24
+ colorPanelDidBeginEditing: function(panel, event){},
25
+ colorPanelDidEndEditing: function(panel, event){},
24
26
  colorPanelDidChangeColor: function(panel, color){},
25
27
  colorPanelDidClose: function(panel){}
26
28
 
@@ -239,8 +241,14 @@ JSClass("UIColorPanelViewController", UIViewController, {
239
241
  this.hexField.leftAccessoryVisibility = UITextField.AccessoryVisibility.always;
240
242
 
241
243
  this.saturationBrightnessControl.addAction(this.saturationBrightnessChanged, this);
244
+ this.saturationBrightnessControl.addAction(this.saturationBrightnessEditingBegan, this, UIControl.Event.editingDidBegin);
245
+ this.saturationBrightnessControl.addAction(this.saturationBrightnessEditingEnded, this, UIControl.Event.editingDidEnd);
242
246
  this.hueSlider.addAction(this.hueSliderChanged, this);
247
+ this.hueSlider.addAction(this.hueSliderEditingBegan, this, UIControl.Event.editingDidBegin);
248
+ this.hueSlider.addAction(this.hueSliderEditingEnded, this, UIControl.Event.editingDidEnd);
243
249
  this.alphaSlider.addAction(this.alphaSliderChanged, this);
250
+ this.alphaSlider.addAction(this.alphaSliderEditingBegan, this, UIControl.Event.editingDidBegin);
251
+ this.alphaSlider.addAction(this.alphaSliderEditingEnded, this, UIControl.Event.editingDidEnd);
244
252
  this.hexField.addAction(this.hexFieldChanged, this);
245
253
  this.component1Field.addAction(this.component1FieldChanged, this);
246
254
  this.component2Field.addAction(this.component2FieldChanged, this);
@@ -308,6 +316,18 @@ JSClass("UIColorPanelViewController", UIViewController, {
308
316
  this.notifyDelegateOfColor();
309
317
  },
310
318
 
319
+ saturationBrightnessEditingBegan: function(sender, event){
320
+ if (this.delegate && this.delegate.colorPanelDidBeginEditing){
321
+ this.delegate.colorPanelDidBeginEditing(this, event);
322
+ }
323
+ },
324
+
325
+ saturationBrightnessEditingEnded: function(sender, event){
326
+ if (this.delegate && this.delegate.colorPanelDidEndEditing){
327
+ this.delegate.colorPanelDidEndEditing(this, event);
328
+ }
329
+ },
330
+
311
331
  hueSliderChanged: function(slider){
312
332
  this._hsvComponents[0] = slider.value;
313
333
  this._recalculateColorFromHSV();
@@ -315,12 +335,36 @@ JSClass("UIColorPanelViewController", UIViewController, {
315
335
  this.notifyDelegateOfColor();
316
336
  },
317
337
 
338
+ hueSliderEditingBegan: function(sender, event){
339
+ if (this.delegate && this.delegate.colorPanelDidBeginEditing){
340
+ this.delegate.colorPanelDidBeginEditing(this, event);
341
+ }
342
+ },
343
+
344
+ hueSliderEditingEnded: function(sender, event){
345
+ if (this.delegate && this.delegate.colorPanelDidEndEditing){
346
+ this.delegate.colorPanelDidEndEditing(this, event);
347
+ }
348
+ },
349
+
318
350
  alphaSliderChanged: function(slider){
319
351
  this._color = this._color.colorWithAlpha(slider.value);
320
352
  this._alphaChanged(slider);
321
353
  this.notifyDelegateOfColor();
322
354
  },
323
355
 
356
+ alphaSliderEditingBegan: function(sender, event){
357
+ if (this.delegate && this.delegate.colorPanelDidBeginEditing){
358
+ this.delegate.colorPanelDidBeginEditing(this, event);
359
+ }
360
+ },
361
+
362
+ alphaSliderEditingEnded: function(sender, event){
363
+ if (this.delegate && this.delegate.colorPanelDidEndEditing){
364
+ this.delegate.colorPanelDidEndEditing(this, event);
365
+ }
366
+ },
367
+
324
368
  component1FieldChanged: function(textField){
325
369
  var value = textField.integerValue;
326
370
  if (isNaN(value)){
@@ -829,6 +873,7 @@ JSClass("UIColorPanelTwoAxisSlider", UIControl, {
829
873
  initialOffset: JSPoint.Zero,
830
874
  };
831
875
  this.active = true;
876
+ this.sendActionsForEvents(UIControl.Event.editingDidBegin, event);
832
877
  if (!isOnKnob){
833
878
  this.value = this.valueForPoint(location);
834
879
  this.didChangeValueForBinding('value');
@@ -852,6 +897,7 @@ JSClass("UIColorPanelTwoAxisSlider", UIControl, {
852
897
  _endSlide: function(event){
853
898
  if (this.active){
854
899
  this.active = false;
900
+ this.sendActionsForEvents(UIControl.Event.editingDidEnd, event);
855
901
  }
856
902
  this._drag = null;
857
903
  },
@@ -211,6 +211,14 @@ JSClass("UIColorWell", UIControl, {
211
211
  // ----------------------------------------------------------------------
212
212
  // MARK: - Color Panel
213
213
 
214
+ colorPanelDidBeginEditing: function(panel, event){
215
+ this.sendActionsForEvents(UIControl.Event.editingDidBegin, event);
216
+ },
217
+
218
+ colorPanelDidEndEditing: function(panel, event){
219
+ this.sendActionsForEvents(UIControl.Event.editingDidEnd, event);
220
+ },
221
+
214
222
  colorPanelDidChangeColor: function(panel, color){
215
223
  this._changeColor(color);
216
224
  },
@@ -419,12 +419,31 @@ JSClass("UIHTMLDisplayServer", UIDisplayServer, {
419
419
  }
420
420
  insertedLayersByParentContext[info.parentContext.objectID].push(info.layer);
421
421
  }
422
+ var parentContext;
423
+ var context;
424
+ var i, l;
422
425
  for (var contextId in insertedLayersByParentContext){
423
426
  insertedLayersByParentContext[contextId].sort(function(a, b){
424
427
  return a.sublayerIndex - b.sublayerIndex;
425
428
  });
426
- for (var i = 0, l = insertedLayersByParentContext[contextId].length; i < l; ++i){
427
- this._insertLayerIntoDOM(insertedLayersByParentContext[contextId][i], parentContextsById[contextId]);
429
+ parentContext = parentContextsById[contextId];
430
+ // If a layer's element is already in the parent element (meaning it
431
+ // is just changing order), first remove it from the parent element
432
+ // so sublayer index calculations will work correctly.
433
+ // NOTE: UIHTMLDisplayServerCanvasContext.insertSublayerContext()
434
+ // already checked for this, but its check only worked when moving a
435
+ // single layer. When adding moving and adding multiple layers at
436
+ // a time, we need first to remove all elements that moving before
437
+ // trying to insert or re-insert any of moved/added elements.
438
+ for (i = 0, l = insertedLayersByParentContext[contextId].length; i < l; ++i){
439
+ layer = insertedLayersByParentContext[contextId][i];
440
+ context = this.contextsByObjectID[layer.objectID];
441
+ if (context && context.element.parentNode === parentContext.element){
442
+ parentContext.element.removeChild(context.element);
443
+ }
444
+ }
445
+ for (i = 0, l = insertedLayersByParentContext[contextId].length; i < l; ++i){
446
+ this._insertLayerIntoDOM(insertedLayersByParentContext[contextId][i], parentContext);
428
447
  }
429
448
  }
430
449
  this._insertedLayers = {};
@@ -578,6 +578,18 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
578
578
  },
579
579
 
580
580
  fillMaskedRect: function(rect, maskImage){
581
+ if (maskImage._htmlImage !== undefined && maskImage._htmlImage !== null){
582
+ var maskCanvas = this.canvasContext.canvas.ownerDocument.createElement("canvas");
583
+ maskCanvas.width = rect.size.width;
584
+ maskCanvas.height = rect.size.height;
585
+ var maskContext = maskCanvas.getContext('2d');
586
+ maskContext.fillStyle = this.state.fillColor.cssString();
587
+ maskContext.fillRect(0, 0, rect.size.width, rect.size.height);
588
+ maskContext.globalCompositeOperation = 'destination-in';
589
+ maskContext.drawImage(maskImage._htmlImage, 0, 0, rect.size.width, rect.size.height);
590
+ this.canvasContext.drawImage(maskCanvas, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
591
+ return;
592
+ }
581
593
  var imageElement = this._dequeueReusableImageElement();
582
594
  this._positionImageElement(imageElement, maskImage, rect);
583
595
  var url = maskImage.htmlURLString();
@@ -602,6 +614,10 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
602
614
  _imageElementIndex: 0,
603
615
 
604
616
  drawImage: function(image, rect){
617
+ if (image._htmlImage !== null && image._htmlImage !== undefined){
618
+ this.canvasContext.drawImage(image._htmlImage, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
619
+ return;
620
+ }
605
621
  if (image !== null){
606
622
  var url = image.htmlURLString();
607
623
  // FIXME: proof of concept, but really shouldn't do async
@@ -687,57 +703,96 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
687
703
 
688
704
  drawLinearGradient: function(gradient, rect){
689
705
  this.canvasContext.save();
690
- var canvasGradient = this.canvasContext.createLinearGradient(
691
- rect.origin.x + gradient.start.x * rect.size.width,
692
- rect.origin.y + gradient.start.y * rect.size.height,
693
- rect.origin.x + gradient.end.x * rect.size.width,
694
- rect.origin.y + gradient.end.y * rect.size.height
695
- );
696
- var stop;
697
- for (var i = 0, l = gradient.stops.length; i < l; ++i){
698
- stop = gradient.stops[i];
699
- canvasGradient.addColorStop(stop.position, stop.color.cssString());
700
- }
706
+ var canvasGradient = this._linearGradientForCanvasContext(this.canvasContext, gradient, rect);
701
707
  this.canvasContext.fillStyle = canvasGradient;
702
708
  this.canvasContext.fillRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
703
709
  this.canvasContext.restore();
704
710
  },
705
711
 
706
712
  drawRadialGradient: function(gradient, rect){
707
- var r0 = 0;
708
- var start = JSPoint(
709
- rect.origin.x + gradient.start.x * rect.size.width,
710
- rect.origin.y + gradient.start.y * rect.size.height
711
- );
712
- var end = JSPoint(
713
- rect.origin.x + gradient.end.x * rect.size.width,
714
- rect.origin.y + gradient.end.y * rect.size.height
715
- );
716
- var r1 = start.distanceToPoint(end);
717
713
  this.canvasContext.save();
718
- var canvasGradient = this.canvasContext.createRadialGradient(
719
- start.x,
720
- start.y,
721
- r0,
722
- start.x,
723
- start.y,
724
- r1
725
- );
726
- var stop;
727
- for (var i = 0, l = gradient.stops.length; i < l; ++i){
728
- stop = gradient.stops[i];
729
- canvasGradient.addColorStop(stop.position, stop.color.cssString());
730
- }
714
+ var canvasGradient = this._radialGradientForCanvasContext(this.canvasContext, gradient, rect);
731
715
  this.canvasContext.fillStyle = canvasGradient;
732
716
  this.canvasContext.fillRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
733
717
  this.canvasContext.restore();
734
718
  },
735
719
 
736
- drawLinearGradientStroke: function(gradient, path){
720
+ drawMaskedLinearGradient: function(gradient, rect, maskImage){
721
+ if (maskImage._htmlImage !== undefined && maskImage._htmlImage !== null){
722
+ var maskCanvas = this.canvasContext.canvas.ownerDocument.createElement("canvas");
723
+ maskCanvas.width = rect.size.width;
724
+ maskCanvas.height = rect.size.height;
725
+ var maskContext = maskCanvas.getContext('2d');
726
+ var canvasGradient = this._linearGradientForCanvasContext(maskContext, gradient, JSRect(JSPoint.Zero, rect.size));
727
+ maskContext.fillStyle = canvasGradient;
728
+ maskContext.fillRect(0, 0, rect.size.width, rect.size.height);
729
+ maskContext.globalCompositeOperation = 'destination-in';
730
+ maskContext.drawImage(maskImage._htmlImage, 0, 0, rect.size.width, rect.size.height);
731
+ this.canvasContext.drawImage(maskCanvas, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
732
+ return;
733
+ }
734
+ var imageElement = this._dequeueReusableImageElement();
735
+ this._positionImageElement(imageElement, maskImage, rect);
736
+ var url = maskImage.htmlURLString();
737
+ var cssURL = "url('" + url + "')";
738
+ imageElement.style.maskImage = cssURL;
739
+ imageElement.style.maskSize = '100% 100%';
740
+ imageElement.style.webkitMaskImage = cssURL;
741
+ imageElement.style.webkitMaskSize = '100% 100%';
742
+ imageElement.style.backgroundImage = gradient.cssString(rect.size);
743
+ this._insertChildElement(imageElement);
744
+ },
745
+
746
+ drawMaskedRadialGradient: function(gradient, rect, maskImage){
747
+ if (maskImage._htmlImage !== undefined && maskImage._htmlImage !== null){
748
+ var maskCanvas = this.canvasContext.canvas.ownerDocument.createElement("canvas");
749
+ maskCanvas.width = rect.size.width;
750
+ maskCanvas.height = rect.size.height;
751
+ var maskContext = maskCanvas.getContext('2d');
752
+ var canvasGradient = this._radialGradientForCanvasContext(maskContext, gradient, JSRect(JSPoint.Zero, rect.size));
753
+ maskContext.fillStyle = canvasGradient;
754
+ maskContext.fillRect(0, 0, rect.size.width, rect.size.height);
755
+ maskContext.globalCompositeOperation = 'destination-in';
756
+ maskContext.drawImage(maskImage._htmlImage, 0, 0, rect.size.width, rect.size.height);
757
+ this.canvasContext.drawImage(maskCanvas, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
758
+ return;
759
+ }
760
+ var imageElement = this._dequeueReusableImageElement();
761
+ this._positionImageElement(imageElement, maskImage, rect);
762
+ var url = maskImage.htmlURLString();
763
+ var cssURL = "url('" + url + "')";
764
+ imageElement.style.maskImage = cssURL;
765
+ imageElement.style.maskSize = '100% 100%';
766
+ imageElement.style.webkitMaskImage = cssURL;
767
+ imageElement.style.webkitMaskSize = '100% 100%';
768
+ imageElement.style.backgroundImage = gradient.radialCSSString(rect.size);
769
+ this._insertChildElement(imageElement);
770
+ },
771
+
772
+ drawLinearGradientStroke: function(gradient, path, alternateRect){
773
+ var rect = path.boundingRect;
774
+ this.canvasContext.save();
775
+ this.addPath(path);
776
+ var canvasGradient = this._linearGradientForCanvasContext(this.canvasContext, gradient, alternateRect || rect);
777
+ this.canvasContext.strokeStyle = canvasGradient;
778
+ this.canvasContext.stroke();
779
+ this.canvasContext.restore();
780
+ this.beginPath();
781
+ },
782
+
783
+ drawRadialGradientStroke: function(gradient, path, alternateRect){
737
784
  var rect = path.boundingRect;
738
785
  this.canvasContext.save();
739
786
  this.addPath(path);
740
- var canvasGradient = this.canvasContext.createLinearGradient(
787
+ var canvasGradient = this._radialGradientForCanvasContext(this.canvasContext, gradient, alternateRect || rect);
788
+ this.canvasContext.strokeStyle = canvasGradient;
789
+ this.canvasContext.stroke();
790
+ this.canvasContext.restore();
791
+ this.beginPath();
792
+ },
793
+
794
+ _linearGradientForCanvasContext: function(canvasContext, gradient, rect){
795
+ var canvasGradient = canvasContext.createLinearGradient(
741
796
  rect.origin.x + gradient.start.x * rect.size.width,
742
797
  rect.origin.y + gradient.start.y * rect.size.height,
743
798
  rect.origin.x + gradient.end.x * rect.size.width,
@@ -748,14 +803,10 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
748
803
  stop = gradient.stops[i];
749
804
  canvasGradient.addColorStop(stop.position, stop.color.cssString());
750
805
  }
751
- this.canvasContext.strokeStyle = canvasGradient;
752
- this.canvasContext.stroke();
753
- this.canvasContext.restore();
754
- this.beginPath();
806
+ return canvasGradient;
755
807
  },
756
808
 
757
- drawRadialGradientStroke: function(gradient, path){
758
- var rect = path.boundingRect;
809
+ _radialGradientForCanvasContext: function(canvasContext, gradient, rect){
759
810
  var r0 = 0;
760
811
  var start = JSPoint(
761
812
  rect.origin.x + gradient.start.x * rect.size.width,
@@ -766,9 +817,7 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
766
817
  rect.origin.y + gradient.end.y * rect.size.height
767
818
  );
768
819
  var r1 = start.distanceToPoint(end);
769
- this.canvasContext.save();
770
- this.addPath(path);
771
- var canvasGradient = this.canvasContext.createRadialGradient(
820
+ var canvasGradient = canvasContext.createRadialGradient(
772
821
  start.x,
773
822
  start.y,
774
823
  r0,
@@ -781,10 +830,7 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
781
830
  stop = gradient.stops[i];
782
831
  canvasGradient.addColorStop(stop.position, stop.color.cssString());
783
832
  }
784
- this.canvasContext.strokeStyle = canvasGradient;
785
- this.canvasContext.stroke();
786
- this.canvasContext.restore();
787
- this.beginPath();
833
+ return canvasGradient;
788
834
  },
789
835
 
790
836
  // ----------------------------------------------------------------------
@@ -917,6 +963,45 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
917
963
  }
918
964
  },
919
965
 
966
+ // ----------------------------------------------------------------------
967
+ // MARK: - Transparency Layers
968
+
969
+ _reusableTransparencyLayer: null,
970
+
971
+ createTransparencyLayer: function(){
972
+ var scale = this.deviceScale * this.renderScale;
973
+ if (this._reusableTransparencyLayer !== null){
974
+ layer = this._reusableTransparencyLayer;
975
+ this._reusableTransparencyLayer = null;
976
+ layer.context.resetTransform();
977
+ layer.context.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
978
+ this._canvasContextAdoptState(layer.context, this.state, scale);
979
+ layer.parentContext = this.canvasContext;
980
+ this._canvasContext = layer.context;
981
+ return layer;
982
+ }
983
+ var parentContext = this.canvasContext;
984
+ var canvas = this._canvasElements[0].ownerDocument.createElement("canvas");
985
+ canvas.width = this._canvasElements[0].width;
986
+ canvas.height = this._canvasElements[0].height;
987
+ var context = canvas.getContext("2d");
988
+ this._canvasContextAdoptState(context, this.state, scale);
989
+ var layer = {
990
+ canvas: canvas,
991
+ context: context,
992
+ parentContext: parentContext
993
+ };
994
+ this._canvasContext = context;
995
+ return layer;
996
+ },
997
+
998
+ drawTransparencyLayer: function(transparencyLayer){
999
+ this._canvasContext = transparencyLayer.parentContext;
1000
+ this._canvasContext.resetTransform();
1001
+ this._canvasContext.drawImage(transparencyLayer.canvas, 0, 0);
1002
+ this._reusableTransparencyLayer = transparencyLayer;
1003
+ },
1004
+
920
1005
  // ----------------------------------------------------------------------
921
1006
  // MARK: - Clipping
922
1007
 
@@ -1296,29 +1381,25 @@ JSClass("UIHTMLDisplayServerCanvasContextPath", JSPath, {
1296
1381
  canvasOperations: function(prototype, transform){
1297
1382
  var operations = [];
1298
1383
  var i, l;
1299
- var j, k;
1300
- var subpath;
1301
- var segment;
1384
+ var elements = this.elements;
1385
+ var element;
1302
1386
  var point;
1303
1387
  var cp1;
1304
1388
  var cp2;
1305
- for (i = 0, l = this.subpaths.length; i < l; ++i){
1306
- subpath = this.subpaths[i];
1307
- point = transform.convertPointToTransform(subpath.firstPoint);
1308
- operations.push({method: prototype.moveTo, arguments: [point.x, point.y]});
1309
- for (j = 0, k = subpath.segments.length; j < k; ++j){
1310
- segment = subpath.segments[j];
1311
- if (segment.type === JSPath.SegmentType.line){
1312
- point = transform.convertPointToTransform(segment.end);
1313
- operations.push({method: prototype.lineTo, arguments: [point.x, point.y]});
1314
- }else if (segment.type === JSPath.SegmentType.curve){
1315
- point = transform.convertPointToTransform(segment.curve.p2);
1316
- cp1 = transform.convertPointToTransform(segment.curve.cp1);
1317
- cp2 = transform.convertPointToTransform(segment.curve.cp2);
1318
- operations.push({method: prototype.bezierCurveTo, arguments: [cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y]});
1319
- }
1320
- }
1321
- if (subpath.closed){
1389
+ for (i = 0, l = elements.length; i < l; ++i){
1390
+ element = elements[i];
1391
+ if (element.type === JSPathElement.Type.move){
1392
+ point = transform.convertPointToTransform(element.point);
1393
+ operations.push({method: prototype.moveTo, arguments: [point.x, point.y]});
1394
+ }else if (element.type === JSPathElement.Type.line){
1395
+ point = transform.convertPointToTransform(element.point);
1396
+ operations.push({method: prototype.lineTo, arguments: [point.x, point.y]});
1397
+ }else if (element.type === JSPathElement.Type.cubicCurve){
1398
+ point = transform.convertPointToTransform(element.curve.p2);
1399
+ cp1 = transform.convertPointToTransform(element.curve.cp1);
1400
+ cp2 = transform.convertPointToTransform(element.curve.cp2);
1401
+ operations.push({method: prototype.bezierCurveTo, arguments: [cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y]});
1402
+ }else if (element.type === JSPathElement.Type.close){
1322
1403
  operations.push({method: prototype.closePath, arguments: []});
1323
1404
  }
1324
1405
  }
@@ -99,6 +99,11 @@ JSClass("UIHTMLTextRun", JSTextRun, {
99
99
  context.translateBy(point.x, point.y + this.font.ascender);
100
100
  context.setFont(this.font);
101
101
  context.setFillColor(this.attributes.textColor);
102
+ if (this.attributes.strokeWidth){
103
+ context.setLineWidth(this.attributes.strokeWidth);
104
+ context.setStrokeColor(this.attributes.strokeColor);
105
+ context.setTextDrawingMode(JSContext.TextDrawingMode.fillStroke);
106
+ }
102
107
  context.showText(this.textNode.nodeValue);
103
108
  context.restore();
104
109
  }
@@ -37,6 +37,7 @@ JSClass("UIHTMLTextTypesetter", JSTextTypesetter, {
37
37
  this._lineElementQueue = [];
38
38
  },
39
39
 
40
+ _useCanvasMetricsForMeasure: true,
40
41
  _suggestedHTMLLineLayout: null,
41
42
 
42
43
  canvasContext: null,
@@ -204,6 +205,14 @@ JSClass("UIHTMLTextTypesetter", JSTextTypesetter, {
204
205
  span.style.color = textColor ? textColor.cssString() : 'black';
205
206
  span.style.backgroundColor = backgroundColor ? backgroundColor.cssString() : '';
206
207
  span.style.pointerEvents = 'none';
208
+
209
+ // Stroke
210
+ var strokeColor = attributes[JSAttributedString.Attribute.strokeColor] || null;
211
+ var strokeWidth = attributes[JSAttributedString.Attribute.strokeWidth] || 0;
212
+ if (strokeWidth > 0){
213
+ span.style.webkitTextStrokeWidth = '%fpx'.sprintf(strokeWidth);
214
+ span.style.webkitTextStrokeColor = strokeColor ? strokeColor.cssString() : 'black';
215
+ }
207
216
  },
208
217
 
209
218
  _styleAttachmentElementWithAttributes: function(span, attributes, attachment){
@@ -260,10 +269,16 @@ JSClass("UIHTMLTextTypesetter", JSTextTypesetter, {
260
269
  runDescriptor.attachment = attachment;
261
270
  }else{
262
271
  if (!newline){
263
- if (attributes[JSAttributedString.Attribute.maskCharacter] !== undefined){
264
- metrics = this.canvasContext.measureText(attributes[JSAttributedString.Attribute.maskCharacter]);
272
+ if (this._useCanvasMetricsForMeasure){
273
+ if (attributes[JSAttributedString.Attribute.maskCharacter] !== undefined){
274
+ metrics = this.canvasContext.measureText(attributes[JSAttributedString.Attribute.maskCharacter]);
275
+ }else{
276
+ metrics = this.canvasContext.measureText(iterator.utf16);
277
+ }
265
278
  }else{
266
- metrics = this.canvasContext.measureText(iterator.utf16);
279
+ metrics = {
280
+ width: runDescriptor.font.widthOfString(iterator.utf16)
281
+ };
267
282
  }
268
283
  usedWidth += metrics.width;
269
284
  runDescriptor.userPerceivedCharacterWidths.push(metrics.width);
@@ -2229,17 +2229,19 @@ JSClass("UIListView", UIScrollView, {
2229
2229
 
2230
2230
  _contextSelectCell: function(cell, location){
2231
2231
  if (this.delegate && this.delegate.menuForListViewCellAtIndexPath){
2232
+ if (this._selectionContainsIndexPath(cell.indexPath)){
2233
+ this._contextSelectedIndexPaths = JSCopy(this._selectedIndexPaths);
2234
+ }else{
2235
+ this._contextSelectedIndexPaths = [cell.indexPath];
2236
+ }
2232
2237
  var menu = this.delegate.menuForListViewCellAtIndexPath(this, cell.indexPath);
2233
2238
  if (menu !== null){
2234
- if (this._selectionContainsIndexPath(cell.indexPath)){
2235
- this._contextSelectedIndexPaths = JSCopy(this._selectedIndexPaths);
2236
- }else{
2237
- this._contextSelectedIndexPaths = [cell.indexPath];
2238
- }
2239
2239
  this._updateVisibleCellStates();
2240
2240
  var locationInCell = this.convertPointToView(location, cell);
2241
2241
  menu.delegate = this;
2242
2242
  menu.openAtLocationInContextView(locationInCell, cell);
2243
+ }else{
2244
+ this._contextSelectedIndexPaths = [];
2243
2245
  }
2244
2246
  }
2245
2247
  },
@@ -477,6 +477,7 @@ JSClass("UIMenuWindowStyler", UIMenuStyler, {
477
477
  borderWidth: JSDynamicProperty('_borderWidth', 0.5),
478
478
  itemContentInsets: JSDynamicProperty('_itemContentInsets', JSInsets(2, 3, 2, 7)),
479
479
  indentationSize: JSDynamicProperty('_indentationSize', 10),
480
+ submenuIndicatorIcon: null,
480
481
  separatorSize: 10,
481
482
  separatorLineWidth: 1,
482
483
  separatorColor: null,
@@ -490,6 +491,7 @@ JSClass("UIMenuWindowStyler", UIMenuStyler, {
490
491
  this.shadowColor = JSColor.menuShadow;
491
492
  this.shadowOffset = JSPoint.Zero;
492
493
  this.separatorColor = this.disabledTextColor;
494
+ this.submenuIndicatorIcon = JSImage.initWithResourceName("UIMenuItemSubmenu", this.bundle).imageWithRenderMode(JSImage.RenderMode.template);
493
495
  },
494
496
 
495
497
  initWithSpec: function(spec){
@@ -552,8 +554,17 @@ JSClass("UIMenuWindowStyler", UIMenuStyler, {
552
554
  if (spec.containsKey('indentationSize')){
553
555
  this.indentationSize = spec.valueForKey("indentationSize", Number);
554
556
  }
557
+ if (spec.containsKey('submenuIndicatorIcon')){
558
+ this.submenuIndicatorIcon = spec.valueForKey("submenuIndicatorIcon", Number);
559
+ }else{
560
+ this.submenuIndicatorIcon = JSImage.initWithResourceName("UIMenuItemSubmenu", this.bundle).imageWithRenderMode(JSImage.RenderMode.template);
561
+ }
555
562
  },
556
563
 
564
+ bundle: JSLazyInitProperty(function(){
565
+ return JSBundle.initWithIdentifier("io.breakside.JSKit.UIKit");
566
+ }),
567
+
557
568
  presentMenuAdjacentToView: function(menu, view, preferredPlacement, spacing){
558
569
  if (preferredPlacement === undefined){
559
570
  preferredPlacement = UIMenu.Placement.below;
@@ -857,46 +868,47 @@ JSClass("UIMenuWindowStyler", UIMenuStyler, {
857
868
  invalidateMenuPresentation: function(menu){
858
869
  var window = menu.stylerProperties.window || null;
859
870
  if (window !== null && window.screen !== null){
860
- menu.updateEnabled();
861
- window.update();
862
-
863
- var screenMetrics = this.metricsForScreen(window.screen);
864
- var size = JSSize(window.frame.size);
871
+ window.invalidate();
872
+ }
873
+ },
865
874
 
866
- // Ensure we're at least the minimum width
867
- // (although max width will take precedence)
868
- if (size.width < menu._minimumWidth){
869
- size.width = menu._minimumWidth;
870
- }
875
+ repositionWindow: function(window){
876
+ var menu = window._menu;
877
+ var screenMetrics = this.metricsForScreen(window.screen);
878
+ var size = JSSize(window.frame.size);
871
879
 
872
- // Limit width to the max screen width
873
- if (size.width > screenMetrics.maximumWidth){
874
- size.width = screenMetrics.maximumWidth;
875
- }
880
+ // Ensure we're at least the minimum width
881
+ // (although max width will take precedence)
882
+ if (size.width < menu._minimumWidth){
883
+ size.width = menu._minimumWidth;
884
+ }
876
885
 
877
- // Set our origin so the origin of the target item matches the given location
878
- var origin = JSPoint(window.frame.origin);
886
+ // Limit width to the max screen width
887
+ if (size.width > screenMetrics.maximumWidth){
888
+ size.width = screenMetrics.maximumWidth;
889
+ }
879
890
 
880
- // Adjust our x position if we've overflowed
881
- var over = origin.x + size.width - screenMetrics.safeFrame.origin.x - screenMetrics.safeFrame.size.width;
882
- if (over > 0){
883
- origin.x -= over;
884
- }
885
- if (origin.x < screenMetrics.safeFrame.origin.x){
886
- origin.x = screenMetrics.safeFrame.origin.x;
887
- }
891
+ // Set our origin so the origin of the target item matches the given location
892
+ var origin = JSPoint(window.frame.origin);
888
893
 
889
- var offset = JSPoint.Zero;
894
+ // Adjust our x position if we've overflowed
895
+ var over = origin.x + size.width - screenMetrics.safeFrame.origin.x - screenMetrics.safeFrame.size.width;
896
+ if (over > 0){
897
+ origin.x -= over;
898
+ }
899
+ if (origin.x < screenMetrics.safeFrame.origin.x){
900
+ origin.x = screenMetrics.safeFrame.origin.x;
901
+ }
890
902
 
891
- // If we extend beyond the bottom, adjust our height
892
- over = origin.y + size.height - screenMetrics.safeFrame.origin.y - screenMetrics.safeFrame.size.height;
893
- if (over > 0){
894
- size.height -= over;
895
- }
903
+ var offset = JSPoint.Zero;
896
904
 
897
- window.frame = JSRect(origin, size);
898
- window.layoutIfNeeded();
905
+ // If we extend beyond the bottom, adjust our height
906
+ over = origin.y + size.height - screenMetrics.safeFrame.origin.y - screenMetrics.safeFrame.size.height;
907
+ if (over > 0){
908
+ size.height -= over;
899
909
  }
910
+
911
+ window.frame = JSRect(origin, size);
900
912
  },
901
913
 
902
914
  metricsForScreen: function(screen){
@@ -1009,6 +1021,12 @@ UIMenu.keyEquivalentCodeForKeyCode = function(keyCode){
1009
1021
  case 0xBD:
1010
1022
  characterCode = 0x2D; // -
1011
1023
  break;
1024
+ case 0xDB:
1025
+ characterCode = 0x5B; // [
1026
+ break;
1027
+ case 0xDD:
1028
+ characterCode = 0x5D; // [
1029
+ break;
1012
1030
  }
1013
1031
  }
1014
1032
  return UIMenu.keyEquivalentCodeForCharacterCode(characterCode);