@breakside/jskit 2026.10.0 → 2026.26.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 (111) 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 +13 -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/JSNodeHTTPClient.js +34 -14
  14. package/Frameworks/Foundation.jsframework/JS/JSNodeURLSessionDataTask.js +2 -2
  15. package/Frameworks/Foundation.jsframework/JS/JSNumberFormatter.js +23 -1
  16. package/Frameworks/Foundation.jsframework/JS/JSPath.js +192 -6
  17. package/Frameworks/Foundation.jsframework/JS/JSPoint.js +56 -0
  18. package/Frameworks/Foundation.jsframework/JS/JSTextRun.js +21 -4
  19. package/Frameworks/Foundation.jsframework/JS/JSTextStorage.js +5 -0
  20. package/Frameworks/Foundation.jsframework/JS/JSTextTypesetter.js +6 -1
  21. package/Frameworks/Foundation.jsframework/JS/JSURLRequest.js +3 -0
  22. package/Frameworks/Foundation.jsframework/JS/JSXMLParser.js +1 -1
  23. package/Frameworks/Foundation.jsframework/JS/JSZip.js +155 -1
  24. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  25. package/Frameworks/Foundation.jsframework/sources.json +1 -0
  26. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  27. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  28. package/Info.json +2 -2
  29. package/Node/Docs/DocClass.js +38 -1
  30. package/Node/Docs/DocComponent.js +26 -1
  31. package/Node/Docs/DocDictionaryProperty.js +8 -0
  32. package/Node/Docs/DocEnum.js +35 -2
  33. package/Node/Docs/DocEnumOption.js +3 -0
  34. package/Node/Docs/DocExtension.js +130 -0
  35. package/Node/Docs/DocFunction.js +8 -0
  36. package/Node/Docs/DocMethod.js +3 -0
  37. package/Node/Docs/DocProperty.js +11 -0
  38. package/Node/Docs/DocProtocol.js +4 -0
  39. package/Node/Docs/DocSpec.js +4 -0
  40. package/Node/Docs/DocSpecProperty.js +8 -0
  41. package/Node/Docs/DocTopicBasedComponent.js +75 -65
  42. package/Node/Documentation.js +73 -4
  43. package/Node/HTMLProjectServer.js +1 -1
  44. package/Node/Markdown.js +2 -2
  45. package/Node/io.breakside.jskit-bundle.js +3 -3
  46. package/Node/jskit +1 -0
  47. package/Resources/doc-default.css +2 -2
  48. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  49. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  50. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  51. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  52. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  53. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  54. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  55. package/Root/Frameworks/DOM/Info.yaml +1 -1
  56. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  57. package/Root/Frameworks/FontKit/FNTOpenTypeFont.js +331 -22
  58. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  59. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  60. package/Root/Frameworks/Foundation/JSAttributedString.js +13 -1
  61. package/Root/Frameworks/Foundation/JSContext.js +44 -17
  62. package/Root/Frameworks/Foundation/JSCubicBezier.js +54 -1
  63. package/Root/Frameworks/Foundation/JSFont.js +8 -9
  64. package/Root/Frameworks/Foundation/JSGradient.js +28 -2
  65. package/Root/Frameworks/Foundation/JSImage+HTML.js +27 -1
  66. package/Root/Frameworks/Foundation/JSNodeHTTPClient.js +34 -14
  67. package/Root/Frameworks/Foundation/JSNodeURLSessionDataTask.js +2 -2
  68. package/Root/Frameworks/Foundation/JSNumberFormatter.js +23 -1
  69. package/Root/Frameworks/Foundation/JSPath.js +192 -6
  70. package/Root/Frameworks/Foundation/JSPoint.js +56 -0
  71. package/Root/Frameworks/Foundation/JSTextRun.js +21 -4
  72. package/Root/Frameworks/Foundation/JSTextStorage.js +5 -0
  73. package/Root/Frameworks/Foundation/JSTextTypesetter.js +6 -1
  74. package/Root/Frameworks/Foundation/JSURLRequest.js +3 -0
  75. package/Root/Frameworks/Foundation/JSXMLParser.js +1 -1
  76. package/Root/Frameworks/Foundation/JSZip.js +155 -1
  77. package/Root/Frameworks/ImageKit/IKHTMLCanvasBitmapContext.js +187 -101
  78. package/Root/Frameworks/ImageKit/IKNodeBitmapContext.js +4 -4
  79. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  80. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  81. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  82. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  83. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  84. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  85. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  86. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  87. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  88. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  89. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  90. package/Root/Frameworks/TestKit/TKAssert.js +4 -4
  91. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  92. package/Root/Frameworks/UIKit/UIApplication.js +9 -1
  93. package/Root/Frameworks/UIKit/UICollectionView.js +4 -0
  94. package/Root/Frameworks/UIKit/UIColorPanel.js +46 -0
  95. package/Root/Frameworks/UIKit/UIColorWell.js +8 -0
  96. package/Root/Frameworks/UIKit/UIHTMLDisplayServer.js +21 -2
  97. package/Root/Frameworks/UIKit/UIHTMLDisplayServerCanvasContext.js +150 -69
  98. package/Root/Frameworks/UIKit/UIHTMLTextRun.js +5 -0
  99. package/Root/Frameworks/UIKit/UIHTMLTextTypesetter.js +18 -3
  100. package/Root/Frameworks/UIKit/UIListView.js +7 -5
  101. package/Root/Frameworks/UIKit/UIMenu.js +50 -32
  102. package/Root/Frameworks/UIKit/UIMenuItem.js +12 -0
  103. package/Root/Frameworks/UIKit/UIMenuView.js +27 -5
  104. package/Root/Frameworks/UIKit/UIPlatform.js +7 -0
  105. package/Root/Frameworks/UIKit/UIPopupButton.js +7 -1
  106. package/Root/Frameworks/UIKit/UIResponder.js +8 -0
  107. package/Root/Frameworks/UIKit/UITextEditor.js +14 -7
  108. package/Root/Frameworks/UIKit/UITokenField.js +65 -0
  109. package/Root/Frameworks/UIKit/UIToolbar.js +1 -1
  110. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  111. package/package.json +1 -1
