@breakside/jskit 2026.4.1 → 2026.8.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 (46) 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/io.breakside.JSKit.FontKit-bundle.js +2 -2
  5. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  6. package/Frameworks/Foundation.jsframework/JS/JSContext.js +23 -1
  7. package/Frameworks/Foundation.jsframework/JS/JSGradient.js +25 -0
  8. package/Frameworks/Foundation.jsframework/JS/JSPath.js +80 -0
  9. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  10. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  11. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  12. package/Info.json +2 -2
  13. package/Node/TestCommand.js +1 -1
  14. package/Node/io.breakside.jskit-bundle.js +2 -2
  15. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  16. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  17. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  18. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  19. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  20. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  21. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  22. package/Root/Frameworks/DOM/Info.yaml +1 -1
  23. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  24. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  25. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  26. package/Root/Frameworks/Foundation/JSContext.js +23 -1
  27. package/Root/Frameworks/Foundation/JSGradient.js +25 -0
  28. package/Root/Frameworks/Foundation/JSPath.js +80 -0
  29. package/Root/Frameworks/ImageKit/IKHTMLCanvasBitmapContext.js +91 -13
  30. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  31. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  32. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  33. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  34. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  35. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  36. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  37. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  38. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  39. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  40. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  41. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  42. package/Root/Frameworks/UIKit/UICollectionViewGridLayout.js +59 -19
  43. package/Root/Frameworks/UIKit/UIHTMLDisplayServerCanvasContext.js +87 -13
  44. package/Root/Frameworks/UIKit/UILayer.js +6 -0
  45. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  46. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.DOM",
4
- "JSBundleVersion": "2026.4.1",
4
+ "JSBundleVersion": "2026.8.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
@@ -9,5 +9,5 @@
9
9
  "node": "DOM+Node.js"
10
10
  },
11
11
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
12
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
12
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
13
13
  }
@@ -3,7 +3,7 @@ JSBundle.bundles['io.breakside.JSKit.DOM'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.DOM",
6
- "JSBundleVersion": "2026.4.1",
6
+ "JSBundleVersion": "2026.8.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
@@ -11,7 +11,7 @@ JSBundle.bundles['io.breakside.JSKit.DOM'] = {
11
11
  "node": "DOM+Node.js"
12
12
  },
13
13
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
14
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
14
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
15
15
  },
16
16
  "Resources": [],
17
17
  "ResourceLookup": {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.FontKit",
4
- "JSBundleVersion": "2026.4.1",
4
+ "JSBundleVersion": "2026.8.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
8
8
  "html": "FontKit+HTML.js"
9
9
  },
10
10
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
11
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
11
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
12
12
  }
@@ -3,14 +3,14 @@ JSBundle.bundles['io.breakside.JSKit.FontKit'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.FontKit",
6
- "JSBundleVersion": "2026.4.1",
6
+ "JSBundleVersion": "2026.8.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
10
10
  "html": "FontKit+HTML.js"
11
11
  },
12
12
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
13
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
13
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
14
14
  },
15
15
  "Resources": [],
16
16
  "ResourceLookup": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.Foundation",
4
- "JSBundleVersion": "2026.4.1",
4
+ "JSBundleVersion": "2026.8.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
@@ -9,5 +9,5 @@
9
9
  "node": "Foundation+Node.js"
10
10
  },
11
11
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
12
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
12
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
13
13
  }
