@breakside/jskit 2022.23.0 → 2022.25.1

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 (94) 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/JSAttributedString.js +139 -10
  7. package/Frameworks/Foundation.jsframework/JS/JSFileManager+Node.js +2 -2
  8. package/Frameworks/Foundation.jsframework/JS/JSParagraphStyle.js +71 -0
  9. package/Frameworks/Foundation.jsframework/JS/JSTextAttachment.js +1 -1
  10. package/Frameworks/Foundation.jsframework/JS/JSTextContainer.js +56 -29
  11. package/Frameworks/Foundation.jsframework/JS/JSTextFrame.js +2 -32
  12. package/Frameworks/Foundation.jsframework/JS/JSTextFramesetter.js +125 -22
  13. package/Frameworks/Foundation.jsframework/JS/JSTextLayoutManager.js +11 -3
  14. package/Frameworks/Foundation.jsframework/JS/JSTextLine.js +4 -3
  15. package/Frameworks/Foundation.jsframework/JS/JSTextRun.js +5 -3
  16. package/Frameworks/Foundation.jsframework/JS/JSTextTypesetter.js +41 -25
  17. package/Frameworks/Foundation.jsframework/JS/JSURL.js +3 -0
  18. package/Frameworks/Foundation.jsframework/JS/JSZip.js +97 -76
  19. package/Frameworks/Foundation.jsframework/JS/String+JS.js +35 -0
  20. package/Frameworks/Foundation.jsframework/JS/UnicodeChar.js +8 -0
  21. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  22. package/Frameworks/Foundation.jsframework/sources.json +3 -0
  23. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  24. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  25. package/Info.json +2 -2
  26. package/Node/io.breakside.jskit-bundle.js +2 -2
  27. package/Node/jskit +1 -0
  28. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  29. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  30. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  31. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  32. package/Root/Frameworks/ChartKit/CHAxis.js +5 -5
  33. package/Root/Frameworks/ChartKit/CHPieChart.js +1 -1
  34. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  35. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  36. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  37. package/Root/Frameworks/DOM/Info.yaml +1 -1
  38. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  39. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  40. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  41. package/Root/Frameworks/Foundation/JSAttributedString.js +139 -10
  42. package/Root/Frameworks/Foundation/JSFileManager+Node.js +2 -2
  43. package/Root/Frameworks/Foundation/JSParagraphStyle.js +71 -0
  44. package/Root/Frameworks/Foundation/JSTextAttachment.js +1 -1
  45. package/Root/Frameworks/Foundation/JSTextContainer.js +56 -29
  46. package/Root/Frameworks/Foundation/JSTextFrame.js +2 -32
  47. package/Root/Frameworks/Foundation/JSTextFramesetter.js +125 -22
  48. package/Root/Frameworks/Foundation/JSTextLayoutManager.js +11 -3
  49. package/Root/Frameworks/Foundation/JSTextLine.js +4 -3
  50. package/Root/Frameworks/Foundation/JSTextRun.js +5 -3
  51. package/Root/Frameworks/Foundation/JSTextTypesetter.js +41 -25
  52. package/Root/Frameworks/Foundation/JSURL.js +3 -0
  53. package/Root/Frameworks/Foundation/JSZip.js +97 -76
  54. package/Root/Frameworks/Foundation/String+JS.js +35 -0
  55. package/Root/Frameworks/Foundation/UnicodeChar.js +8 -0
  56. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  57. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  58. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  59. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  60. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  61. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  62. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  63. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  64. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  65. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  66. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  67. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  68. package/Root/Frameworks/UIKit/UIBasicAnimation.js +1 -1
  69. package/Root/Frameworks/UIKit/UIHTMLApplication.js +53 -15
  70. package/Root/Frameworks/UIKit/UIHTMLContentEditableTextInputManager.js +36 -0
  71. package/Root/Frameworks/UIKit/UIHTMLDisplayServer.js +1 -1
  72. package/Root/Frameworks/UIKit/UIHTMLTextField.js +1 -1
  73. package/Root/Frameworks/UIKit/UIHTMLTextFrame.js +19 -91
  74. package/Root/Frameworks/UIKit/UIHTMLTextFramesetter.js +52 -9
  75. package/Root/Frameworks/UIKit/UIHTMLTextLine.js +95 -30
  76. package/Root/Frameworks/UIKit/UIHTMLTextRun.js +22 -9
  77. package/Root/Frameworks/UIKit/UIHTMLTextTypesetter.js +334 -459
  78. package/Root/Frameworks/UIKit/UIHTMLWindowServer.js +16 -0
  79. package/Root/Frameworks/UIKit/UIImageLayer.js +14 -7
  80. package/Root/Frameworks/UIKit/UILabel.js +2 -0
  81. package/Root/Frameworks/UIKit/UIScrollView.js +12 -0
  82. package/Root/Frameworks/UIKit/UISegmentedControl.js +6 -6
  83. package/Root/Frameworks/UIKit/UIState.js +8 -1
  84. package/Root/Frameworks/UIKit/UITextAttachmentView.js +1 -1
  85. package/Root/Frameworks/UIKit/UITextEditor.js +210 -25
  86. package/Root/Frameworks/UIKit/UITextField.js +58 -31
  87. package/Root/Frameworks/UIKit/UITextInput.js +3 -1
  88. package/Root/Frameworks/UIKit/UITextInputManager.js +2 -2
  89. package/Root/Frameworks/UIKit/UITextLayer.js +39 -26
  90. package/Root/Frameworks/UIKit/UITokenField.js +4 -4
  91. package/Root/Frameworks/UIKit/UIView.js +3 -1
  92. package/Root/Frameworks/UIKit/UIWindow.js +16 -3
  93. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  94. package/package.json +1 -1