@@ -88,12 +88,6 @@ JSClass("JSFont", JSObject, {
88
88
 
89
89
  initFromDictionary: function(dictionary){
90
90
  var descriptor = JSFontDescriptor.descriptorWithFamily(dictionary.family, dictionary.weight, dictionary.style);
91
- if (descriptor === null){
92
- if (dictionary.url){
93
- descriptor = JSURLFontDescriptor.initWithURL(JSURL.initWithString(dictionary.url), dictionary.family, dictionary.weight, dictionary.style);
94
- JSFontDescriptor.registerDescriptor(descriptor);
95
- }
96
- }
97
91
  if (descriptor !== null){
98
92
  return JSFont.initWithDescriptor(descriptor, dictionary.size);
99
93
  }
@@ -102,7 +96,6 @@ JSClass("JSFont", JSObject, {
102
96
 
103
97
  dictionaryRepresentation: function(){
104
98
  return {
105
- url: this.descriptor instanceof JSURLFontDescriptor ? this._descriptor.url : undefined,
106
99
  family: this._descriptor.family,
107
100
  weight: this._descriptor.weight,
108
101
  style: this._descriptor.style,
@@ -164,8 +157,14 @@ JSClass("JSFont", JSObject, {
164
157
  },
165
158
 
166
159
  _calculateDisplayMetrics: function(){
167
- this._displayAscender = Math.round(this._ascender);
168
- this._displayDescender = Math.round(this._descender);
160
+ // Math.round always rounds towards +Infinity when a given number
161
+ // has a fractional part exactly equal to 0.5.
162
+ // So, 2.5 rounds to 3 but -2.5 rounds to -2.
163
+ //
164
+ // For the purposes of this calculation, we want negative
165
+ // values like -2.5 to round to -3.
166
+ this._displayAscender = this._ascender < 0 ? -Math.round(-this._ascender) : Math.round(this._ascender);
167
+ this._displayDescender = this._descender < 0 ? -Math.round(-this._descender) : Math.round(this._descender);
169
168
  this._displayLineHeight = this._displayAscender - this._displayDescender;
170
169
  },
171
170
 
@@ -142,7 +142,17 @@ JSClass('JSGradient', JSObject, {
142
142
  },
143
143
 
144
144
  addStop: function(position, color){
145
- this.stops.push({position: position, color: color});
145
+ var l = this.stops.length;
146
+ var stop = {position: position, color: color};
147
+ var i = l - 1;
148
+ for (; i >= 0; --i){
149
+ if (this.stops[i].position <= position){
150
+ break;
151
+ }
152
+ }
153
+ i++;
154
+ this.stops.splice(i, 0, stop);
155
+ return i;
146
156
  },
147
157
 
148
158
  cssString: function(size){
@@ -167,6 +177,19 @@ JSClass('JSGradient', JSObject, {
167
177
  return 'linear-gradient(%fdeg, %s)'.sprintf(angle, cssStops.join(', '));
168
178
  },
169
179
 
180
+ radialCSSString: function(size){
181
+ var cssStops = [];
182
+ var stop;
183
+ for (var i = 0, l = this.stops.length; i < l; ++i){
184
+ stop = this.stops[i];
185
+ cssStops.push('%s %f%%'.sprintf(stop.color.cssString(), stop.position * 100));
186
+ }
187
+ var start = JSPoint(this.start.x * size.width, this.start.y * size.height);
188
+ var end = JSPoint(this.end.x * size.width, this.end.y * size.height);
189
+ var d = start.distanceToPoint(end);
190
+ return 'radial-gradient(%fpx at %f%% %f%%, %s)'.sprintf(d, this.start.x * 100, this.start.y * 100, cssStops.join(', '));
191
+ },
192
+
170
193
  colorAtPosition: function(position){
171
194
  if (position <= this.stops[0].position){
172
195
  return this.stops[0].color;
@@ -229,7 +252,10 @@ JSClass('JSGradient', JSObject, {
229
252
  var gradient = JSGradient.init();
230
253
  gradient.start = transform.convertPointFromTransform(this.start);
231
254
  gradient.end = transform.convertPointFromTransform(this.end);
232
- gradient.stops = JSCopy(this.stops);
255
+ var i, l;
256
+ for (i = 0, l = this.stops.length; i < l; ++i){
257
+ gradient.addStop(this.stops[i].position, this.stops[i].color);
258
+ }
233
259
  return gradient;
234
260
  }
235
261
 
@@ -32,4 +32,30 @@ _JSURLImage.definePropertiesFromExtensions({
32
32
  htmlURLString: function(){
33
33
  return this.url.encodedString;
34
34
  }
35
- });
35
+ });
36
+
37
+ JSImage.definePropertiesFromExtensions({
38
+
39
+ _htmlImage: null,
40
+ _htmlImagePromise: null,
41
+
42
+ preloadForHTMLContexts: function(){
43
+ if (this._htmlImagePromise === null){
44
+ var image = this;
45
+ this._htmlImagePromise = new Promise(function(resolve, reject){
46
+ var htmlImage = new JSGlobalObject.Image();
47
+ htmlImage.src = image.htmlURLString();
48
+ htmlImage.onload = function(){
49
+ resolve(htmlImage);
50
+ };
51
+ htmlImage.onerror = function(){
52
+ reject(new Error("failed to load image"));
53
+ };
54
+ }).then(function(htmlImage){
55
+ image._htmlImage = htmlImage;
56
+ });
57
+ }
58
+ return this._htmlImagePromise;
59
+ }
60
+
61
+ });
@@ -28,7 +28,10 @@ var https = require('https');
28
28
 
29
29
  JSClass("JSNodeHTTPClient", JSObject, {
30
30
 
31
- send: function(request, callbacks, target){
31
+ send: function(request, callbacks, target, allowedRedirects){
32
+ if (allowedRedirects === undefined){
33
+ allowedRedirects = 5;
34
+ }
32
35
  var completed = false;
33
36
  var complete = function(error){
34
37
  if (completed){
@@ -41,6 +44,7 @@ JSClass("JSNodeHTTPClient", JSObject, {
41
44
  };
42
45
  try{
43
46
  var response = null;
47
+ var client = this;
44
48
 
45
49
  // Create node request
46
50
  var options = {
@@ -66,20 +70,36 @@ JSClass("JSNodeHTTPClient", JSObject, {
66
70
  }
67
71
  response._headerMap.parse(rawHeaders.join("\r\n"));
68
72
  response.statusText = message.statusMessage;
69
- if (callbacks.response){
70
- callbacks.response.call(target, response);
71
- }
72
- var chunks = [];
73
- message.on('data', function(chunk){
74
- chunks.push(JSData.initWithNodeBuffer(chunk));
75
- });
76
- message.on('end', function(){
77
- if (completed){
78
- return;
73
+ if (allowedRedirects > 0 && (response.statusCode === JSURLResponse.StatusCode.movedPermanently || response.statusCode === JSURLResponse.StatusCode.found)){
74
+ message.on('data', function(chunk){
75
+ });
76
+ message.on('end', function(){
77
+ if (completed){
78
+ return;
79
+ }
80
+ request = request.redirectedRequestToURL(response.location);
81
+ if (callbacks.redirect){
82
+ callbacks.redirect(request);
83
+ }
84
+ client.send(request, callbacks, target, allowedRedirects - 1);
85
+ // TODO: send new request
86
+ });
87
+ }else{
88
+ if (callbacks.response){
89
+ callbacks.response.call(target, response);
79
90
  }
80
- response.data = JSData.initWithChunks(chunks);
81
- complete(null);
82
- });
91
+ var chunks = [];
92
+ message.on('data', function(chunk){
93
+ chunks.push(JSData.initWithNodeBuffer(chunk));
94
+ });
95
+ message.on('end', function(){
96
+ if (completed){
97
+ return;
98
+ }
99
+ response.data = JSData.initWithChunks(chunks);
100
+ complete(null);
101
+ });
102
+ }
83
103
  });
84
104
  nodeRequest.on('error', function(error){
85
105
  complete(error);
@@ -38,8 +38,8 @@ JSClass("JSNodeURLSessionDataTask", JSURLSessionDataTask, {
38
38
  }
39
39
  if (this.cancelation === null){
40
40
  this.cancelation = JSNodeHTTPClient.shared.send(this.currentRequest, {
41
- redirect: function(url){
42
- this._currentRequest = this._originalRequest.redirectedRequestToURL(url);
41
+ redirect: function(request){
42
+ this._currentRequest = request;
43
43
  },
44
44
  uploadProgress: function(sent, total){
45
45
  this.session._taskDidSendBodyData(this, sent, total);
@@ -560,6 +560,8 @@ JSClass("JSNumberFormatter", JSObject, {
560
560
  var canSeePercent = false;
561
561
  var canSeeGroup = false;
562
562
  var seenDigit = false;
563
+ var prefix = "";
564
+ var suffix = "";
563
565
  var stringMatches = function(substr){
564
566
  if (substr.length === 0){
565
567
  return false;
@@ -588,6 +590,7 @@ JSClass("JSNumberFormatter", JSObject, {
588
590
  sign = -1;
589
591
  canSeeSign = false;
590
592
  i += this.minusSign.length;
593
+ prefix += this.minusSign;
591
594
  }else if (canSeeSign && stringMatches(this.plusSign)){
592
595
  canSeeSign = false;
593
596
  i += this.plusSign.length;
@@ -625,8 +628,12 @@ JSClass("JSNumberFormatter", JSObject, {
625
628
  i += this.groupingSeparator.length;
626
629
  }else if (c == 0x20){
627
630
  ++i;
631
+ }else if (!seenDigit){
632
+ prefix += string[i];
633
+ ++i;
628
634
  }else{
629
- return null;
635
+ suffix += string[i];
636
+ ++i;
630
637
  }
631
638
  }
632
639
  if (!seenDigit){
@@ -639,6 +646,21 @@ JSClass("JSNumberFormatter", JSObject, {
639
646
  if (sign < 0){
640
647
  n = -n;
641
648
  }
649
+ if (n >= 0){
650
+ if (prefix !== "" && prefix !== this.positivePrefix){
651
+ return null;
652
+ }
653
+ if (suffix !== "" && suffix !== this.positiveSuffix){
654
+ return null;
655
+ }
656
+ }else{
657
+ if (prefix !== "" && prefix !== this.negativePrefix){
658
+ return null;
659
+ }
660
+ if (suffix !== "" && suffix !== this.negativeSuffix){
661
+ return null;
662
+ }
663
+ }
642
664
  return n;
643
665
  },
644
666
 
@@ -53,6 +53,40 @@ JSClass("JSPath", JSObject, {
53
53
  return true;
54
54
  },
55
55
 
56
+ elements: JSReadOnlyProperty(),
57
+
58
+ _elements: null,
59
+
60
+ _invalidateElements: function(){
61
+ this._elements = null;
62
+ },
63
+
64
+ getElements: function(){
65
+ if (this._elements === null){
66
+ this._elements = [];
67
+ var i, l, j, k;
68
+ var subpath, segment;
69
+ for (i = 0, l = this.subpaths.length; i < l; ++i){
70
+ subpath = this.subpaths[i];
71
+ this._elements.push(JSPathElement(JSPathElement.Type.move, subpath.firstPoint));
72
+ if (subpath.segments.length > 0){
73
+ for (j = 0, k = subpath.segments.length; j < k; ++j){
74
+ segment = subpath.segments[j];
75
+ if (segment.type == JSPath.SegmentType.line){
76
+ this._elements.push(JSPathElement(JSPathElement.Type.line, segment.end));
77
+ }else if (segment.type == JSPath.SegmentType.curve){
78
+ this._elements.push(JSPathElement(JSPathElement.Type.cubicCurve, segment.curve.p2, segment.curve.p1, segment.curve.cp1, segment.curve.cp2));
79
+ }
80
+ }
81
+ }
82
+ if (subpath.closed){
83
+ this._elements.push(JSPathElement(JSPathElement.Type.close));
84
+ }
85
+ }
86
+ }
87
+ return this._elements;
88
+ },
89
+
56
90
  subpaths: null,
57
91
  currentPoint: JSReadOnlyProperty('_currentPoint', null),
58
92
  currentSubpath: null,
@@ -68,6 +102,7 @@ JSClass("JSPath", JSObject, {
68
102
  this.subpaths.push(this.currentSubpath);
69
103
  this._currentPoint = point;
70
104
  this._invalidateBoundingRect();
105
+ this._invalidateElements();
71
106
  },
72
107
 
73
108
  addLineToPoint: function(point, transform){
@@ -80,6 +115,7 @@ JSClass("JSPath", JSObject, {
80
115
  this.currentSubpath.segments.push({type: JSPath.SegmentType.line, end: point});
81
116
  this._currentPoint = point;
82
117
  this._invalidateBoundingRect();
118
+ this._invalidateElements();
83
119
  },
84
120
 
85
121
  addCurveToPoint: function(point, control1, control2, transform){
@@ -93,6 +129,15 @@ JSClass("JSPath", JSObject, {
93
129
  this.currentSubpath.segments.push({type: JSPath.SegmentType.curve, curve: curve});
94
130
  this._currentPoint = point;
95
131
  this._invalidateBoundingRect();
132
+ this._invalidateElements();
133
+ },
134
+
135
+ addQuadraticCurveToPoint: function(point, control, transform){
136
+ if (transform !== undefined){
137
+ point = transform.convertPointFromTransform(point);
138
+ control = transform.convertPointFromTransform(control);
139
+ }
140
+ this.addCurveToPoint(point, this._currentPoint.interpolation(control, 2/3), point.interpolation(control, 2/3));
96
141
  },
97
142
 
98
143
  addRect: function(rect, transform){
@@ -328,7 +373,9 @@ JSClass("JSPath", JSObject, {
328
373
  if (this.subpaths.length === 0){
329
374
  this.moveToPoint(p1, transform);
330
375
  }else{
331
- this.addLineToPoint(p1, transform);
376
+ if (Math.abs(p1.x - this._currentPoint.x) > 0.00001 || Math.abs(p1.y - this._currentPoint.y) > 0.00001){
377
+ this.addLineToPoint(p1, transform);
378
+ }
332
379
  }
333
380
 
334
381
  // If there's no radius, then there's nothing left to do
@@ -512,10 +559,10 @@ JSClass("JSPath", JSObject, {
512
559
  // distance is clockwise or counter clockwise.
513
560
  // Note: we could swap the angles and always arc in a fixed direction,
514
561
  // but I think it makes more sense to always arc from p0 to p2.
515
- if (startAngle < 0){
562
+ if (startAngle <= 0){
516
563
  startAngle += TWO_PI;
517
564
  }
518
- if (endAngle < 0){
565
+ if (endAngle <= 0){
519
566
  endAngle += TWO_PI;
520
567
  }
521
568
  var clockwise;
@@ -524,7 +571,9 @@ JSClass("JSPath", JSObject, {
524
571
  }else{
525
572
  clockwise = endAngle - startAngle <= Math.PI;
526
573
  }
527
- this.moveToPoint(t1);
574
+ if (Math.abs(t1.x - this._currentPoint.x) > 0.00001 || Math.abs(t1.y - this._currentPoint.y) > 0.00001){
575
+ this.moveToPoint(t1);
576
+ }
528
577
  this.addArc(center, radius, startAngle, endAngle, clockwise);
529
578
  },
530
579
 
@@ -533,6 +582,25 @@ JSClass("JSPath", JSObject, {
533
582
  this._currentPoint = this.currentSubpath.firstPoint;
534
583
  this.currentSubpath.closed = true;
535
584
  this.currentSubpath = null;
585
+ this._invalidateElements();
586
+ }
587
+ },
588
+
589
+ addPath: function(path, transform){
590
+ var i, l;
591
+ var elements = path.elements;
592
+ var element;
593
+ for (i = 0, l = elements.length; i < l; ++i){
594
+ element = elements[i];
595
+ if (element.type === JSPathElement.Type.move){
596
+ this.moveToPoint(element.point, transform);
597
+ }else if (element.type === JSPathElement.Type.line){
598
+ this.addLineToPoint(element.point, transform);
599
+ }else if (element.type === JSPathElement.Type.cubicCurve){
600
+ this.addCurveToPoint(element.curve.p2, element.curve.cp1, element.curve.cp2, transform);
601
+ }else if (element.type === JSPathElement.Type.close){
602
+ this.closeSubpath();
603
+ }
536
604
  }
537
605
  },
538
606
 
@@ -841,6 +909,9 @@ JSClass("JSPath", JSObject, {
841
909
  intersectsPath: function(other, fillRule){
842
910
  var boundingRect = this.boundingRect;
843
911
  // no need to check if bounding rect don't intersect
912
+ if (boundingRect === null){
913
+ return false;
914
+ }
844
915
  if (!boundingRect.intersectsRect(other.boundingRect)){
845
916
  return false;
846
917
  }
@@ -989,8 +1060,59 @@ JSClass("JSPath", JSObject, {
989
1060
  return path;
990
1061
  },
991
1062
 
1063
+ pathWithFlatness: function(flatness){
1064
+ var path = JSPath.init();
1065
+ var i, l, j, k;
1066
+ var element;
1067
+ var elements = this.elements;
1068
+ var points;
1069
+ for (i = 0, l = elements.length; i < l; ++i){
1070
+ element = elements[i];
1071
+ if (element.type === JSPathElement.Type.move){
1072
+ path.moveToPoint(element.point);
1073
+ }else if (element.type === JSPathElement.Type.line){
1074
+ path.addLineToPoint(element.point);
1075
+ }else if (element.type === JSPathElement.Type.cubicCurve){
1076
+ points = element.curve.pointsWithFlatness(flatness);
1077
+ for (j = 1, k = points.length; j < k; ++j){
1078
+ path.addLineToPoint(points[j]);
1079
+ }
1080
+ }else if (element.type === JSPathElement.Type.close){
1081
+ path.closeSubpath();
1082
+ }
1083
+ }
1084
+ return path;
1085
+ },
1086
+
1087
+ outlineContainsPoint: function(point, tolerance){
1088
+ // Only works on an already flattened path (we don't check curves)
1089
+ var firstPoint = JSPoint.Zero;
1090
+ var currentPoint = JSPoint.firstPoint;
1091
+ var elements = this.elements;
1092
+ var element;
1093
+ var i, l;
1094
+ for (i = 0, l = elements.length; i < l; ++i){
1095
+ element = elements[i];
1096
+ if (element.type === JSPathElement.Type.move){
1097
+ firstPoint = element.point;
1098
+ currentPoint = firstPoint;
1099
+ }else if (element.type === JSPathElement.Type.line){
1100
+ if (currentPoint.lineToPointContainsPoint(element.point, point, tolerance)){
1101
+ return true;
1102
+ }
1103
+ currentPoint = element.point;
1104
+ }else if (element.type === JSPathElement.Type.close){
1105
+ if (currentPoint.lineToPointContainsPoint(firstPoint, point, tolerance)){
1106
+ return true;
1107
+ }
1108
+ currentPoint = firstPoint;
1109
+ }
1110
+ }
1111
+ return false;
1112
+ },
1113
+
992
1114
  initWithSVGPathData: function(svgPathData){
993
- // TODO: Q, q, T, t, A, a
1115
+ // TODO: T, t, A, a
994
1116
  //
995
1117
  // - All instructions are expressed as one character (e.g., a moveto
996
1118
  // is expressed as an M).
@@ -1161,7 +1283,7 @@ JSClass("JSPath", JSObject, {
1161
1283
  break;
1162
1284
  }
1163
1285
  if (point !== null){
1164
- point = point.adding(JSPoint(command.values[0], command.values[1]))
1286
+ point = point.adding(JSPoint(command.values[0], command.values[1]));
1165
1287
  }else{
1166
1288
  point = JSPoint(command.values[0], command.values[1]);
1167
1289
  }
@@ -1291,6 +1413,42 @@ JSClass("JSPath", JSObject, {
1291
1413
  subpath.segments.push(segment);
1292
1414
  point = curve.p2;
1293
1415
  }
1416
+ }else if (command.name === "Q"){
1417
+ if (command.values.length < 4){
1418
+ break;
1419
+ }
1420
+ if (subpath === null){
1421
+ subpath = Subpath(point);
1422
+ this.subpaths.push(subpath);
1423
+ }
1424
+ for (v = 0; v < command.values.length - 3; v += 4){
1425
+ curve = JSCubicBezier.fromQuadradicBezier(
1426
+ point,
1427
+ JSPoint(command.values[v], command.values[v + 1]),
1428
+ JSPoint(command.values[v + 2], command.values[v + 3])
1429
+ );
1430
+ segment = {type: JSPath.SegmentType.curve, curve: curve};
1431
+ subpath.segments.push(segment);
1432
+ point = curve.p2;
1433
+ }
1434
+ }else if (command.name === "q"){
1435
+ if (command.values.length < 4){
1436
+ break;
1437
+ }
1438
+ if (subpath === null){
1439
+ subpath = Subpath(point);
1440
+ this.subpaths.push(subpath);
1441
+ }
1442
+ for (v = 0; v < command.values.length - 3; v += 4){
1443
+ curve = JSCubicBezier.fromQuadradicBezier(
1444
+ point,
1445
+ point.adding(JSPoint(command.values[v], command.values[v + 1])),
1446
+ point.adding(JSPoint(command.values[v + 2], command.values[v + 3]))
1447
+ );
1448
+ segment = {type: JSPath.SegmentType.curve, curve: curve};
1449
+ subpath.segments.push(segment);
1450
+ point = curve.p2;
1451
+ }
1294
1452
  }else if (command.name === "S"){
1295
1453
  if (command.values.length < 4){
1296
1454
  break;
@@ -1502,6 +1660,34 @@ JSPath.Sides = {
1502
1660
  }
1503
1661
  };
1504
1662
 
1663
+ JSGlobalObject.JSPathElement = function(type, point, point1, point2, point3){
1664
+ if (this === undefined){
1665
+ if (type === null){
1666
+ return null;
1667
+ }
1668
+ return new JSPathElement(type, point, point1, point2, point3);
1669
+ }
1670
+ if (type instanceof JSPathElement){
1671
+ this.type = type.type;
1672
+ this.point = JSPoint(type.point);
1673
+ this.curve = JSCubicBezier(type.curve);
1674
+ }else{
1675
+ this.type = type;
1676
+ this.point = JSPoint(point || null);
1677
+ if (type === JSPathElement.Type.cubicCurve){
1678
+ this.curve = JSCubicBezier(point1, point2, point3, point);
1679
+ }
1680
+ }
1681
+ };
1682
+
1683
+ JSPathElement.Type = {
1684
+ move: 1,
1685
+ line: 2,
1686
+ cubicCurve: 3,
1687
+ close: 4
1688
+ };
1689
+
1690
+
1505
1691
  JSPath.Sides.minYmaxY = JSPath.Sides.minY | JSPath.Sides.maxY;
1506
1692
  JSPath.Sides.minXmaxX = JSPath.Sides.minX | JSPath.Sides.maxX;
1507
1693
 
@@ -72,6 +72,62 @@ JSPoint.prototype = {
72
72
 
73
73
  subtracting: function(other){
74
74
  return JSPoint(this.x - other.x, this.y - other.y);
75
+ },
76
+
77
+ interpolation: function(p1, percentage){
78
+ return JSPoint(
79
+ this.x + (p1.x - this.x) * percentage,
80
+ this.y + (p1.y - this.y) * percentage
81
+ );
82
+ },
83
+
84
+ lineToPointContainsPoint: function(other, point, tolerance){
85
+ // first check if we're within the box defined by the segment
86
+ var minX, maxX;
87
+ if (this.x <= other.x){
88
+ minX = this.x - tolerance;
89
+ maxX = other.x + tolerance;
90
+ }else{
91
+ minX = other.x - tolerance;
92
+ maxX = this.x + tolerance;
93
+ }
94
+ var minY, maxY;
95
+ if (this.y <= other.y){
96
+ minY = this.y - tolerance;
97
+ maxY = other.y + tolerance;
98
+ }else{
99
+ minY = other.y - tolerance;
100
+ maxY = this.y + tolerance;
101
+ }
102
+ if (point.x < minX){
103
+ return false;
104
+ }
105
+ if (point.x > maxX){
106
+ return false;
107
+ }
108
+ if (point.y < minY){
109
+ return false;
110
+ }
111
+ if (point.y > maxY){
112
+ return false;
113
+ }
114
+
115
+ // next check for the special cases of a vertical and horizontal line,
116
+ // where we must be near the line if we're within the box
117
+ var dx = (other.x - this.x);
118
+ var dy = (other.y - this.y);
119
+ if (Math.abs(dx) < 0.00001 || (Math.abs(dy) < 0.00001)){
120
+ return true;
121
+ }
122
+
123
+ // Finally, find the nearest point to the line and see how far away we are
124
+ var m1 = dy / dx;
125
+ var b1 = this.y - m1 * this.x;
126
+ var m2 = -1/m1;
127
+ var b2 = point.y - m2 * point.x;
128
+ var x = (b2 - b1) / (m1 - m2);
129
+ var y = m1 * x + b1;
130
+ return JSPoint(x, y).distanceToPoint(point) <= tolerance;
75
131
  }
76
132
  };
77
133
 
@@ -16,6 +16,7 @@
16
16
  // #import "JSObject.js"
17
17
  // #import "CoreTypes.js"
18
18
  // #import "JSAttributedString.js"
19
+ // #import "JSContext.js"
19
20
  'use strict';
20
21
 
21
22
  (function(){
@@ -31,8 +32,10 @@ JSClass("JSTextRun", JSObject, {
31
32
  attributes: null,
32
33
  font: null,
33
34
  attachment: null,
35
+ _useDisplayMetrics: false,
34
36
 
35
- initWithGlyphs: function(glyphs, glyphCharacterLengths, font, attributes, range){
37
+ initWithGlyphs: function(glyphs, glyphCharacterLengths, font, attributes, range, useDisplayMetrics){
38
+ this._useDisplayMetrics = useDisplayMetrics === true;
36
39
  this.glyphs = glyphs;
37
40
  this.glyphCharacterLengths = glyphCharacterLengths;
38
41
  this.attributes = attributes;
@@ -42,8 +45,13 @@ JSClass("JSTextRun", JSObject, {
42
45
  this._size = JSSize(this.attachment.size);
43
46
  }else{
44
47
  this.font = font;
45
- this.baseline = -font.descender;
46
- this._size = JSSize(0, font.lineHeight);
48
+ if (this._useDisplayMetrics){
49
+ this.baseline = -font.displayDescender;
50
+ this._size = JSSize(0, font.displayLineHeight);
51
+ }else{
52
+ this.baseline = -font.descender;
53
+ this._size = JSSize(0, font.lineHeight);
54
+ }
47
55
  for (var i = 0, l = glyphs.length; i < l; ++i){
48
56
  this._size.width += font.widthOfGlyph(glyphs[i]);
49
57
  }
@@ -67,9 +75,18 @@ JSClass("JSTextRun", JSObject, {
67
75
  context.restore();
68
76
  }else{
69
77
  context.save();
70
- context.translateBy(point.x, point.y + this.font.ascender);
78
+ if (this._useDisplayMetrics){
79
+ context.translateBy(point.x, point.y + this.font.displayAscender);
80
+ }else{
81
+ context.translateBy(point.x, point.y + this.font.ascender);
82
+ }
71
83
  context.setFont(this.font);
72
84
  context.setFillColor(this.attributes.textColor);
85
+ if (this.attributes.strokeWidth){
86
+ context.setLineWidth(this.attributes.strokeWidth);
87
+ context.setStrokeColor(this.attributes.strokeColor);
88
+ context.setTextDrawingMode(JSContext.TextDrawingMode.fillStroke);
89
+ }
73
90
  context.showGlyphs(this.glyphs);
74
91
  context.restore();
75
92
  }
@@ -30,6 +30,11 @@ JSClass("JSTextStorage", JSAttributedString, {
30
30
  this._commonTextStorageInit();
31
31
  },
32
32
 
33
+ initFromDictionary: function(dictionary){
34
+ JSTextStorage.$super.initFromDictionary.call(this, dictionary);
35
+ this._commonTextStorageInit();
36
+ },
37
+
33
38
  _commonTextStorageInit: function(){
34
39
  this._layoutManagers = [];
35
40
  },