@@ -209,7 +209,29 @@ JSClass("JSContext", JSObject, {
209
209
  drawLinearGradient: function(gradient, rect){
210
210
  },
211
211
 
212
- drawRadialGradient: function(gradient, rect, r0, r1){
212
+ drawRadialGradient: function(gradient, rect){
213
+ },
214
+
215
+ drawLinearGradientStroke: function(gradient, path){
216
+ // FIXME: gradient should be aligned to original path rect
217
+ var strokePath = path.pathThatFillsStroke(this.state.lineWidth, this.state.lineCap, this.state.lineJoin, this.state.miterLimit);
218
+ this.save();
219
+ var rect = strokePath.boundingRect;
220
+ this.addPath(path);
221
+ this.clip();
222
+ this.drawLinearGradient(gradient, rect);
223
+ this.restore();
224
+ },
225
+
226
+ drawRadialGradientStroke: function(gradient, path){
227
+ // FIXME: gradient should be aligned to original path rect
228
+ var strokePath = path.pathThatFillsStroke(this.state.lineWidth, this.state.lineCap, this.state.lineJoin, this.state.miterLimit);
229
+ this.save();
230
+ var rect = strokePath.boundingRect;
231
+ this.addPath(path);
232
+ this.clip();
233
+ this.drawRadialGradient(gradient, rect);
234
+ this.restore();
213
235
  },
214
236
 
215
237
  // ----------------------------------------------------------------------
@@ -86,6 +86,31 @@ JSClass('JSGradient', JSObject, {
86
86
  }
87
87
  },
88
88
 
89
+ initFromDictionary: function(dictionary){
90
+ if (dictionary === null || dictionary === undefined){
91
+ return null;
92
+ }
93
+ this.start = JSPoint(dictionary.x0, dictionary.y0);
94
+ this.end = JSPoint(dictionary.x1, dictionary.y1);
95
+ this.stops = [];
96
+ var i, l;
97
+ for (i = 0, l = dictionary.stops.length; i < l; ++i){
98
+ this.addStop(dictionary.stops[i][0], JSColor.initWithRGBAHexString(dictionary.stops[i][1]));
99
+ }
100
+ },
101
+
102
+ dictionaryRepresentation: function(){
103
+ return {
104
+ x0: this.start.x,
105
+ y0: this.start.y,
106
+ x1: this.end.x,
107
+ y1: this.end.y,
108
+ stops: this.stops.map(function(s){
109
+ return [s.position, s.color.rgbaHexStringRepresentation()];
110
+ })
111
+ };
112
+ },
113
+
89
114
  initWithStops: function(position1, color1 /* , ... */){
90
115
  this.init();
91
116
  var args = Array.prototype.slice.call(arguments, 0);
@@ -838,6 +838,83 @@ JSClass("JSPath", JSObject, {
838
838
  return JSRect(min, JSSize(max.x - min.x, max.y - min.y));
839
839
  },
840
840
 
841
+ initWithSVGPathData: function(svgPathData){
842
+ if (svgPathData === null || svgPathData === undefined){
843
+ return null;
844
+ }
845
+ var parts = svgPathData.split(" ").filter(function(p){
846
+ return p !== "";
847
+ });
848
+ this.subpaths = [];
849
+ var part;
850
+ var subpath = null;
851
+ var point = JSPoint.Zero;
852
+ var cp1;
853
+ var cp2;
854
+ var curve;
855
+ while (parts.length > 0){
856
+ part = parts.shift();
857
+ if (part === "M"){
858
+ if (parts.length < 2){
859
+ break;
860
+ }
861
+ point = JSPoint.Zero;
862
+ part = parts.shift();
863
+ point.x = parseFloat(part);
864
+ part = parts.shift();
865
+ point.y = parseFloat(part);
866
+ subpath = Subpath(point);
867
+ this.subpaths.push(subpath);
868
+ this._currentPoint = point;
869
+ }else if (part === "L"){
870
+ if (parts.length < 2){
871
+ break;
872
+ }
873
+ if (subpath === null){
874
+ subpath = Subpath(point);
875
+ this.subpaths.push(subpath);
876
+ }
877
+ point = JSPoint.Zero;
878
+ part = parts.shift();
879
+ point.x = parseFloat(part);
880
+ part = parts.shift();
881
+ point.y = parseFloat(part);
882
+ subpath.segments.push({type: JSPath.SegmentType.line, end: point});
883
+ this._currentPoint = point;
884
+ }else if (part === "C"){
885
+ if (parts.length < 6){
886
+ break;
887
+ }
888
+ if (subpath === null){
889
+ subpath = Subpath(point);
890
+ this.subpaths.push(subpath);
891
+ }
892
+ cp1 = JSPoint.Zero;
893
+ cp2 = JSPoint.Zero;
894
+ point = JSPoint.Zero;
895
+ part = parts.shift();
896
+ cp1.x = parseFloat(part);
897
+ part = parts.shift();
898
+ cp1.y = parseFloat(part);
899
+ part = parts.shift();
900
+ cp2.x = parseFloat(part);
901
+ part = parts.shift();
902
+ cp2.y = parseFloat(part);
903
+ part = parts.shift();
904
+ point.x = parseFloat(part);
905
+ part = parts.shift();
906
+ point.y = parseFloat(part);
907
+ subpath.segments.push({type: JSPath.SegmentType.curve, curve: JSCubicBezier(this._currentPoint, cp1, cp2, point)});
908
+ this._currentPoint = point;
909
+ }else if (part === "Z"){
910
+ if (subpath !== null){
911
+ subpath.closed = true;
912
+ subpath = null;
913
+ }
914
+ }
915
+ }
916
+ },
917
+
841
918
  svgPathData: function(){
842
919
  var subpath;
843
920
  var segment;
@@ -868,6 +945,9 @@ JSClass("JSPath", JSObject, {
868
945
  data += "Z ";
869
946
  }
870
947
  }
948
+ if (data.length > 0 && data[data.length - 1] == " "){
949
+ data = data.substr(0, data.length - 1);
950
+ }
871
951
  return data;
872
952
  },
873
953
 
@@ -3,7 +3,7 @@ JSBundle.bundles['io.breakside.JSKit.Foundation'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.Foundation",
6
- "JSBundleVersion": "2026.4.1",
6
+ "JSBundleVersion": "2026.8.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
@@ -11,7 +11,7 @@ JSBundle.bundles['io.breakside.JSKit.Foundation'] = {
11
11
  "node": "Foundation+Node.js"
12
12
  },
13
13
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
14
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
14
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
15
15
  },
16
16
  "Resources": [
17
17
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.SecurityKit",
4
- "JSBundleVersion": "2026.4.1",
4
+ "JSBundleVersion": "2026.8.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
@@ -9,5 +9,5 @@
9
9
  "node": "SecurityKit+Node.js"
10
10
  },
11
11
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
12
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
12
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
13
13
  }
@@ -3,7 +3,7 @@ JSBundle.bundles['io.breakside.JSKit.SecurityKit'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.SecurityKit",
6
- "JSBundleVersion": "2026.4.1",
6
+ "JSBundleVersion": "2026.8.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
@@ -11,7 +11,7 @@ JSBundle.bundles['io.breakside.JSKit.SecurityKit'] = {
11
11
  "node": "SecurityKit+Node.js"
12
12
  },
13
13
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
14
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
14
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
15
15
  },
16
16
  "Resources": [],
17
17
  "ResourceLookup": {
package/Info.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "JSBundleType": "node",
3
3
  "JSBundleIdentifier": "io.breakside.jskit",
4
- "JSBundleVersion": "2026.4.1",
4
+ "JSBundleVersion": "2026.8.0",
5
5
  "JSExecutableName": "jskit",
6
6
  "NPMOrganization": "breakside",
7
7
  "JSResources": [
8
8
  "Tests/HTMLTestRunner.js",
9
9
  "Tests/NodeTestRunner.js"
10
10
  ],
11
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
11
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
12
12
  }
@@ -239,7 +239,7 @@ JSClass("TestCommand", Command, {
239
239
  var query = url.query;
240
240
  query.add("headless");
241
241
  url.query = query;
242
- var page = await browser.newPage();
242
+ var page = await browser.newPage({locale: Intl.DateTimeFormat().resolvedOptions().locale});
243
243
  var cmd = this;
244
244
  await new Promise(function(resolve, reject){
245
245
  page.exposeFunction("headlessPrint", function(text, ttyOnly){
@@ -3,14 +3,14 @@ JSBundle.bundles['io.breakside.jskit'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "node",
5
5
  "JSBundleIdentifier": "io.breakside.jskit",
6
- "JSBundleVersion": "2026.4.1",
6
+ "JSBundleVersion": "2026.8.0",
7
7
  "JSExecutableName": "jskit",
8
8
  "NPMOrganization": "breakside",
9
9
  "JSResources": [
10
10
  "Tests/HTMLTestRunner.js",
11
11
  "Tests/NodeTestRunner.js"
12
12
  ],
13
- "GitRevision": "81476f101c402411441be784d6beeee4a31239a4"
13
+ "GitRevision": "b4a1f6046edfde1343010e48660aff5aa3e8353e"
14
14
  },
15
15
  "Resources": [
16
16
  {
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.APIKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSIncludeDirectories:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.APIKitTesting
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.AuthKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.CSSOM
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.ChartKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2021 Breakside Inc.
6
6
  # JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.ConferenceKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.DBKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.DOM
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.Dispatch
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSCopyright: Copyright © 2020 Breakside Inc.
5
5
  JSBundleEnvironments:
6
6
  html: Dispatch+HTML.js
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.FontKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.Foundation
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -209,7 +209,29 @@ JSClass("JSContext", JSObject, {
209
209
  drawLinearGradient: function(gradient, rect){
210
210
  },
211
211
 
212
- drawRadialGradient: function(gradient, rect, r0, r1){
212
+ drawRadialGradient: function(gradient, rect){
213
+ },
214
+
215
+ drawLinearGradientStroke: function(gradient, path){
216
+ // FIXME: gradient should be aligned to original path rect
217
+ var strokePath = path.pathThatFillsStroke(this.state.lineWidth, this.state.lineCap, this.state.lineJoin, this.state.miterLimit);
218
+ this.save();
219
+ var rect = strokePath.boundingRect;
220
+ this.addPath(path);
221
+ this.clip();
222
+ this.drawLinearGradient(gradient, rect);
223
+ this.restore();
224
+ },
225
+
226
+ drawRadialGradientStroke: function(gradient, path){
227
+ // FIXME: gradient should be aligned to original path rect
228
+ var strokePath = path.pathThatFillsStroke(this.state.lineWidth, this.state.lineCap, this.state.lineJoin, this.state.miterLimit);
229
+ this.save();
230
+ var rect = strokePath.boundingRect;
231
+ this.addPath(path);
232
+ this.clip();
233
+ this.drawRadialGradient(gradient, rect);
234
+ this.restore();
213
235
  },
214
236
 
215
237
  // ----------------------------------------------------------------------
@@ -86,6 +86,31 @@ JSClass('JSGradient', JSObject, {
86
86
  }
87
87
  },
88
88
 
89
+ initFromDictionary: function(dictionary){
90
+ if (dictionary === null || dictionary === undefined){
91
+ return null;
92
+ }
93
+ this.start = JSPoint(dictionary.x0, dictionary.y0);
94
+ this.end = JSPoint(dictionary.x1, dictionary.y1);
95
+ this.stops = [];
96
+ var i, l;
97
+ for (i = 0, l = dictionary.stops.length; i < l; ++i){
98
+ this.addStop(dictionary.stops[i][0], JSColor.initWithRGBAHexString(dictionary.stops[i][1]));
99
+ }
100
+ },
101
+
102
+ dictionaryRepresentation: function(){
103
+ return {
104
+ x0: this.start.x,
105
+ y0: this.start.y,
106
+ x1: this.end.x,
107
+ y1: this.end.y,
108
+ stops: this.stops.map(function(s){
109
+ return [s.position, s.color.rgbaHexStringRepresentation()];
110
+ })
111
+ };
112
+ },
113
+
89
114
  initWithStops: function(position1, color1 /* , ... */){
90
115
  this.init();
91
116
  var args = Array.prototype.slice.call(arguments, 0);
@@ -838,6 +838,83 @@ JSClass("JSPath", JSObject, {
838
838
  return JSRect(min, JSSize(max.x - min.x, max.y - min.y));
839
839
  },
840
840
 
841
+ initWithSVGPathData: function(svgPathData){
842
+ if (svgPathData === null || svgPathData === undefined){
843
+ return null;
844
+ }
845
+ var parts = svgPathData.split(" ").filter(function(p){
846
+ return p !== "";
847
+ });
848
+ this.subpaths = [];
849
+ var part;
850
+ var subpath = null;
851
+ var point = JSPoint.Zero;
852
+ var cp1;
853
+ var cp2;
854
+ var curve;
855
+ while (parts.length > 0){
856
+ part = parts.shift();
857
+ if (part === "M"){
858
+ if (parts.length < 2){
859
+ break;
860
+ }
861
+ point = JSPoint.Zero;
862
+ part = parts.shift();
863
+ point.x = parseFloat(part);
864
+ part = parts.shift();
865
+ point.y = parseFloat(part);
866
+ subpath = Subpath(point);
867
+ this.subpaths.push(subpath);
868
+ this._currentPoint = point;
869
+ }else if (part === "L"){
870
+ if (parts.length < 2){
871
+ break;
872
+ }
873
+ if (subpath === null){
874
+ subpath = Subpath(point);
875
+ this.subpaths.push(subpath);
876
+ }
877
+ point = JSPoint.Zero;
878
+ part = parts.shift();
879
+ point.x = parseFloat(part);
880
+ part = parts.shift();
881
+ point.y = parseFloat(part);
882
+ subpath.segments.push({type: JSPath.SegmentType.line, end: point});
883
+ this._currentPoint = point;
884
+ }else if (part === "C"){
885
+ if (parts.length < 6){
886
+ break;
887
+ }
888
+ if (subpath === null){
889
+ subpath = Subpath(point);
890
+ this.subpaths.push(subpath);
891
+ }
892
+ cp1 = JSPoint.Zero;
893
+ cp2 = JSPoint.Zero;
894
+ point = JSPoint.Zero;
895
+ part = parts.shift();
896
+ cp1.x = parseFloat(part);
897
+ part = parts.shift();
898
+ cp1.y = parseFloat(part);
899
+ part = parts.shift();
900
+ cp2.x = parseFloat(part);
901
+ part = parts.shift();
902
+ cp2.y = parseFloat(part);
903
+ part = parts.shift();
904
+ point.x = parseFloat(part);
905
+ part = parts.shift();
906
+ point.y = parseFloat(part);
907
+ subpath.segments.push({type: JSPath.SegmentType.curve, curve: JSCubicBezier(this._currentPoint, cp1, cp2, point)});
908
+ this._currentPoint = point;
909
+ }else if (part === "Z"){
910
+ if (subpath !== null){
911
+ subpath.closed = true;
912
+ subpath = null;
913
+ }
914
+ }
915
+ }
916
+ },
917
+
841
918
  svgPathData: function(){
842
919
  var subpath;
843
920
  var segment;
@@ -868,6 +945,9 @@ JSClass("JSPath", JSObject, {
868
945
  data += "Z ";
869
946
  }
870
947
  }
948
+ if (data.length > 0 && data[data.length - 1] == " "){
949
+ data = data.substr(0, data.length - 1);
950
+ }
871
951
  return data;
872
952
  },
873
953
 
@@ -248,38 +248,116 @@ JSClass("IKHTMLCanvasBitmapContext", IKBitmapContext, {
248
248
 
249
249
  drawLinearGradient: function(gradient, rect){
250
250
  this.enqueueOperation(function(state, completion, target){
251
- // since all the gradient coordinates are in the unit rectangle,
252
- // we'll align our transform so the unit rectangle matches rect.
253
- this.canvasContext.translate(rect.origin.x, rect.origin.y);
254
- this.canvasContext.scale(rect.size.width, rect.size.height);
255
- var canvasGradient = this.canvasContext.createLinearGradient(gradient.start.x, gradient.start.y, gradient.end.x, gradient.end.y);
251
+ this.canvasContext.save();
252
+ var canvasGradient = this.canvasContext.createLinearGradient(
253
+ rect.origin.x + gradient.start.x * rect.size.width,
254
+ rect.origin.y + gradient.start.y * rect.size.height,
255
+ rect.origin.x + gradient.end.x * rect.size.width,
256
+ rect.origin.y + gradient.end.y * rect.size.height
257
+ );
256
258
  var stop;
257
259
  for (var i = 0, l = gradient.stops.length; i < l; ++i){
258
260
  stop = gradient.stops[i];
259
261
  canvasGradient.addColorStop(stop.position, stop.color.cssString());
260
262
  }
261
263
  this.canvasContext.fillStyle = canvasGradient;
262
- this.canvasContext.fillRect(0, 0, 1, 1);
264
+ this.canvasContext.fillRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
265
+ this.canvasContext.restore();
263
266
  completion.call(target);
264
267
  });
265
268
  },
266
269
 
267
- drawRadialGradient: function(gradient, rect, r0, r1){
270
+ drawRadialGradient: function(gradient, rect){
268
271
  this.enqueueOperation(function(state, completion, target){
269
- // since all the gradient coordinates are in the unit rectangle,
270
- // we'll align our transform so the unit rectangle matches rect.
271
- this.canvasContext.translate(rect.origin.x, rect.origin.y);
272
- this.canvasContext.scale(rect.size.width, rect.size.height);
273
- var canvasGradient = this.canvasContext.createRadialGradient(gradient.start.x, gradient.start.y, r0, gradient.end.x, gradient.end.y, r1);
272
+ var r0 = 0;
273
+ var start = JSPoint(
274
+ rect.origin.x + gradient.start.x * rect.size.width,
275
+ rect.origin.y + gradient.start.y * rect.size.height
276
+ );
277
+ var end = JSPoint(
278
+ rect.origin.x + gradient.end.x * rect.size.width,
279
+ rect.origin.y + gradient.end.y * rect.size.height
280
+ );
281
+ var r1 = start.distanceToPoint(end);
282
+ this.canvasContext.save();
283
+ var canvasGradient = this.canvasContext.createRadialGradient(
284
+ start.x,
285
+ start.y,
286
+ r0,
287
+ start.x,
288
+ start.y,
289
+ r1
290
+ );
274
291
  var stop;
275
292
  for (var i = 0, l = gradient.stops.length; i < l; ++i){
276
293
  stop = gradient.stops[i];
277
294
  canvasGradient.addColorStop(stop.position, stop.color.cssString());
278
295
  }
279
296
  this.canvasContext.fillStyle = canvasGradient;
280
- this.canvasContext.fillRect(0, 0, 1, 1);
297
+ this.canvasContext.fillRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
298
+ this.canvasContext.restore();
299
+ completion.call(target);
300
+ });
301
+ },
302
+
303
+ drawLinearGradientStroke: function(gradient, path){
304
+ this.enqueueOperation(function(state, completion, target){
305
+ var rect = path.boundingRect;
306
+ this.canvasContext.save();
307
+ this.addPath(path);
308
+ var canvasGradient = this.canvasContext.createLinearGradient(
309
+ rect.origin.x + gradient.start.x * rect.size.width,
310
+ rect.origin.y + gradient.start.y * rect.size.height,
311
+ rect.origin.x + gradient.end.x * rect.size.width,
312
+ rect.origin.y + gradient.end.y * rect.size.height
313
+ );
314
+ var stop;
315
+ for (var i = 0, l = gradient.stops.length; i < l; ++i){
316
+ stop = gradient.stops[i];
317
+ canvasGradient.addColorStop(stop.position, stop.color.cssString());
318
+ }
319
+ this.canvasContext.strokeStyle = canvasGradient;
320
+ this.canvasContext.stroke();
321
+ this.canvasContext.restore();
281
322
  completion.call(target);
282
323
  });
324
+ this.beginPath();
325
+ },
326
+
327
+ drawRadialGradientStroke: function(gradient, path){
328
+ this.enqueueOperation(function(state, completion, target){
329
+ var rect = path.boundingRect;
330
+ var r0 = 0;
331
+ var start = JSPoint(
332
+ rect.origin.x + gradient.start.x * rect.size.width,
333
+ rect.origin.y + gradient.start.y * rect.size.height
334
+ );
335
+ var end = JSPoint(
336
+ rect.origin.x + gradient.end.x * rect.size.width,
337
+ rect.origin.y + gradient.end.y * rect.size.height
338
+ );
339
+ var r1 = start.distanceToPoint(end);
340
+ this.canvasContext.save();
341
+ this.addPath(path);
342
+ var canvasGradient = this.canvasContext.createRadialGradient(
343
+ start.x,
344
+ start.y,
345
+ r0,
346
+ start.x,
347
+ start.y,
348
+ r1
349
+ );
350
+ var stop;
351
+ for (var i = 0, l = gradient.stops.length; i < l; ++i){
352
+ stop = gradient.stops[i];
353
+ canvasGradient.addColorStop(stop.position, stop.color.cssString());
354
+ }
355
+ this.canvasContext.strokeStyle = canvasGradient;
356
+ this.canvasContext.stroke();
357
+ this.canvasContext.restore();
358
+ completion.call(target);
359
+ });
360
+ this.beginPath();
283
361
  },
284
362
 
285
363
  // ----------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.ImageKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.MediaKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.MediaKitUI
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.NotificationKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2021 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.PDFKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.QRKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.SearchKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.SecurityKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.ServerKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.ServerKitTesting
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.TestKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.UIKit
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -17,6 +17,14 @@
17
17
  /* global UICollectionView */
18
18
  "use strict";
19
19
 
20
+ JSProtocol("UICollectionViewGridLayoutDelegate", JSProtocol, {
21
+
22
+ heightForCollectionViewHeaderInSection: function(collectionView, sectionIndex){},
23
+ heightForCollectionViewFooterInSection: function(collectionView, sectionIndex){},
24
+ cellSizeForCollectionViewInSection: function(collectionView, sectionIndex){},
25
+
26
+ });
27
+
20
28
  JSClass("UICollectionViewGridLayout", UICollectionViewLayout, {
21
29
 
22
30
  init: function(){
@@ -228,33 +236,63 @@ JSClass("UICollectionViewGridLayout", UICollectionViewLayout, {
228
236
  var rowOffset = 0;
229
237
  var sectionHeaderAndSpacingHeight = 0;
230
238
  var sectionFooterAndSpacingHeight = 0;
231
- if (this._sectionHeaderHeight > 0){
232
- sectionHeaderAndSpacingHeight = this._sectionHeaderHeight + this._sectionHeaderSpacing;
233
- }
234
- if (this._sectionFooterHeight > 0){
235
- sectionFooterAndSpacingHeight= this._sectionFooterHeight + this._sectionFooterSpacing;
236
- }
239
+ var sectionHeaderHeight;
240
+ var sectionFooterHeight;
241
+ var sectionCellSize;
242
+ var sectionColumns;
237
243
  for (sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex){
244
+ sectionHeaderHeight = this._sectionHeaderHeight;
245
+ sectionFooterHeight = this._sectionFooterHeight;
246
+ sectionCellSize = this._cellSize;
247
+ if (this.collectionView.delegate && this.collectionView.delegate.heightForCollectionViewHeaderInSection){
248
+ sectionHeaderHeight = this.collectionView.delegate.heightForCollectionViewHeaderInSection(this.collectionView, sectionIndex);
249
+ }
250
+ if (this.collectionView.delegate && this.collectionView.delegate.heightForCollectionViewFooterInSection){
251
+ sectionFooterHeight = this.collectionView.delegate.heightForCollectionViewFooterInSection(this.collectionView, sectionIndex);
252
+ }
253
+ if (this.collectionView.delegate && this.collectionView.delegate.cellSizeForCollectionViewInSection){
254
+ sectionCellSize = this.collectionView.delegate.cellSizeForCollectionViewInSection(this.collectionView, sectionIndex);
255
+ }
256
+ if (sectionHeaderHeight > 0){
257
+ sectionHeaderAndSpacingHeight = sectionHeaderHeight + this._sectionHeaderSpacing;
258
+ }
259
+ if (sectionFooterHeight > 0){
260
+ sectionFooterAndSpacingHeight= sectionFooterHeight + this._sectionFooterSpacing;
261
+ }
262
+ if (sectionCellSize.width != this._cellSize.width){
263
+ columnCount = Math.max(1, Math.floor((availableWidth + this._columnSpacing) / (sectionCellSize.width + this._columnSpacing)));
264
+ x = this._collectionInsets.left + this._sectionInsets.left;
265
+ sectionColumns = [];
266
+ for (columnIndex = 0; columnIndex < columnCount; ++columnIndex){
267
+ sectionColumns.push({x: x});
268
+ x += sectionCellSize.width + this._columnSpacing;
269
+ }
270
+ }else{
271
+ columnCount = this._cachedLayout.columns.length;
272
+ sectionColumns = this._cachedLayout.columns;
273
+ }
238
274
  cellCount = this.collectionView.dataSource.numberOfCellsInCollectionViewSection(this.collectionView, sectionIndex);
239
275
  rowCount = Math.ceil(cellCount / columnCount);
240
276
  size.height = this._sectionInsets.height + sectionHeaderAndSpacingHeight + sectionFooterAndSpacingHeight;
241
- size.height += rowCount * this._cellSize.height + (rowCount - 1) * this._rowSpacing;
277
+ size.height += rowCount * sectionCellSize.height + (rowCount - 1) * this._rowSpacing;
242
278
  this._cachedLayout.sections.push({
243
279
  rowOffset: rowOffset,
244
280
  cellCount: cellCount,
245
281
  rowCount: rowCount,
246
282
  frame: JSRect(origin, size),
283
+ columns: sectionColumns,
284
+ cellSize: sectionCellSize,
247
285
  headerFrame: JSRect(
248
286
  origin.x + this._sectionInsets.left,
249
287
  origin.y + this._sectionInsets.top,
250
288
  availableWidth,
251
- this._sectionHeaderHeight
289
+ sectionHeaderHeight
252
290
  ),
253
291
  footerFrame: JSRect(
254
292
  origin.x + this._sectionInsets.left,
255
- origin.y + size.height - this._sectionInsets.bottom - this._sectionFooterHeight,
293
+ origin.y + size.height - this._sectionInsets.bottom - sectionFooterHeight,
256
294
  availableWidth,
257
- this._sectionFooterHeight
295
+ sectionFooterHeight
258
296
  ),
259
297
  cellsFrame: JSRect(
260
298
  origin.x + this._sectionInsets.left,
@@ -295,7 +333,7 @@ JSClass("UICollectionViewGridLayout", UICollectionViewLayout, {
295
333
  if (this._cachedLayout.headerFrame.size.height > 0 && (this._headersStickToTop || rect.intersectsRect(this._cachedLayout.headerFrame))){
296
334
  attributes.push(this.layoutAttributesForSupplimentaryViewAtIndexPath(JSIndexPath([]), UICollectionViewGridLayout.SupplimentaryKind.header));
297
335
  }
298
- var columnCount = this._cachedLayout.columns.length;
336
+ var columnCount;
299
337
  var columnIndex;
300
338
  var sectionIndex;
301
339
  var sectionCount = this._cachedLayout.sections.length;
@@ -304,12 +342,14 @@ JSClass("UICollectionViewGridLayout", UICollectionViewLayout, {
304
342
  var y;
305
343
  var y0 = rect.origin.y;
306
344
  var y1 = rect.origin.y + rect.size.height;
307
- var rowHeight = this._cellSize.height + this._rowSpacing;
345
+ var rowHeight;
308
346
  var rowIndex;
309
347
  var attrs;
310
348
  // FIXME: can do a more efficient search of sections
311
349
  for (sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex){
312
350
  sectionLayout = this._cachedLayout.sections[sectionIndex];
351
+ columnCount = sectionLayout.columns.length;
352
+ rowHeight = sectionLayout.cellSize.height + this._rowSpacing;
313
353
  if (sectionLayout.frame.size.height > 0 && rect.intersectsRect(sectionLayout.frame)){
314
354
  if (this._showsSectionBackgroundViews){
315
355
  attributes.push(this.layoutAttributesForSupplimentaryViewAtIndexPath(JSIndexPath([sectionIndex]), UICollectionViewGridLayout.SupplimentaryKind.background));
@@ -326,10 +366,10 @@ JSClass("UICollectionViewGridLayout", UICollectionViewLayout, {
326
366
  for (; rowIndex < sectionLayout.rowCount && y < y1; ++rowIndex, y += rowHeight){
327
367
  for (columnIndex = 0; columnIndex < columnCount && indexPath.row < sectionLayout.cellCount; ++columnIndex, ++indexPath.row){
328
368
  attrs = UICollectionViewLayoutAttributes.initCellAtIndexPath(indexPath, JSRect(
329
- this._cachedLayout.columns[columnIndex].x,
369
+ sectionLayout.columns[columnIndex].x,
330
370
  y,
331
- this._cellSize.width,
332
- this._cellSize.height
371
+ sectionLayout.cellSize.width,
372
+ sectionLayout.cellSize.height
333
373
  ));
334
374
  attrs.rowIndex = sectionLayout.rowOffset + rowIndex;
335
375
  attrs.columnIndex = columnIndex;
@@ -350,16 +390,16 @@ JSClass("UICollectionViewGridLayout", UICollectionViewLayout, {
350
390
 
351
391
  layoutAttributesForCellAtIndexPath: function(indexPath){
352
392
  var sectionLayout = this._cachedLayout.sections[indexPath.section];
353
- var columnCount = this._cachedLayout.columns.length;
393
+ var columnCount = sectionLayout.columns.length;
354
394
  var rowIndex = Math.floor(indexPath.row / columnCount);
355
395
  var columnIndex = indexPath.row % columnCount;
356
396
  var origin = JSPoint(
357
- this._cachedLayout.columns[columnIndex].x,
358
- sectionLayout.cellsFrame.origin.y + rowIndex * (this._cellSize.height + this._rowSpacing)
397
+ sectionLayout.columns[columnIndex].x,
398
+ sectionLayout.cellsFrame.origin.y + rowIndex * (sectionLayout.cellSize.height + this._rowSpacing)
359
399
  );
360
400
  var frame = JSRect(
361
401
  origin,
362
- this._cellSize
402
+ sectionLayout.cellSize
363
403
  );
364
404
  var attributes = UICollectionViewLayoutAttributes.initCellAtIndexPath(indexPath, frame);
365
405
  attributes.rowIndex = sectionLayout.rowOffset + rowIndex;
@@ -169,6 +169,7 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
169
169
  this[methodName](layer);
170
170
  }
171
171
  this.propertiesNeedingUpdate = {};
172
+ this.renderScale = layer._renderScale;
172
173
 
173
174
  if (this.needsCustomDisplay){
174
175
  layer._drawInContext(this);
@@ -412,13 +413,14 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
412
413
  return this.element.ownerDocument.defaultView.devicePixelRatio || 1;
413
414
  },
414
415
 
416
+ renderScale: 1,
417
+
415
418
  getCanvasContext: function(){
416
419
  if (!this._canvasContext){
417
- var scale = this.deviceScale;
418
- // FIXME: scale should account for any transform ancestor layers
420
+ var scale = this.deviceScale * this.renderScale;
419
421
  var canvas = this._dequeueReusableCanvasElement();
420
- canvas.width = this.bounds.size.width * scale;
421
- canvas.height = this.bounds.size.height * scale;
422
+ canvas.width = Math.ceil(this.bounds.size.width * scale);
423
+ canvas.height = Math.ceil(this.bounds.size.height * scale);
422
424
  this._insertChildElement(canvas);
423
425
  this._canvasContext = canvas.getContext('2d');
424
426
  this._canvasContext.save();
@@ -685,32 +687,104 @@ JSClass("UIHTMLDisplayServerCanvasContext", UIHTMLDisplayServerContext, {
685
687
 
686
688
  drawLinearGradient: function(gradient, rect){
687
689
  this.canvasContext.save();
688
- this.canvasContext.translate(rect.origin.x, rect.origin.y);
689
- this.canvasContext.scale(rect.size.width, rect.size.height);
690
- var canvasGradient = this.canvasContext.createLinearGradient(gradient.start.x, gradient.start.y, gradient.end.x, gradient.end.y);
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
+ );
691
696
  var stop;
692
697
  for (var i = 0, l = gradient.stops.length; i < l; ++i){
693
698
  stop = gradient.stops[i];
694
699
  canvasGradient.addColorStop(stop.position, stop.color.cssString());
695
700
  }
696
701
  this.canvasContext.fillStyle = canvasGradient;
697
- this.canvasContext.fillRect(0, 0, 1, 1);
702
+ this.canvasContext.fillRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
698
703
  this.canvasContext.restore();
699
704
  },
700
705
 
701
- drawRadialGradient: function(gradient, rect, r0, r1){
706
+ 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);
702
717
  this.canvasContext.save();
703
- this.canvasContext.translate(rect.origin.x, rect.origin.y);
704
- this.canvasContext.scale(rect.size.width, rect.size.height);
705
- var canvasGradient = this.canvasContext.createRadialGradient(gradient.start.x, gradient.start.y, r0, gradient.end.x, gradient.end.y, r1);
718
+ var canvasGradient = this.canvasContext.createRadialGradient(
719
+ start.x,
720
+ start.y,
721
+ r0,
722
+ start.x,
723
+ start.y,
724
+ r1
725
+ );
706
726
  var stop;
707
727
  for (var i = 0, l = gradient.stops.length; i < l; ++i){
708
728
  stop = gradient.stops[i];
709
729
  canvasGradient.addColorStop(stop.position, stop.color.cssString());
710
730
  }
711
731
  this.canvasContext.fillStyle = canvasGradient;
712
- this.canvasContext.fillRect(0, 0, 1, 1);
732
+ this.canvasContext.fillRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
733
+ this.canvasContext.restore();
734
+ },
735
+
736
+ drawLinearGradientStroke: function(gradient, path){
737
+ var rect = path.boundingRect;
738
+ this.canvasContext.save();
739
+ this.addPath(path);
740
+ var canvasGradient = this.canvasContext.createLinearGradient(
741
+ rect.origin.x + gradient.start.x * rect.size.width,
742
+ rect.origin.y + gradient.start.y * rect.size.height,
743
+ rect.origin.x + gradient.end.x * rect.size.width,
744
+ rect.origin.y + gradient.end.y * rect.size.height
745
+ );
746
+ var stop;
747
+ for (var i = 0, l = gradient.stops.length; i < l; ++i){
748
+ stop = gradient.stops[i];
749
+ canvasGradient.addColorStop(stop.position, stop.color.cssString());
750
+ }
751
+ this.canvasContext.strokeStyle = canvasGradient;
752
+ this.canvasContext.stroke();
713
753
  this.canvasContext.restore();
754
+ this.beginPath();
755
+ },
756
+
757
+ drawRadialGradientStroke: function(gradient, path){
758
+ var rect = path.boundingRect;
759
+ var r0 = 0;
760
+ var start = JSPoint(
761
+ rect.origin.x + gradient.start.x * rect.size.width,
762
+ rect.origin.y + gradient.start.y * rect.size.height
763
+ );
764
+ var end = JSPoint(
765
+ rect.origin.x + gradient.end.x * rect.size.width,
766
+ rect.origin.y + gradient.end.y * rect.size.height
767
+ );
768
+ var r1 = start.distanceToPoint(end);
769
+ this.canvasContext.save();
770
+ this.addPath(path);
771
+ var canvasGradient = this.canvasContext.createRadialGradient(
772
+ start.x,
773
+ start.y,
774
+ r0,
775
+ start.x,
776
+ start.y,
777
+ r1
778
+ );
779
+ var stop;
780
+ for (var i = 0, l = gradient.stops.length; i < l; ++i){
781
+ stop = gradient.stops[i];
782
+ canvasGradient.addColorStop(stop.position, stop.color.cssString());
783
+ }
784
+ this.canvasContext.strokeStyle = canvasGradient;
785
+ this.canvasContext.stroke();
786
+ this.canvasContext.restore();
787
+ this.beginPath();
714
788
  },
715
789
 
716
790
  // ----------------------------------------------------------------------
@@ -769,6 +769,12 @@ JSClass("UILayer", JSObject, {
769
769
  // MARK: - Display
770
770
 
771
771
  _needsDisplay: false,
772
+ renderScale: JSDynamicProperty("_renderScale", 1),
773
+
774
+ setRenderScale: function(renderScale){
775
+ this._renderScale = renderScale;
776
+ this.setNeedsDisplay();
777
+ },
772
778
 
773
779
  setNeedsDisplay: function(){
774
780
  if (this._displayServer !== null){
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.UIKitTesting
3
- JSBundleVersion: 2026.4.1
3
+ JSBundleVersion: 2026.8.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "node": ">=10.10.0"
13
13
  },
14
14
  "name": "@breakside/jskit",
15
- "version": "2026.4.1",
15
+ "version": "2026.8.0",
16
16
  "license": "SEE LICENSE IN LICENSE.txt",
17
17
  "files": [
18
18
  "*"