@breakside/jskit 2022.22.1 → 2022.23.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 (63) hide show
  1. package/Frameworks/DOM.jsframework/Info.json +2 -2
  2. package/Frameworks/DOM.jsframework/JS/XMLSerializer.js +9 -4
  3. package/Frameworks/DOM.jsframework/io.breakside.JSKit.DOM-bundle.js +2 -2
  4. package/Frameworks/DOM.jsframework/sources.json +10 -3
  5. package/Frameworks/FontKit.jsframework/Info.json +2 -2
  6. package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
  7. package/Frameworks/FontKit.jsframework/sources.json +34 -2
  8. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  9. package/Frameworks/Foundation.jsframework/JS/JSAttributedString.js +213 -20
  10. package/Frameworks/Foundation.jsframework/JS/JSFont.js +39 -15
  11. package/Frameworks/Foundation.jsframework/JS/JSFontDescriptor+HTML.js +8 -0
  12. package/Frameworks/Foundation.jsframework/JS/JSFontDescriptor.js +105 -27
  13. package/Frameworks/Foundation.jsframework/JS/JSTextAttachment.js +6 -1
  14. package/Frameworks/Foundation.jsframework/JS/JSTextRun.js +3 -0
  15. package/Frameworks/Foundation.jsframework/JS/String+JS.js +34 -0
  16. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  17. package/Frameworks/Foundation.jsframework/sources.json +167 -8
  18. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  19. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  20. package/Frameworks/SecurityKit.jsframework/sources.json +43 -2
  21. package/Info.json +2 -2
  22. package/Node/Builder.js +20 -9
  23. package/Node/FrameworkBuilder.js +22 -2
  24. package/Node/Project.js +22 -2
  25. package/Node/io.breakside.jskit-bundle.js +2 -2
  26. package/Node/jskit +7 -7
  27. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  28. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  29. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  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/DOM/XMLSerializer.js +9 -4
  36. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  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 +213 -20
  40. package/Root/Frameworks/Foundation/JSFont.js +39 -15
  41. package/Root/Frameworks/Foundation/JSFontDescriptor+HTML.js +8 -0
  42. package/Root/Frameworks/Foundation/JSFontDescriptor.js +105 -27
  43. package/Root/Frameworks/Foundation/JSTextAttachment.js +6 -1
  44. package/Root/Frameworks/Foundation/JSTextRun.js +3 -0
  45. package/Root/Frameworks/Foundation/String+JS.js +34 -0
  46. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  47. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  48. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  49. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  50. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  51. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  52. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  53. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  54. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  55. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  56. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  57. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  58. package/Root/Frameworks/UIKit/UIHTMLTextFrame.js +3 -0
  59. package/Root/Frameworks/UIKit/UIHTMLTextRun.js +3 -1
  60. package/Root/Frameworks/UIKit/UIHTMLTextTypesetter.js +14 -0
  61. package/Root/Frameworks/UIKit/UILabel.js +43 -10
  62. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  63. package/package.json +1 -1