@@ -18,15 +18,18 @@
18
18
  // #import "JSTextTypesetter.js"
19
19
  // #import "JSTextFrame.js"
20
20
  // #import "JSAttributedString.js"
21
+ // #import "JSParagraphStyle.js"
21
22
  'use strict';
22
23
 
23
24
  (function(){
24
25
 
26
+ var logger = JSLog("foundation", "text");
27
+
25
28
  JSClass("JSTextFramesetter", JSObject, {
26
29
 
27
30
  typesetter: JSReadOnlyProperty('_typesetter', null),
28
31
  attributedString: JSDynamicProperty(),
29
- attributes: null,
32
+ defaultParagraphStyle: JSDynamicProperty("_defaultParagraphStyle", null),
30
33
 
31
34
  init: function(){
32
35
  this.initWithTypesetter(JSTextTypesetter.init());
@@ -34,7 +37,7 @@ JSClass("JSTextFramesetter", JSObject, {
34
37
 
35
38
  initWithTypesetter: function(typesetter){
36
39
  this._typesetter = typesetter;
37
- this.attributes = Object.create(JSTextParagraphAttributes);
40
+ this._defaultParagraphStyle = JSParagraphStyle.init();
38
41
  },
39
42
 
40
43
  getAttributedString: function(){
@@ -45,8 +48,8 @@ JSClass("JSTextFramesetter", JSObject, {
45
48
  this._typesetter.attributedString = attributedString;
46
49
  },
47
50
 
48
- constructFrame: function(lines, size, attributes){
49
- return JSTextFrame.initWithLines(lines, size, attributes);
51
+ constructFrame: function(lines, size){
52
+ return JSTextFrame.initWithLines(lines, size);
50
53
  },
51
54
 
52
55
  createFrame: function(size, range, maximumLines){
@@ -61,35 +64,96 @@ JSClass("JSTextFramesetter", JSObject, {
61
64
  var widthLimit = size.width || Number.MAX_VALUE;
62
65
  var heightLimit = size.height || Number.MAX_VALUE;
63
66
  var lineLimit = maximumLines || Number.MAX_VALUE;
64
- var spacing = 0;
67
+ var lineWidthLimit;
68
+ var attributes = this._typesetter.resolveAttributes(this._typesetter.attributedString.attributesAtIndex(remianingRange.location));
69
+ var paragraphStyle = this._defaultParagraphStyle.styleWithAttributes(attributes);
70
+ var characterIterator = this._typesetter.attributedString.string.userPerceivedCharacterIterator(remianingRange.location);
71
+ characterIterator.decrement();
65
72
  do{
66
- lineRange = this._typesetter.suggestLineBreak(widthLimit, remianingRange, this.effectiveLineBreakMode(this.attributes.lineBreakMode, lines.length + 1, lineLimit));
73
+ lineWidthLimit = widthLimit;
74
+ if (lineWidthLimit < Number.MAX_VALUE){
75
+ if (characterIterator.isParagraphBreak){
76
+ lineWidthLimit -= paragraphStyle.firstLineHeadIndent;
77
+ }else{
78
+ lineWidthLimit -= paragraphStyle.headIndent;
79
+ }
80
+ lineWidthLimit -= paragraphStyle.tailIndent;
81
+ }
82
+ lineRange = this._typesetter.suggestLineBreak(lineWidthLimit, remianingRange, this.effectiveLineBreakMode(paragraphStyle.lineBreakMode, lines.length + 1, lineLimit));
67
83
  line = this._typesetter.createLine(lineRange);
68
84
  line.origin.y = y;
85
+ if (paragraphStyle.lineHeightMultiple > 0){
86
+ line.size.height *= paragraphStyle.lineHeightMultiple;
87
+ }
88
+ if (line.size.height < paragraphStyle.minimumLineHeight){
89
+ line.size.height = paragraphStyle.minimumLineHeight;
90
+ }
69
91
  y += line.size.height;
70
92
  if (y <= heightLimit){
71
- spacing = Math.min(heightLimit - y, (this.attributes.lineSpacing - 1.0) * line.size.height);
72
- line.size.height += Math.max(0, spacing);
73
- y += spacing;
74
- lines.push(line);
75
93
  remianingRange.advance(line.range.length);
76
- }else{
77
- spacing = 0;
94
+ characterIterator = this._typesetter.attributedString.string.userPerceivedCharacterIterator(remianingRange.location);
95
+ characterIterator.decrement();
96
+ if (characterIterator.isParagraphBreak){
97
+ y += Math.max(0, Math.min(heightLimit - y, paragraphStyle.paragraphSpacing));
98
+ attributes = this._typesetter.resolveAttributes(this._typesetter.attributedString.attributesAtIndex(remianingRange.location));
99
+ paragraphStyle = this._defaultParagraphStyle.styleWithAttributes(attributes);
100
+ y += Math.max(0, Math.min(heightLimit - y, paragraphStyle.beforeParagraphSpacing));
101
+ }
102
+ y += Math.max(0, Math.min(heightLimit - y, paragraphStyle.lineSpacing));
103
+ lines.push(line);
78
104
  }
79
105
  } while (lineRange.length > 0 && remianingRange.length > 0 && y < heightLimit && lines.length < lineLimit);
80
106
 
81
- if (this.attributes.lineBreakMode == JSLineBreakMode.truncateTail && lines.length > 0){
107
+ if (paragraphStyle.lineBreakMode == JSLineBreakMode.truncateTail && lines.length > 0){
82
108
  line = lines.pop();
83
109
  if (remianingRange.length > 0 && lineLimit > lines.length + 1){
84
110
  // we got truncated because of height. Re-run the last line so it
85
111
  // gets broken according to truncation rules rather than word break
86
- lineRange = this._typesetter.suggestLineBreak(widthLimit, JSRange(line.range.location, line.range.length + remianingRange.length), this.attributes.lineBreakMode);
112
+ lineRange = this._typesetter.suggestLineBreak(widthLimit, JSRange(line.range.location, line.range.length + remianingRange.length), paragraphStyle.lineBreakMode);
87
113
  line = this._typesetter.createLine(lineRange);
88
114
  }
89
115
  var truncated = line.truncatedLine(widthLimit);
90
116
  lines.push(truncated);
91
117
  }
92
- return this.constructFrame(lines, size, this.attributes);
118
+ var frame = this.constructFrame(lines, size);
119
+ this._alignLinesInFrame(frame);
120
+ return frame;
121
+ },
122
+
123
+ _resizeFrame: function(frame, size){
124
+ if (size.width < frame._usedSize.width || size.height < frame._usedSize.height){
125
+ throw new Error("Cannot adjust text frame to smaller than its used size");
126
+ }
127
+ frame._size = JSSize(size);
128
+ this._alignLinesInFrame(frame);
129
+ },
130
+
131
+ _alignLinesInFrame: function(frame){
132
+ var i, l;
133
+ var line;
134
+ var attributes;
135
+ var headIndent = 0;
136
+ var characterIterator;
137
+ var paragraphStyle;
138
+ for (i = 0, l = frame.lines.length; i < l; ++i){
139
+ line = frame.lines[i];
140
+ attributes = this._typesetter.resolveAttributes(this._typesetter.attributedString.attributesAtIndex(line.range.location));
141
+ paragraphStyle = this._defaultParagraphStyle.styleWithAttributes(attributes);
142
+ characterIterator = this._typesetter.attributedString.string.userPerceivedCharacterIterator(line.range.location);
143
+ characterIterator.decrement();
144
+ if (characterIterator.isParagraphBreak){
145
+ headIndent = paragraphStyle.firstLineHeadIndent;
146
+ }else{
147
+ headIndent = paragraphStyle.headIndent;
148
+ }
149
+ if (paragraphStyle.textAlignment === JSTextAlignment.left){
150
+ line.origin.x = headIndent;
151
+ }else if (paragraphStyle.textAlignment === JSTextAlignment.center){
152
+ line.origin.x = headIndent + (frame._size.width - headIndent - paragraphStyle.tailIndent - line.size.width + line.trailingWhitespaceWidth) / 2.0;
153
+ }else if (paragraphStyle.textAlignment === JSTextAlignment.right){
154
+ line.origin.x = (frame._size.width - line.size.width + line.trailingWhitespaceWidth) - paragraphStyle.tailIndent;
155
+ }
156
+ }
93
157
  },
94
158
 
95
159
  effectiveLineBreakMode: function(frameLineBreakMode, lineNumber, lineLimit){
@@ -104,15 +168,54 @@ JSClass("JSTextFramesetter", JSObject, {
104
168
  case JSLineBreakMode.characterWrap:
105
169
  return frameLineBreakMode;
106
170
  }
171
+ },
172
+
173
+ // Deprecated:
174
+
175
+ attributes: JSReadOnlyProperty("_attributes", null),
176
+
177
+ getAttributes: function(){
178
+ logger.warn("JSTextFramesetter.attributes is deprecated");
179
+ if (this._attributes === null){
180
+ var framesetter = this;
181
+ this._attributes = Object.create({}, {
182
+ lineBreakMode: {
183
+ get: function(){
184
+ return framesetter._defaultParagraphStyle.lineBreakMode;
185
+ },
186
+ set: function(lineBreakMode){
187
+ framesetter._defaultParagraphStyle.lineBreakMode = lineBreakMode;
188
+ }
189
+ },
190
+ textAlignment: {
191
+ get: function(){
192
+ return framesetter._defaultParagraphStyle.textAlignment;
193
+ },
194
+ set: function(textAlignment){
195
+ framesetter._defaultParagraphStyle.textAlignment = textAlignment;
196
+ }
197
+ },
198
+ lineSpacing: {
199
+ get: function(){
200
+ return framesetter._defaultParagraphStyle.lineHeightMultiple > 0 ? framesetter._defaultParagraphStyle.lineHeightMultiple : 1.0;
201
+ },
202
+ set: function(lineSpacing){
203
+ framesetter._defaultParagraphStyle.lineHeightMultiple = lineSpacing;
204
+ }
205
+ },
206
+ minimumLineHeight: {
207
+ get: function(){
208
+ return framesetter._defaultParagraphStyle.minimumLineHeight;
209
+ },
210
+ set: function(minimumLineHeight){
211
+ framesetter._defaultParagraphStyle.minimumLineHeight = minimumLineHeight;
212
+ }
213
+ }
214
+ });
215
+ }
216
+ return this._attributes;
107
217
  }
108
218
 
109
219
  });
110
220
 
111
- var JSTextParagraphAttributes = {
112
- minimumLineHeight: 0,
113
- lineSpacing: 1.0,
114
- textAlignment: JSTextAlignment.left,
115
- lineBreakMode: JSLineBreakMode.truncateTail
116
- };
117
-
118
221
  })();
@@ -17,6 +17,7 @@
17
17
  // #import "CoreTypes.js"
18
18
  // #import "JSAttributedString.js"
19
19
  // #import "JSColor.js"
20
+ // #import "JSParagraphStyle.js"
20
21
  'use strict';
21
22
 
22
23
  (function(){
@@ -26,6 +27,7 @@ JSClass("JSTextLayoutManager", JSObject, {
26
27
  textStorage: JSDynamicProperty('_textStorage', null),
27
28
  defaultFont: JSDynamicProperty('_defaultFont', null),
28
29
  defaultTextColor: JSDynamicProperty('_defaultTextColor', null),
30
+ defaultParagraphStyle: JSDynamicProperty("_defaultParagraphStyle", null),
29
31
  includeEmptyFinalLine: false,
30
32
 
31
33
  delegate: null,
@@ -37,6 +39,7 @@ JSClass("JSTextLayoutManager", JSObject, {
37
39
  init: function(){
38
40
  this._textContainers = [];
39
41
  this._defaultTextColor = JSColor.black;
42
+ this._defaultParagraphStyle = JSParagraphStyle.init();
40
43
  },
41
44
 
42
45
  // MARK: - Managing Containers
@@ -106,6 +109,11 @@ JSClass("JSTextLayoutManager", JSObject, {
106
109
  this.setNeedsLayout();
107
110
  },
108
111
 
112
+ setDefaultParagraphStyle: function(paragraphStyle){
113
+ this._defaultParagraphStyle = paragraphStyle;
114
+ this.setNeedsLayout();
115
+ },
116
+
109
117
  // MARK: - Private Helpers for finalizing runs
110
118
 
111
119
  effectiveAttributedString: function(){
@@ -353,13 +361,13 @@ JSClass("JSTextLayoutManager", JSObject, {
353
361
  rightX += 8;
354
362
  }
355
363
  }else{
356
- characterRect = line.rectForCharacterAtIndex(remainingRange.end - line.range.location);
364
+ characterRect = line.rectForCharacterAtIndex(remainingRange.end);
357
365
  rightX = characterRect.origin.x;
358
366
  }
359
367
  if (remainingRange.location === line.range.location){
360
368
  leftX = 0;
361
369
  }else{
362
- characterRect = line.rectForCharacterAtIndex(remainingRange.location - line.range.location);
370
+ characterRect = line.rectForCharacterAtIndex(remainingRange.location);
363
371
  leftX = characterRect.origin.x;
364
372
  }
365
373
  rect = JSRect(line.origin.x + leftX, line.origin.y, rightX - leftX, line.size.height);
@@ -406,7 +414,7 @@ var JSTextLayoutManagerLineEnumerator = function(layoutManager, index){
406
414
  this._containerIndex = layoutManager._textContainerIndexForCharacterAtIndex(index);
407
415
  var container = this.container;
408
416
  if (container !== null){
409
- this._lineIndex = container.lineIndexForCharacterAtIndex(index - container.range.location);
417
+ this._lineIndex = container.lineIndexForCharacterAtIndex(index);
410
418
  }else{
411
419
  this._lineIndex = 0;
412
420
  }
@@ -72,6 +72,7 @@ JSClass("JSTextLine", JSObject, {
72
72
  }
73
73
  var height = this._size.height;
74
74
  for (i = 0, l = this._runs.length; i < l; ++i){
75
+ run = this._runs[i];
75
76
  if (run.size.height - run.baseline + baseline > height){
76
77
  height = run.size.height - run.baseline + baseline;
77
78
  }
@@ -105,7 +106,7 @@ JSClass("JSTextLine", JSObject, {
105
106
  rectForCharacterAtIndex: function(index){
106
107
  var run = this.runForCharacterAtIndex(index);
107
108
  if (run !== null){
108
- var rect = run.rectForCharacterAtIndex(index - (run.range.location - this.range.location));
109
+ var rect = run.rectForCharacterAtIndex(index);
109
110
  rect.origin.x += run.origin.x;
110
111
  rect.origin.y += run.origin.y;
111
112
  return rect;
@@ -132,7 +133,7 @@ JSClass("JSTextLine", JSObject, {
132
133
  while (min < max){
133
134
  mid = Math.floor(min + (max - min) / 2);
134
135
  run = this._runs[mid];
135
- i = run.range.location - this.range.location;
136
+ i = run.range.location;
136
137
  l = run.range.length;
137
138
  if (index < i){
138
139
  max = mid;
@@ -178,7 +179,7 @@ JSClass("JSTextLine", JSObject, {
178
179
  },
179
180
 
180
181
  copy: function(){
181
- var line = JSTextLine.init();
182
+ var line = this.$class.init();
182
183
  line._origin = JSPoint(this._origin);
183
184
  line._size = JSSize(this._size);
184
185
  line._trailingWhitespaceWidth = this._trailingWhitespaceWidth;
@@ -39,7 +39,7 @@ JSClass("JSTextRun", JSObject, {
39
39
  this.attachment = attributes[JSAttributedString.Attribute.attachment] || null;
40
40
  if (this.attachment){
41
41
  this.baseline = this.attachment.baselineAdjustment;
42
- this._size = JSSize(this.attachment.size.width, this.attachment.size.height - Math.min(0, this.attachment.baselineAdjustment));
42
+ this._size = JSSize(this.attachment.size);
43
43
  }else{
44
44
  this.font = font;
45
45
  this.baseline = -font.descender;
@@ -113,7 +113,7 @@ JSClass("JSTextRun", JSObject, {
113
113
  var glyph = null;
114
114
  var width;
115
115
  var characterLength;
116
- var runningIndex = 0;
116
+ var runningIndex = this.range.location;
117
117
  for (var i = 0, l = this.glyphs.length; runningIndex < index && i < l; ++i){
118
118
  glyph = this.glyphs[i];
119
119
  width = this.font.widthOfGlyph(glyph);
@@ -130,11 +130,13 @@ JSClass("JSTextRun", JSObject, {
130
130
  },
131
131
 
132
132
  copy: function(){
133
- var run = JSTextRun.init();
133
+ var run = this.$class.init();
134
134
  run._origin = JSPoint(this._origin);
135
135
  run._size = JSSize(this._size);
136
+ run.baseline = this.baseline;
136
137
  run._range = JSRange(this._range);
137
138
  run.attributes = this.attributes;
139
+ run.attachment = this.attachment;
138
140
  run.font = this.font;
139
141
  run.glyphs = [];
140
142
  run.glyphCharacterLengths = [];
@@ -14,6 +14,7 @@
14
14
  // limitations under the License.
15
15
 
16
16
  // #import "JSObject.js"
17
+ // #import "JSProtocol.js"
17
18
  // #import "CoreTypes.js"
18
19
  // #import "JSAttributedString.js"
19
20
  // #import "JSFont.js"
@@ -26,6 +27,12 @@
26
27
 
27
28
  var logger = JSLog("foundation", "text");
28
29
 
30
+ JSProtocol("JSTextTypesetterDelegate", JSProtocol, {
31
+
32
+ typesetterAttributesForStringAttributes: function(typesetter, stringAttributes){}
33
+
34
+ });
35
+
29
36
  JSClass("JSTextTypesetter", JSObject, {
30
37
 
31
38
  attributedString: JSDynamicProperty('_attributedString'),
@@ -68,7 +75,8 @@ JSClass("JSTextTypesetter", JSObject, {
68
75
 
69
76
  _createEmptyLine: function(range){
70
77
  var attributes = this._attributedString.attributesAtIndex(range.location);
71
- var font = JSTextTypesetter.FontFromAttributes(attributes);
78
+ attributes = this.resolveAttributes(attributes);
79
+ var font = attributes.font;
72
80
  return JSTextLine.initWithHeight(font.lineHeight, -font.descender, range.location);
73
81
  },
74
82
 
@@ -129,16 +137,18 @@ JSClass("JSTextTypesetter", JSObject, {
129
137
 
130
138
  var iterator = this._attributedString.string.userPerceivedCharacterIterator(range.location);
131
139
  var runIterator = this._attributedString.runIterator(range.location);
132
- var initialLineAttributes = runIterator.attributes;
133
140
  var codeIterator;
134
141
  var attachment;
142
+ var attributes;
135
143
  // TODO: support mask character (only important if we need to rasterize a password field, which isn't a major use case)
136
144
  // Create run descriptors that at fill the line, maybe going a bit over
137
145
  do {
138
146
 
147
+ attributes = this.resolveAttributes(runIterator.attributes);
148
+
139
149
  // If we're at the start of a new run, make a new run descriptor
140
150
  if (runDescriptor === null){
141
- runDescriptor = JSTextTypesetterRunDescriptor(remainingRange.location, runIterator.attributes);
151
+ runDescriptor = JSTextTypesetterRunDescriptor(remainingRange.location, attributes);
142
152
  runDescriptors.push(runDescriptor);
143
153
  preferredFont = runDescriptor.font;
144
154
  }
@@ -146,9 +156,10 @@ JSClass("JSTextTypesetter", JSObject, {
146
156
  // Check some properties of this character
147
157
  newline = iterator.isMandatoryLineBreak;
148
158
  printable = !newline && !iterator.isWhiteSpace;
159
+ attachment = runIterator.attachment;
149
160
 
150
161
  // Adjust the run's font if needed
151
- if (runIterator.range.length == 1 && iterator.firstCharacter.code == JSAttributedString.SpecialCharacter.attachment){
162
+ if (attachment !== null){
152
163
  // 1. Attachment font:
153
164
  // Attachment runs use a special "attachment font" that can draw the attachment rather
154
165
  // than draw a glyph. It's perhaps a little strange to call an attachment a font, but
@@ -156,7 +167,6 @@ JSClass("JSTextTypesetter", JSObject, {
156
167
  // here is actually a specially drawn attachment. The attachment font has a single glyph
157
168
  // that corresponds to the 0xFFFC attachment character code, whose width matches the
158
169
  // attachment's width
159
- attachment = runIterator.attributes[JSAttributedString.Attribute.attachment];
160
170
  attachment.layout(preferredFont, width);
161
171
  usedWidth += attachment.size.width;
162
172
  runDescriptor.glyphs.push(0);
@@ -169,7 +179,7 @@ JSClass("JSTextTypesetter", JSObject, {
169
179
  // If our preferred font has a glyph for the character, but we're in a run
170
180
  // that is using a fallback font, create a new run, switching back to the preferred font
171
181
  if (runDescriptor.font !== preferredFont){
172
- runDescriptor = JSTextTypesetterRunDescriptor(remainingRange.location, runIterator.attributes);
182
+ runDescriptor = JSTextTypesetterRunDescriptor(remainingRange.location, attributes);
173
183
  runDescriptors.push(runDescriptor);
174
184
  }
175
185
  }else{
@@ -179,7 +189,7 @@ JSClass("JSTextTypesetter", JSObject, {
179
189
  // If the fallback doesn't match our run, adjust the run or create a new run depending on if it's emtpy
180
190
  if (fallbackFont !== runDescriptor.font){
181
191
  if (runDescriptor.length > 0){
182
- runDescriptor = JSTextTypesetterRunDescriptor(remainingRange.location, runIterator.attributes);
192
+ runDescriptor = JSTextTypesetterRunDescriptor(remainingRange.location, attributes);
183
193
  runDescriptors.push(runDescriptor);
184
194
  }
185
195
  runDescriptor.font = fallbackFont;
@@ -317,7 +327,27 @@ JSClass("JSTextTypesetter", JSObject, {
317
327
  if (ranges.length === 0){
318
328
  ranges.push(JSTextTypesetterPrintableRange(location, 0, 0));
319
329
  }
320
- }
330
+ },
331
+
332
+ delegate: null,
333
+
334
+ resolveAttributes: function(attributes){
335
+ if (attributes.font){
336
+ if (attributes.bold || attributes.italic){
337
+ attributes = JSCopy(attributes);
338
+ if (attributes.bold){
339
+ attributes.font = attributes.font.fontWithWeight(JSFont.Weight.bold);
340
+ }
341
+ if (attributes.italic){
342
+ attributes.font = attributes.font.fontWithStyle(JSFont.Style.italic);
343
+ }
344
+ }
345
+ }
346
+ if (this.delegate && this.delegate.typesetterAttributesForStringAttributes){
347
+ attributes = this.delegate.typesetterAttributesForStringAttributes(this, attributes);
348
+ }
349
+ return attributes;
350
+ },
321
351
 
322
352
  });
323
353
 
@@ -329,8 +359,8 @@ var JSTextTypesetterRunDescriptor = function(location, attributes){
329
359
  this.location = location.location;
330
360
  this.attributes = location.attributes;
331
361
  this.length = location.length;
332
- this.glyphs = location.glyphs;
333
- this.glyphCharacterLengths = location.glyphCharacterLengths;
362
+ this.glyphs = JSCopy(location.glyphs);
363
+ this.glyphCharacterLengths = JSCopy(location.glyphCharacterLengths);
334
364
  this.font = location.font;
335
365
  }else{
336
366
  this.location = location;
@@ -338,7 +368,7 @@ var JSTextTypesetterRunDescriptor = function(location, attributes){
338
368
  this.length = 0;
339
369
  this.glyphs = [];
340
370
  this.glyphCharacterLengths = [];
341
- this.font = JSTextTypesetter.FontFromAttributes(attributes);
371
+ this.font = attributes.font || null;
342
372
  }
343
373
  };
344
374
 
@@ -357,18 +387,4 @@ var JSTextTypesetterPrintableRange = function(location, length, width){
357
387
  }
358
388
  };
359
389
 
360
- JSTextTypesetter.FontFromAttributes = function(attributes){
361
- var font = attributes[JSAttributedString.Attribute.font];
362
- if (!font){
363
- return null;
364
- }
365
- if (attributes[JSAttributedString.Attribute.bold]){
366
- font = font.fontWithWeight(JSFont.Weight.bold);
367
- }
368
- if (attributes[JSAttributedString.Attribute.italic]){
369
- font = font.fontWithStyle(JSFont.Style.italic);
370
- }
371
- return font;
372
- };
373
-
374
390
  })();
@@ -523,6 +523,9 @@ JSClass("JSURL", JSObject, {
523
523
  },
524
524
 
525
525
  isEqual: function(url){
526
+ if (url === null || url === undefined){
527
+ return false;
528
+ }
526
529
  if (this._scheme !== url._scheme){
527
530
  return false;
528
531
  }