@@ -138,9 +138,10 @@ JSClass("FrameworkBuilder", Builder, {
138
138
  var genericFrameworks = new Set();
139
139
  var genericFiles = new Set();
140
140
  var genericFeatures = new Set();
141
+ var genericGlobals = new Set();
141
142
  var sources = {};
142
143
  for (let env in this.bundleEnvironments){
143
- sources[env] = {frameworks: [], files: [], features: []};
144
+ sources[env] = {frameworks: [], files: [], features: [], globals: []};
144
145
  let imports = this.importsByEnvironment[env];
145
146
  for (let i = 0, l = imports.frameworks.length; i < l; ++i){
146
147
  let framework = imports.frameworks[i];
@@ -176,6 +177,15 @@ JSClass("FrameworkBuilder", Builder, {
176
177
  sources[env].features.push(feature);
177
178
  }
178
179
  }
180
+ for (let i = 0, l = imports.globals.length; i < l; ++i){
181
+ let name = imports.globals[i];
182
+ if (env == 'generic'){
183
+ genericGlobals.add(name);
184
+ }
185
+ if (env == 'generic' || !genericGlobals.has(name)){
186
+ sources[env].globals.push(name);
187
+ }
188
+ }
179
189
  }
180
190
  return sources;
181
191
  },
@@ -185,6 +195,7 @@ JSClass("FrameworkBuilder", Builder, {
185
195
  var genericFrameworks = new Set();
186
196
  var genericFiles = new Set();
187
197
  var genericFeatures = new Set();
198
+ var genericGlobals = new Set();
188
199
  var sources = {};
189
200
  var copyright = this.project.getInfoString("JSCopyright", this.resources);
190
201
  var licenseString = await this.project.licenseNoticeString();
@@ -195,7 +206,7 @@ JSClass("FrameworkBuilder", Builder, {
195
206
  }
196
207
  var header = "%s (%s)\n----\n%s%s".sprintf(this.project.info.JSBundleIdentifier, this.project.info.JSBundleVersion, copyright, licenseString);
197
208
  for (let env in this.bundleEnvironments){
198
- sources[env] = {frameworks: [], files: [], features: []};
209
+ sources[env] = {frameworks: [], files: [], features: [], globals: []};
199
210
  let filename = this.bundleEnvironments[env];
200
211
  let compilation = JavascriptCompilation.initWithName(filename, this.sourcesURL, this.fileManager);
201
212
  var fullSourcesURL = this.sourcesURL.appendingPathComponent("_debug", true);
@@ -238,6 +249,15 @@ JSClass("FrameworkBuilder", Builder, {
238
249
  sources[env].features.push(feature);
239
250
  }
240
251
  }
252
+ for (let i = 0, l = imports.globals.length; i < l; ++i){
253
+ let name = imports.globals[i];
254
+ if (env == 'generic'){
255
+ genericGlobals.add(name);
256
+ }
257
+ if (env == 'generic' || !genericGlobals.has(name)){
258
+ sources[env].globals.push(name);
259
+ }
260
+ }
241
261
  }
242
262
  return sources;
243
263
  },
package/Node/Project.js CHANGED
@@ -310,7 +310,8 @@ JSClass("Project", JSObject, {
310
310
  var result = {
311
311
  files: [],
312
312
  frameworks: [],
313
- features: []
313
+ features: [],
314
+ globals: []
314
315
  };
315
316
  var visited = {
316
317
  paths: new Set(),
@@ -359,6 +360,11 @@ JSClass("Project", JSObject, {
359
360
  result.features.push(feature);
360
361
  }
361
362
  }
363
+ let globals = js.globals();
364
+ for (let i = 0, l = globals.length; i < l; ++i){
365
+ let name = globals[i];
366
+ result.globals.push(name);
367
+ }
362
368
  }
363
369
  };
364
370
 
@@ -443,7 +449,21 @@ JSClass("Project", JSObject, {
443
449
  let url = await this.urlForFrameworkName(name, includeDirectoryURLs);
444
450
  if (url !== null){
445
451
  if (url.fileExtension === '.jsframework'){
446
- // TODO: extract from built framework
452
+ let sourcesURL = url.appendingPathComponent("sources.json");
453
+ let data = await this.fileManager.contentsAtURL(sourcesURL);
454
+ let sources = JSON.parse(data.stringByDecodingUTF8());
455
+ if (sources.generic.globals){
456
+ for (let name of sources.generic.globals){
457
+ globals.push(name);
458
+ }
459
+ }
460
+ for (let env of envs){
461
+ if ((env in sources) && sources[env].globals){
462
+ for (let name of sources[env].globals){
463
+ globals.push(name);
464
+ }
465
+ }
466
+ }
447
467
  }else{
448
468
  let frameworkProject = Project.initWithURL(url);
449
469
  try{
@@ -3,14 +3,14 @@ JSBundle.bundles['io.breakside.jskit'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "node",
5
5
  "JSBundleIdentifier": "io.breakside.jskit",
6
- "JSBundleVersion": "2022.22.1",
6
+ "JSBundleVersion": "2022.23.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": "5fd19ed5f4d9bc2a74be2eef1e45ad5c0de02cc9"
13
+ "GitRevision": "e2a77335b26d1a321448e73eb7b733cdd0714cc8"
14
14
  },
15
15
  "Resources": [
16
16
  {
package/Node/jskit CHANGED
@@ -49,19 +49,19 @@ require("./../Frameworks/Foundation.jsframework/JS/JSURLResponse.js");
49
49
  require("./../Frameworks/Foundation.jsframework/JS/JSXMLParser.js");
50
50
  require("./../Frameworks/Foundation.jsframework/JS/JSImage.js");
51
51
  require("./../Frameworks/Foundation.jsframework/JS/JSTextAttachment.js");
52
- require("./../Frameworks/Foundation.jsframework/JS/JSAttributedString.js");
53
- require("./../Frameworks/Foundation.jsframework/JS/JSSpec.js");
54
- require("./../Frameworks/Foundation.jsframework/JS/JSArrayController.js");
55
- require("./../Frameworks/Foundation.jsframework/JS/JSSparseArrayController.js");
56
- require("./../Frameworks/Foundation.jsframework/JS/JSColorSpace.js");
57
- require("./../Frameworks/Foundation.jsframework/JS/JSColor.js");
58
- require("./../Frameworks/Foundation.jsframework/JS/JSGradient.js");
59
52
  require("./../Frameworks/Foundation.jsframework/JS/Huffman.js");
60
53
  require("./../Frameworks/Foundation.jsframework/JS/Deflate.js");
61
54
  require("./../Frameworks/Foundation.jsframework/JS/Adler32.js");
62
55
  require("./../Frameworks/Foundation.jsframework/JS/Zlib.js");
63
56
  require("./../Frameworks/Foundation.jsframework/JS/JSFontDescriptor.js");
64
57
  require("./../Frameworks/Foundation.jsframework/JS/JSFont.js");
58
+ require("./../Frameworks/Foundation.jsframework/JS/JSColorSpace.js");
59
+ require("./../Frameworks/Foundation.jsframework/JS/JSColor.js");
60
+ require("./../Frameworks/Foundation.jsframework/JS/JSAttributedString.js");
61
+ require("./../Frameworks/Foundation.jsframework/JS/JSSpec.js");
62
+ require("./../Frameworks/Foundation.jsframework/JS/JSArrayController.js");
63
+ require("./../Frameworks/Foundation.jsframework/JS/JSSparseArrayController.js");
64
+ require("./../Frameworks/Foundation.jsframework/JS/JSGradient.js");
65
65
  require("./../Frameworks/Foundation.jsframework/JS/JSPath.js");
66
66
  require("./../Frameworks/Foundation.jsframework/JS/JSContext.js");
67
67
  require("./../Frameworks/Foundation.jsframework/JS/JSDateFormatter.js");
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.APIKit
3
- JSBundleVersion: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -113,10 +113,15 @@ XMLSerializer.prototype = {
113
113
  str += " " + node.name;
114
114
  }
115
115
  if (node.publicId.length > 0){
116
- str += " " + node.publicId;
117
- }
118
- if (node.systemId.length > 0){
119
- str += " " + node.systemId;
116
+ value = node.publicId.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
117
+ str += ' PUBLIC "%s"'.sprintf(value);
118
+ if (node.systemId.length > 0){
119
+ value = node.systemId.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
120
+ str += ' "%s"'.sprintf(value);
121
+ }
122
+ }else if (node.systemId.length > 0){
123
+ value = node.systemId.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
124
+ str += ' SYSTEM "%s"'.sprintf(value);
120
125
  }
121
126
  str += ">\n";
122
127
  }
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.Dispatch
3
- JSBundleVersion: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.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: 2022.22.1
3
+ JSBundleVersion: 2022.23.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSBundleEnvironments:
@@ -16,6 +16,8 @@
16
16
  // #import "Javascript.js"
17
17
  // #import "JSObject.js"
18
18
  // #import "JSTextAttachment.js"
19
+ // #import "JSFont.js"
20
+ // #import "JSColor.js"
19
21
  'use strict';
20
22
 
21
23
  (function(){
@@ -40,6 +42,123 @@ JSClass("JSAttributedString", JSObject, {
40
42
  this._runs = [run];
41
43
  },
42
44
 
45
+ initFromDictionary: function(dictionary){
46
+ this._string = typeof(dictionary.string) === "string" ? dictionary.string : "";
47
+ this._runs = [];
48
+ var i, l;
49
+ var runDictionary;
50
+ var run;
51
+ var attributes;
52
+ var name;
53
+ var location = 0;
54
+ var length;
55
+ var maxLength = this._string.length;
56
+ var decodeAttribute = function(name, value){
57
+ switch (name){
58
+ case JSAttributedString.Attribute.font:
59
+ return JSAttributedString.AttributeDecoder.font(value);
60
+ case JSAttributedString.Attribute.textColor:
61
+ case JSAttributedString.Attribute.backgroundColor:
62
+ return JSAttributedString.AttributeDecoder.color(value);
63
+ case JSAttributedString.Attribute.link:
64
+ return JSAttributedString.AttributeDecoder.url(value);
65
+ case JSAttributedString.Attribute.cursor:
66
+ return JSAttributedString.AttributeDecoder.skip(value);
67
+ case JSAttributedString.Attribute.attachment:
68
+ // TODO:
69
+ return JSAttributedString.AttributeDecoder.skip(value);
70
+ case JSAttributedString.Attribute.maskCharacter:
71
+ return JSAttributedString.AttributeDecoder.string(value);
72
+ case JSAttributedString.Attribute.bold:
73
+ case JSAttributedString.Attribute.italic:
74
+ case JSAttributedString.Attribute.underline:
75
+ case JSAttributedString.Attribute.strike:
76
+ return JSAttributedString.AttributeDecoder.boolean(value);
77
+ case JSAttributedString.Attribute.lineHeight:
78
+ case JSAttributedString.Attribute.lineSpacing:
79
+ return JSAttributedString.AttributeDecoder.number(value);
80
+ case JSAttributedString.Attribute.textAlignment:
81
+ // TODO: validate
82
+ case JSAttributedString.Attribute.lineBreakMode:
83
+ // TODO: validate
84
+ default:
85
+ return value;
86
+ }
87
+
88
+ };
89
+ if (dictionary.runs instanceof Array){
90
+ for (i = 0, l = dictionary.runs.length; i < l && maxLength > 0; ++i){
91
+ runDictionary = dictionary.runs[i];
92
+ attributes = {};
93
+ length = typeof(runDictionary.length) === "number" ? Math.min(maxLength, Math.floor(runDictionary.length)) : 0;
94
+ if (length > 0 && typeof(runDictionary.attributes) === "object"){
95
+ for (name in runDictionary.attributes){
96
+ attributes[name] = decodeAttribute(name, runDictionary.attributes[name]);
97
+ }
98
+ this._runs.push(JSAttributedStringRun(JSRange(location, length), attributes));
99
+ }
100
+ location += length;
101
+ maxLength -= length;
102
+ }
103
+ }
104
+ if (maxLength > 0){
105
+ this._runs.push(JSAttributedStringRun(JSRange(location, maxLength), {}));
106
+ }
107
+ if (this._runs.length === 0){
108
+ this._runs.push(JSAttributedStringRun(JSRange(0, 0), {}));
109
+ }
110
+ },
111
+
112
+ dictionaryRepresentation: function(){
113
+ var dictionary = {
114
+ string: this._string,
115
+ runs: []
116
+ };
117
+ var i, l;
118
+ var run;
119
+ var name;
120
+ var runDictionary;
121
+ var encodeAttribute = function(name, value){
122
+ switch (name){
123
+ case JSAttributedString.Attribute.font:
124
+ return JSAttributedString.AttributeEncoder.font(value);
125
+ case JSAttributedString.Attribute.textColor:
126
+ case JSAttributedString.Attribute.backgroundColor:
127
+ return JSAttributedString.AttributeEncoder.color(value);
128
+ case JSAttributedString.Attribute.link:
129
+ return JSAttributedString.AttributeEncoder.url(value);
130
+ case JSAttributedString.Attribute.cursor:
131
+ return JSAttributedString.Attribute.Encoder.skip(value);
132
+ case JSAttributedString.Attribute.attachment:
133
+ // TODO:
134
+ return JSAttributedString.Attribute.Encoder.skip(value);
135
+ case JSAttributedString.Attribute.bold:
136
+ case JSAttributedString.Attribute.italic:
137
+ case JSAttributedString.Attribute.underline:
138
+ case JSAttributedString.Attribute.strike:
139
+ case JSAttributedString.Attribute.lineHeight:
140
+ case JSAttributedString.Attribute.lineSpacing:
141
+ case JSAttributedString.Attribute.textAlignment:
142
+ case JSAttributedString.Attribute.lineBreakMode:
143
+ case JSAttributedString.Attribute.maskCharacter:
144
+ default:
145
+ return value;
146
+ }
147
+ };
148
+ for (i = 0, l = this._runs.length; i < l; ++i){
149
+ run = this._runs[i];
150
+ runDictionary = {
151
+ length: run.range.length,
152
+ attributes: {}
153
+ };
154
+ for (name in run.attributes){
155
+ runDictionary.attributes[name] = encodeAttribute(name, run.attributes[name]);
156
+ }
157
+ dictionary.runs.push(runDictionary);
158
+ }
159
+ return dictionary;
160
+ },
161
+
43
162
  initWithAttributedString: function(attributedString, defaultAttributes){
44
163
  this._string = attributedString.string;
45
164
  this._runs = [];
@@ -294,6 +413,44 @@ JSClass("JSAttributedString", JSObject, {
294
413
  return new JSAttributedStringRunIterator(this, index);
295
414
  },
296
415
 
416
+ longestRangeOfAttributeAtIndex: function(attributeName, index){
417
+ return this.longestRangeOfAttributesAtIndex([attributeName], index);
418
+ },
419
+
420
+ longestRangeOfAttributesAtIndex: function(attributeNames, index){
421
+ var runIndex = this._runIndexForStringIndex(index);
422
+ var run = this._runs[runIndex];
423
+ var range = JSRange(run.range);
424
+ var attributes = {};
425
+ var i, l;
426
+ for (i = 0, l = attributeNames.length; i < l; ++i){
427
+ attributes[attributeNames[i]] = run.attributes[attributeNames[i]];
428
+ }
429
+ i = runIndex - 1;
430
+ while (i >= 0){
431
+ run = this._runs[i];
432
+ if (run.containsEqualAttributes(attributes)){
433
+ range.length = range.end - run.range.location;
434
+ range.location = run.range.location;
435
+ }else{
436
+ break;
437
+ }
438
+ --i;
439
+ }
440
+ i = runIndex + 1;
441
+ l = this._runs.length;
442
+ while (i < l){
443
+ run = this._runs[i];
444
+ if (run.containsEqualAttributes(attributes)){
445
+ range.length = run.range.end - range.location;
446
+ }else{
447
+ break;
448
+ }
449
+ ++i;
450
+ }
451
+ return range;
452
+ },
453
+
297
454
  // MARK: - Formatting
298
455
 
299
456
  replaceFormat: function(){
@@ -339,7 +496,7 @@ JSClass("JSAttributedString", JSObject, {
339
496
  var runA;
340
497
  for (var runIndex = lastRunIndex - 1; runIndex >= expandedRunRange.location; --runIndex){
341
498
  runA = this._runs[runIndex];
342
- if (runA.hasIdenticalAttributes(runB)){
499
+ if (runA.onlyContainsEqualAttributes(runB.attributes)){
343
500
  this._runs.splice(runIndex, 1);
344
501
  runB.range.location = runA.range.location;
345
502
  runB.range.length += runA.range.length;
@@ -358,7 +515,7 @@ JSClass("JSAttributedString", JSObject, {
358
515
  }
359
516
  var runA = this._runs[runIndex - 1];
360
517
  var runB = this._runs[runIndex];
361
- if (runA.hasIdenticalAttributes(runB)){
518
+ if (runA.onlyContainsEqualAttributes(runB.attributes)){
362
519
  this._runs.splice(runIndex, 1);
363
520
  runA.range.length += runB.range.length;
364
521
  }
@@ -443,6 +600,31 @@ JSAttributedString.Attribute = {
443
600
  link: "link"
444
601
  };
445
602
 
603
+ JSAttributedString.AttributeEncoder = {
604
+ skip: function(value){ return undefined; },
605
+ identity: function(value){ return value; },
606
+ dataBase64: function(value){ return value.base64StringRepresentation(); },
607
+ dataBase64URL: function(value){ return value.base64URLStringRepresentation(); },
608
+ dataHex: function(value){ return value.hexStringRepresentation(); },
609
+ url: function(value){ return value.encodedString; },
610
+ font: function(value){ return value.dictionaryRepresentation(); },
611
+ color: function(value){ return value.dictionaryRepresentation(); },
612
+ };
613
+
614
+ JSAttributedString.AttributeDecoder = {
615
+ skip: function(value){ return undefined; },
616
+ identity: function(value){ return value; },
617
+ dataBase64: function(value){ return value.dataByDecodingBase64(); },
618
+ dataBase64URL: function(value){ return value.dataByDecodingBase64URL(); },
619
+ dataHex: function(value){ return value.dataByDecodingHex(); },
620
+ url: function(value){ return JSURL.initWithString(value); },
621
+ font: function(value){ return JSFont.initFromDictionary(value); },
622
+ color: function(value){ return JSColor.initFromDictionary(value); },
623
+ boolean: function(value){ return typeof(value) === "boolean" ? value : undefined; },
624
+ number: function(value){ return typeof(value) === "number" ? value : undefined; },
625
+ string: function(value){ return typeof(value) === "string" ? value : undefined; },
626
+ };
627
+
446
628
  JSAttributedString.SpecialCharacter = {
447
629
  attachment: 0xFFFC,
448
630
  attachmentUTF16: "\uFFFC"
@@ -471,35 +653,46 @@ function JSAttributedStringRun(range, attributes){
471
653
 
472
654
  JSAttributedStringRun.prototype = {
473
655
 
474
- hasIdenticalAttributes: function(other){
656
+ containsEqualAttributes: function(attributes){
475
657
  var attribute;
476
658
  var a, b;
477
- for (attribute in this.attributes){
478
- if (attribute in other.attributes){
479
- a = this.attributes[attribute];
480
- b = other.attributes[attribute];
481
- if (a.isEqual){
482
- if (!a.isEqual(b)){
483
- return false;
484
- }
485
- }else if (b.isEqual){
486
- if (!b.isEqual(a)){
487
- return false;
488
- }
489
- }else if (a != b){
659
+ for (attribute in attributes){
660
+ a = attributes[attribute];
661
+ b = this.attributes[attribute];
662
+ if (a === undefined){
663
+ if (!!b){
490
664
  return false;
491
665
  }
492
- }else{
666
+ }else if (a === null){
667
+ if (b !== null && b !== undefined){
668
+ return false;
669
+ }
670
+ }else if (a.isEqual){
671
+ if (b === null || b === undefined){
672
+ return false;
673
+ }
674
+ if (!a.isEqual(b)){
675
+ return false;
676
+ }
677
+ }else if (a != b){
493
678
  return false;
494
679
  }
495
680
  }
496
- for (attribute in other.attributes){
497
- if (!(attribute in this.attributes)){
681
+ return true;
682
+ },
683
+
684
+ onlyContainsEqualAttributes: function(attributes){
685
+ if (!this.containsEqualAttributes(attributes)){
686
+ return false;
687
+ }
688
+ var attribute;
689
+ for (attribute in this.attributes){
690
+ if (!(attribute in attributes)){
498
691
  return false;
499
692
  }
500
693
  }
501
694
  return true;
502
- }
695
+ },
503
696
 
504
697
  };
505
698