@breakside/jskit 2022.22.2 → 2022.24.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 (98) 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 +347 -25
  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/JSParagraphStyle.js +71 -0
  14. package/Frameworks/Foundation.jsframework/JS/JSTextAttachment.js +1 -1
  15. package/Frameworks/Foundation.jsframework/JS/JSTextContainer.js +56 -29
  16. package/Frameworks/Foundation.jsframework/JS/JSTextFrame.js +2 -32
  17. package/Frameworks/Foundation.jsframework/JS/JSTextFramesetter.js +125 -22
  18. package/Frameworks/Foundation.jsframework/JS/JSTextLayoutManager.js +11 -3
  19. package/Frameworks/Foundation.jsframework/JS/JSTextLine.js +4 -3
  20. package/Frameworks/Foundation.jsframework/JS/JSTextRun.js +5 -3
  21. package/Frameworks/Foundation.jsframework/JS/JSTextTypesetter.js +41 -25
  22. package/Frameworks/Foundation.jsframework/JS/String+JS.js +69 -0
  23. package/Frameworks/Foundation.jsframework/JS/UnicodeChar.js +8 -0
  24. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  25. package/Frameworks/Foundation.jsframework/sources.json +170 -8
  26. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  27. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  28. package/Frameworks/SecurityKit.jsframework/sources.json +43 -2
  29. package/Info.json +2 -2
  30. package/Node/Builder.js +20 -9
  31. package/Node/FrameworkBuilder.js +22 -2
  32. package/Node/Project.js +22 -2
  33. package/Node/io.breakside.jskit-bundle.js +2 -2
  34. package/Node/jskit +8 -7
  35. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  36. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  37. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  38. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  39. package/Root/Frameworks/ChartKit/CHAxis.js +5 -5
  40. package/Root/Frameworks/ChartKit/CHPieChart.js +1 -1
  41. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  42. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  43. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  44. package/Root/Frameworks/DOM/Info.yaml +1 -1
  45. package/Root/Frameworks/DOM/XMLSerializer.js +9 -4
  46. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  47. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  48. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  49. package/Root/Frameworks/Foundation/JSAttributedString.js +347 -25
  50. package/Root/Frameworks/Foundation/JSFont.js +39 -15
  51. package/Root/Frameworks/Foundation/JSFontDescriptor+HTML.js +8 -0
  52. package/Root/Frameworks/Foundation/JSFontDescriptor.js +105 -27
  53. package/Root/Frameworks/Foundation/JSParagraphStyle.js +71 -0
  54. package/Root/Frameworks/Foundation/JSTextAttachment.js +1 -1
  55. package/Root/Frameworks/Foundation/JSTextContainer.js +56 -29
  56. package/Root/Frameworks/Foundation/JSTextFrame.js +2 -32
  57. package/Root/Frameworks/Foundation/JSTextFramesetter.js +125 -22
  58. package/Root/Frameworks/Foundation/JSTextLayoutManager.js +11 -3
  59. package/Root/Frameworks/Foundation/JSTextLine.js +4 -3
  60. package/Root/Frameworks/Foundation/JSTextRun.js +5 -3
  61. package/Root/Frameworks/Foundation/JSTextTypesetter.js +41 -25
  62. package/Root/Frameworks/Foundation/String+JS.js +69 -0
  63. package/Root/Frameworks/Foundation/UnicodeChar.js +8 -0
  64. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  65. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  66. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  67. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  68. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  69. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  70. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  71. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  72. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  73. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  74. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  75. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  76. package/Root/Frameworks/UIKit/UIHTMLContentEditableTextInputManager.js +36 -0
  77. package/Root/Frameworks/UIKit/UIHTMLDisplayServer.js +1 -1
  78. package/Root/Frameworks/UIKit/UIHTMLTextField.js +1 -1
  79. package/Root/Frameworks/UIKit/UIHTMLTextFrame.js +19 -91
  80. package/Root/Frameworks/UIKit/UIHTMLTextFramesetter.js +52 -9
  81. package/Root/Frameworks/UIKit/UIHTMLTextLine.js +95 -30
  82. package/Root/Frameworks/UIKit/UIHTMLTextRun.js +22 -9
  83. package/Root/Frameworks/UIKit/UIHTMLTextTypesetter.js +334 -459
  84. package/Root/Frameworks/UIKit/UIHTMLWindowServer.js +16 -0
  85. package/Root/Frameworks/UIKit/UILabel.js +43 -10
  86. package/Root/Frameworks/UIKit/UIScrollView.js +12 -0
  87. package/Root/Frameworks/UIKit/UISegmentedControl.js +6 -6
  88. package/Root/Frameworks/UIKit/UITextAttachmentView.js +1 -1
  89. package/Root/Frameworks/UIKit/UITextEditor.js +210 -25
  90. package/Root/Frameworks/UIKit/UITextField.js +58 -31
  91. package/Root/Frameworks/UIKit/UITextInput.js +3 -1
  92. package/Root/Frameworks/UIKit/UITextInputManager.js +2 -2
  93. package/Root/Frameworks/UIKit/UITextLayer.js +39 -26
  94. package/Root/Frameworks/UIKit/UITokenField.js +4 -4
  95. package/Root/Frameworks/UIKit/UIView.js +3 -1
  96. package/Root/Frameworks/UIKit/UIWindow.js +16 -3
  97. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  98. package/package.json +1 -1
@@ -26,40 +26,15 @@
26
26
  JSClass("UIHTMLTextFrame", JSTextFrame, {
27
27
 
28
28
  element: null,
29
- attachments: null,
29
+ attachmentRuns: null,
30
30
 
31
- initWithElement: function(element, lines, size, attributes){
31
+ initWithElement: function(element, lines, size){
32
32
  this.element = element;
33
- this.attachments = [];
33
+ this.attachmentRuns = [];
34
34
  var line;
35
35
  var i, l;
36
36
  var j, k;
37
37
 
38
- // If our element is not in the document, we need to add it so calls
39
- // to getBoundingClientRect() will work.
40
- // The HTML framesetter always adds a newly created frame to the document,
41
- // but it's possible to be removed from the document if we're reusing a
42
- // frame that's in a view that itself has been removed from the document.
43
- // After we're done making calls, we'll add it back to the original parent
44
- // in its original location.
45
- //
46
- // UPDATE 2020-07-26 - When text frames are drawn inside a scaled or rotated
47
- // view, the getBoundingClientRect() based calculations return transformed
48
- // values, which we don't want. The quickest fix is to ALWAYS remove the
49
- // frame element from its parent and attach it to the document body so
50
- // it will be untransformed. This doesn't appear to be a major performance
51
- // issue, but it would be nice to find another way around. For now, I'm
52
- // preserving the original code with the addition of `true || ` to always
53
- // enter the conditional.
54
- var originalParent = null;
55
- var originalSibling = null;
56
- if (true || !this.element.isConnected){
57
- originalParent = this.element.parentNode;
58
- originalSibling = this.element.nextSibling;
59
- this.element.ownerDocument.body.appendChild(this.element);
60
- this.element.style.visibility = 'hidden';
61
- }
62
-
63
38
  // add all the lines to our element
64
39
  for (i = 0, l = lines.length; i < l; ++i){
65
40
  line = lines[i];
@@ -69,38 +44,15 @@ JSClass("UIHTMLTextFrame", JSTextFrame, {
69
44
  }
70
45
  }
71
46
 
72
- // set the size of all the lines and runs
73
- // NOTE: this could be a job for the typesetter, but doing so incrementally
74
- // as runs/lines are created would force a extra layouts that aren't necessary.
75
- // So we try to force only one layout here and then grab all the metrics
76
- var origin = JSPoint(0, 0);
77
- for (i = 0, l = lines.length; i < l; ++i){
78
- line = lines[i];
79
- line.recalculateSize();
80
- line.recalculateTrailingWhitespace();
81
- line._origin = JSPoint(origin);
82
- origin.y += line._size.height;
83
- }
84
-
85
- var attachmentInfo;
47
+ var run;
86
48
  for (i = 0, l = lines.length; i < l; ++i){
87
49
  line = lines[i];
88
- for (j = 0, k = line.attachments.length; j < k; ++j){
89
- attachmentInfo = line.attachments[j];
90
- if (attachmentInfo.attachment.isKindOfClass(UITextAttachmentView)){
91
- // attachmentInfo.attachment.view.frame = JSRect(
92
- // JSPoint(line.origin.x + attachmentInfo.run.origin.x, line.origin.y + attachmentInfo.run.origin.y),
93
- // attachmentInfo.attachment.view.frame.size
94
- // );
95
- }
96
- this.attachments.push(attachmentInfo);
50
+ for (j = 0, k = line.attachmentRuns.length; j < k; ++j){
51
+ run = line.attachmentRuns[j];
52
+ this.attachmentRuns.push(run);
97
53
  }
98
54
  }
99
55
 
100
- if (originalParent !== null){
101
- originalParent.insertBefore(this.element, originalSibling);
102
- }
103
-
104
56
  if (size.width === 0 || size.width === Number.MAX_VALUE){
105
57
  this._widthMatchesUsedWidth = true;
106
58
  }
@@ -108,53 +60,30 @@ JSClass("UIHTMLTextFrame", JSTextFrame, {
108
60
  this._heightMatchesUsedHeight = true;
109
61
  }
110
62
 
111
- // Superclass init will adjust origins according to text alignment, but
112
- // we must have properly set the line size and trailingWhitespaceWidth first
113
- UIHTMLTextFrame.$super.initWithLines.call(this, lines, size, attributes);
114
- this._updateSizesAndPositions();
63
+ UIHTMLTextFrame.$super.initWithLines.call(this, lines, size);
115
64
  this.element.dataset.rangeLocation = this._range.location;
116
65
  this.element.dataset.rangeLength = this._range.length;
117
66
  this.element.dataset.jstext = "frame";
118
67
  },
119
68
 
120
- adjustSize: function(newSize){
121
- UIHTMLTextFrame.$super.adjustSize.call(this, newSize);
122
- this._updateSizesAndPositions();
123
- },
124
-
125
- _updateSizesAndPositions: function(){
126
- var i, l;
127
- var line;
128
-
129
- // set our size
130
- this.element.style.width = '%dpx'.sprintf(this.size.width);
131
- this.element.style.height = '%dpx'.sprintf(this.size.height);
132
-
133
- // position the lines
134
- for (i = 0, l = this.lines.length; i < l; ++i){
135
- line = this.lines[i];
136
- line.element.style.height = '%dpx'.sprintf(line.size.height);
137
- line.element.style.left = '%dpx'.sprintf(line.origin.x);
138
- line.element.style.top = '%dpx'.sprintf(line.origin.y);
139
- }
140
- },
141
-
142
69
  drawInContextAtPoint: function(context, point){
143
70
  if (context.isKindOfClass(UIHTMLDisplayServerCanvasContext)){
144
71
  if (this.element.style.visibility == 'hidden'){
145
72
  this.element.style.visibility = '';
146
73
  }
147
74
  context.addExternalElementInRect(this.element, JSRect(point, this.size));
148
- var attachmentInfo;
149
- for (var i = 0, l = this.attachments.length; i < l; ++i){
150
- attachmentInfo = this.attachments[i];
151
- if (attachmentInfo.context.element.parentNode !== attachmentInfo.run.element){
152
- attachmentInfo.run.element.appendChild(attachmentInfo.context.element);
75
+ var run;
76
+ var attachmentContext;
77
+ for (var i = 0, l = this.attachmentRuns.length; i < l; ++i){
78
+ run = this.attachmentRuns[i];
79
+ attachmentContext = context.displayServer.contextForAttachment(run.attachment);
80
+ if (attachmentContext.element.parentNode !== run.element){
81
+ run.element.appendChild(attachmentContext.element);
153
82
  }
154
- attachmentInfo.context.save();
155
- attachmentInfo.context.setFillColor(attachmentInfo.run.attributes.textColor);
156
- attachmentInfo.attachment.drawInContextAtPoint(attachmentInfo.context, JSPoint.Zero);
157
- attachmentInfo.context.restore();
83
+ attachmentContext.save();
84
+ attachmentContext.setFillColor(run.attributes.textColor);
85
+ run.attachment.drawInContextAtPoint(attachmentContext, JSPoint.Zero);
86
+ attachmentContext.restore();
158
87
  }
159
88
  }else{
160
89
  UIHTMLTextFrame.$super.drawInContextAtPoint.call(this, context, point);
@@ -219,7 +148,6 @@ JSClass("UIHTMLTextFrame", JSTextFrame, {
219
148
  if (this._heightMatchesUsedHeight){
220
149
  this._size = JSSize(this._size.width, this._usedSize.height);
221
150
  }
222
- this._updateSizesAndPositions();
223
151
  }
224
152
 
225
153
  });
@@ -26,29 +26,50 @@
26
26
  JSClass("UIHTMLTextFramesetter", UITextFramesetter, {
27
27
 
28
28
  _domDocument: null,
29
+ _htmlDisplayServer: null,
29
30
  _htmlTypesetter: null,
30
31
  _frameElement: null,
31
- _frame: null,
32
+ _attachments: null,
32
33
 
33
34
  initWithDocument: function(domDocument, htmlDisplayServer){
34
- this._htmlTypesetter = UIHTMLTextTypesetter.initWithDocument(domDocument, htmlDisplayServer);
35
- UIHTMLTextFramesetter.$super.initWithTypesetter.call(this, this._htmlTypesetter);
36
35
  this._domDocument = domDocument;
36
+ this._htmlDisplayServer = htmlDisplayServer;
37
+ this._htmlTypesetter = UIHTMLTextTypesetter.initWithDocument(domDocument);
38
+ this._attachments = [];
39
+ UIHTMLTextFramesetter.$super.initWithTypesetter.call(this, this._htmlTypesetter);
37
40
  },
38
41
 
39
42
  createFrame: function(size, range, maximumLines){
40
- this._creatingFrame = true;
41
43
  this._createFrameElementIfNeeded();
42
- var lineBreakMode = this.effectiveLineBreakMode(this.attributes.lineBreakMode, 1, maximumLines);
43
44
  this._enqueueElements();
44
- this._htmlTypesetter.layoutRange(range, size, lineBreakMode);
45
+ var attachmentsByObjectID = {};
46
+ var attachment;
47
+ var i, l;
48
+ for (i = 0, l = this._attachments.length; i < l; ++i){
49
+ attachment = this._attachments[i];
50
+ attachmentsByObjectID[attachment.objectID] = attachment;
51
+ }
45
52
  var frame = UIHTMLTextFramesetter.$super.createFrame.call(this, size, range, maximumLines);
53
+ this._attachments = [];
54
+ for (i = 0, l = frame.attachmentRuns.length; i < l; ++i){
55
+ attachment = frame.attachmentRuns[i].attachment;
56
+ this._attachments.push(attachment);
57
+ if (attachment.objectID in attachmentsByObjectID){
58
+ delete attachmentsByObjectID[attachment.objectID];
59
+ }else{
60
+ this._htmlDisplayServer.attachmentInserted(attachment);
61
+ }
62
+ }
63
+ for (i in attachmentsByObjectID){
64
+ attachment = attachmentsByObjectID[i];
65
+ this._htmlDisplayServer.attachmentRemoved(attachment);
66
+ }
46
67
  this._removeQueuedElements();
47
68
  return frame;
48
69
  },
49
70
 
50
- constructFrame: function(lines, size, attributes){
51
- return UIHTMLTextFrame.initWithElement(this._frameElement, lines, size, attributes);
71
+ constructFrame: function(lines, size){
72
+ return UIHTMLTextFrame.initWithElement(this._frameElement, lines, size);
52
73
  },
53
74
 
54
75
  _createFrameElementIfNeeded: function(){
@@ -114,7 +135,29 @@ JSClass("UIHTMLTextFramesetter", UITextFramesetter, {
114
135
  }
115
136
  element = this._htmlTypesetter.dequeueRunElement();
116
137
  }
117
- }
138
+ },
139
+
140
+ _alignLinesInFrame: function(frame){
141
+ UIHTMLTextFramesetter.$super._alignLinesInFrame.call(this, frame);
142
+ this._updateSizesAndPositionsOfLinesInFrame(frame);
143
+ },
144
+
145
+ _updateSizesAndPositionsOfLinesInFrame: function(frame){
146
+ var i, l;
147
+ var line;
148
+
149
+ // set our size
150
+ frame.element.style.width = '%dpx'.sprintf(frame.size.width);
151
+ frame.element.style.height = '%dpx'.sprintf(frame.size.height);
152
+
153
+ // position the lines
154
+ for (i = 0, l = frame.lines.length; i < l; ++i){
155
+ line = frame.lines[i];
156
+ line.element.style.height = '%dpx'.sprintf(line.size.height);
157
+ line.element.style.left = '%dpx'.sprintf(line.origin.x);
158
+ line.element.style.top = '%dpx'.sprintf(line.origin.y);
159
+ }
160
+ },
118
161
 
119
162
  });
120
163
 
@@ -24,28 +24,29 @@ JSClass("UIHTMLTextLine", JSTextLine, {
24
24
 
25
25
  element: null,
26
26
  emptyTextNode: null,
27
- attachments: null,
28
- overflowed: false,
27
+ attachmentRuns: null,
29
28
  fontLineHeight: 0,
29
+ canvasContext: null,
30
30
 
31
- initWithElementAndFont: function(element, font, height, location){
31
+ initWithElementAndFont: function(element, font, height, location, canvasContext){
32
32
  UIHTMLTextLine.$super.initWithHeight.call(this, height, -font.displayDescender, location);
33
33
  this.element = element;
34
34
  element.style.font = font.cssString(height);
35
35
  this.emptyTextNode = element.appendChild(element.ownerDocument.createTextNode('\u200B'));
36
36
  this.fontLineHeight = font.displayLineHeight;
37
- this.attachments = [];
37
+ this.attachmentRuns = [];
38
38
  this.element.dataset.rangeLocation = location;
39
39
  this.element.dataset.rangeLength = 0;
40
40
  this.element.dataset.jstext = "line";
41
+ this.canvasContext = canvasContext;
41
42
  },
42
43
 
43
- initWithElement: function(element, runs, trailingWhitespaceWidth, attachments){
44
+ initWithElement: function(element, runs, trailingWhitespaceWidth, attachmentRuns, canvasContext){
44
45
  // constructing this.element before super init because super init calls
45
46
  // this.align, which neesd to use this.element
46
47
  UIHTMLTextLine.$super.initWithRuns.call(this, runs, trailingWhitespaceWidth);
47
48
  this.element = element;
48
- this.attachments = attachments || [];
49
+ this.attachmentRuns = attachmentRuns || [];
49
50
  var run;
50
51
  for (var i = 0, l = runs.length; i < l; ++i){
51
52
  run = this.runs[i];
@@ -56,6 +57,7 @@ JSClass("UIHTMLTextLine", JSTextLine, {
56
57
  this.element.dataset.rangeLocation = this._range.location;
57
58
  this.element.dataset.rangeLength = this._range.length;
58
59
  this.element.dataset.jstext = "line";
60
+ this.canvasContext = canvasContext;
59
61
  },
60
62
 
61
63
  // verticallyAlignRuns: function(){
@@ -63,39 +65,102 @@ JSClass("UIHTMLTextLine", JSTextLine, {
63
65
  // },
64
66
 
65
67
  truncatedLine: function(width, token){
66
- if (!this.overflowed || width === Number.MAX_VALUE || width === 0){
68
+ if (width + 0.1 >= this._size.width){
67
69
  return this;
68
70
  }
69
71
 
70
72
  if (token === undefined){
71
- token = '\u2026';
73
+ token = "\u2026";
74
+ }
75
+ var line = this.copy();
76
+
77
+ // get rid of any runs that put us over the limit
78
+ var runIndex = line.runs.length - 1;
79
+ while (runIndex > 0 && line.size.width - line.runs[runIndex].size.width >= width){
80
+ line.size.width -= line.runs[runIndex].size.width;
81
+ line.range.length -= line.runs[runIndex].range.length;
82
+ line.element.removeChild(line.runs[runIndex].element);
83
+ line.runs.pop();
84
+ --runIndex;
72
85
  }
73
86
 
74
- this.element.style.maxWidth = '%dpx'.sprintf(width);
75
- this.element.style.overflow = 'hidden';
76
- // only firefox supports an arbitrary string as the token, so for now
77
- // we'll just hard code ellipsis
78
- this.element.style.textOverflow = 'ellipsis';
79
-
80
- // Adopt relevant styles from the final run, otherwise the ellipis will
81
- // use the style of the line div. Currently this only adopts text color,
82
- // and font, taking care to keep our line height 0;
83
- if (this._runs.length > 0){
84
- var lastRun = this._runs[this._runs.length - 1];
85
- this.element.style.color = lastRun.element.style.color;
86
- this.element.style.font = lastRun.element.style.font;
87
- this.element.style.lineHeight = '0';
87
+ // Find the run that has space for ellipis
88
+ var run;
89
+ var metrics = null;
90
+ do {
91
+ run = line.runs[runIndex];
92
+ if (!run.attachment){
93
+ this.canvasContext.font = run.font.cssString();
94
+ metrics = this.canvasContext.measureText(token);
95
+ if (line.size.width - run.size.width + metrics.width <= width){
96
+ break;
97
+ }
98
+ }
99
+ line.element.removeChild(run.element);
100
+ line.runs.pop();
101
+ line.size.width -= run.size.width;
102
+ line.range.length -= run.range.length;
103
+ --runIndex;
104
+ }while (runIndex >= 0);
105
+
106
+ // Bail if there's no room for even the token
107
+ if (runIndex < 0){
108
+ return line;
88
109
  }
89
110
 
90
- // Add an ellipsis...if it fits, great! If not, we'll get the html generated ellipis
91
- this.element.appendChild(this.element.ownerDocument.createTextNode(token));
111
+ if (metrics !== null){
112
+ var span = this.element.ownerDocument.createElement("span");
113
+ span.setAttribute("style", run.element.getAttribute("style"));
114
+ span.appendChild(this.element.ownerDocument.createTextNode(token));
115
+ var tokenRun = UIHTMLTextRun.initWithElement(span, run.font, run.attributes, JSRange.Zero);
116
+ tokenRun.size.width = metrics.width;
117
+ line.runs.push(tokenRun);
118
+ line.element.appendChild(tokenRun.element);
119
+ line.size.width += tokenRun.size.width;
120
+
121
+ var iterator = run.textNode.nodeValue.userPerceivedCharacterIterator(run.textNode.nodeValue.length - 1);
122
+ while (iterator.character !== null && iterator.isMandatoryLineBreak){
123
+ iterator.decrement();
124
+ run.range.length -= iterator.range.length;
125
+ line.range.length -= iterator.range.length;
126
+ }
127
+ while (iterator.character !== null && line.size.width > width){
128
+ metrics = this.canvasContext.measureText(iterator.utf16);
129
+ run.size.width -= metrics.width;
130
+ run.range.length -= iterator.range.length;
131
+ line.size.width -= metrics.width;
132
+ line.range.length -= iterator.range.length;
133
+ run.textNode.nodeValue = run.textNode.nodeValue.substr(0, run.textNode.nodeValue.length - iterator.range.length);
134
+ iterator.decrement();
135
+ }
136
+
137
+ if (run.range.length === 0){
138
+ line.element.removeChild(line.runs[line.runs.length - 2].element);
139
+ line.runs.splice(line.runs.length - 2, 1);
140
+ }
92
141
 
93
- // TODO: add JSTextRun with ellipsis (in case this line is drawn to a non-html context, and so .runs is consistent)
94
- // but this would also possibly require backing up 1+ characters to make enough room for the ellipis
142
+ tokenRun.origin.x = line.size.width - tokenRun.size.width;
95
143
 
96
- // This should perhaps return a copy, but for our current use cases,
97
- // there's no need to copy since the original line gets abandoned.
98
- return this;
144
+ line.verticallyAlignRuns();
145
+ }
146
+
147
+ return line;
148
+ },
149
+
150
+ copy: function(){
151
+ var line = UIHTMLTextLine.$super.copy.call(this);
152
+ line.element = this.element.cloneNode();
153
+ if (this.emptyTextNode !== null){
154
+ line.emptyTextNode = this.emptyTextNode.cloneNode();
155
+ line.element.appendChild(line.emptyTextNode);
156
+ }
157
+ for (var i = 0, l = line.runs.length; i < l; ++i){
158
+ line.element.appendChild(line.runs[i].element);
159
+ }
160
+ line.attachmentRuns = JSCopy(this.attachmentRuns);
161
+ line.fontLineHeight = this.fontLineHeight;
162
+ this.canvasContext = this.canvasContext;
163
+ return line;
99
164
  },
100
165
 
101
166
  rectForEmptyCharacter: function(){
@@ -179,7 +244,7 @@ JSClass("UIHTMLTextLine", JSTextLine, {
179
244
  this.element.dataset.rangeLocation = this._range.location;
180
245
  this.element.dataset.rangeLength = this._range.length;
181
246
  return diff;
182
- }
247
+ },
183
248
 
184
249
  });
185
250
 
@@ -39,6 +39,7 @@ JSClass("UIHTMLTextRun", JSTextRun, {
39
39
  // Only the UIHTMLTextFrame does the final measuring.
40
40
  if (!this.attachment){
41
41
  this._size.height = font.displayLineHeight;
42
+ this.baseline = -font.displayDescender;
42
43
  }
43
44
  if (element.childNodes.length > 0 && element.firstChild.nodeType === element.TEXT_NODE){
44
45
  this.textNode = element.firstChild;
@@ -59,7 +60,6 @@ JSClass("UIHTMLTextRun", JSTextRun, {
59
60
  if (mask){
60
61
  this._maskFactor = mask.length;
61
62
  }
62
- this.baseline = -font.displayDescender;
63
63
  this.element.dataset.rangeLocation = this._range.location;
64
64
  this.element.dataset.rangeLength = this._range.length;
65
65
  this.element.dataset.jstext = "run";
@@ -118,8 +118,8 @@ JSClass("UIHTMLTextRun", JSTextRun, {
118
118
  },
119
119
 
120
120
  characterIndexAtPoint: function(point){
121
- var min = 0;
122
- var max = this.range.length;
121
+ var min = this.range.location;
122
+ var max = this.range.end;
123
123
  var mid;
124
124
  var rect;
125
125
  var i = 0;
@@ -145,7 +145,7 @@ JSClass("UIHTMLTextRun", JSTextRun, {
145
145
  }
146
146
  }
147
147
  }
148
- return this.range.location + min;
148
+ return min;
149
149
  },
150
150
 
151
151
  _textFrameConstructionWidthOfRange: function(range){
@@ -169,17 +169,17 @@ JSClass("UIHTMLTextRun", JSTextRun, {
169
169
  rectForCharacterAtIndex: function(index){
170
170
  // attachment run
171
171
  if (this.textNode === null){
172
- if (index === 0){
172
+ if (index === this.range.location){
173
173
  return JSRect(0, 0, this.size.width, this.size.height);
174
174
  }
175
- if (index === 1){
175
+ if (index === this.range.end){
176
176
  return JSRect(this.size.width, 0, 0, this.size.height);
177
177
  }
178
178
  return JSRect.Zero;
179
179
  }
180
180
 
181
181
  // at end, which we know without measuring
182
- if (index >= this.range.length){
182
+ if (index >= this.range.end){
183
183
  return JSRect(this.size.width, 0, 0, this.size.height);
184
184
  }
185
185
 
@@ -190,8 +190,9 @@ JSClass("UIHTMLTextRun", JSTextRun, {
190
190
  // Create a DOM range for the character in the span because the DOM range can
191
191
  // report its size and coordinates
192
192
  var rect;
193
- index *= this._maskFactor;
194
- var iterator = sharedElementForSizing.firstChild.nodeValue.userPerceivedCharacterIterator(index);
193
+ var localIndex = index - this.range.location;
194
+ localIndex *= this._maskFactor;
195
+ var iterator = sharedElementForSizing.firstChild.nodeValue.userPerceivedCharacterIterator(localIndex);
195
196
  sharedDomRange.setStart(sharedElementForSizing.firstChild, iterator.range.location);
196
197
  sharedDomRange.setEnd(sharedElementForSizing.firstChild, iterator.range.end);
197
198
  var clientRect = this._pickCorrectClientRectFromRects(sharedDomRange.getClientRects());
@@ -210,6 +211,7 @@ JSClass("UIHTMLTextRun", JSTextRun, {
210
211
  sharedElementForSizing.style.position = "absolute";
211
212
  sharedElementForSizing.style.zIndex = -100;
212
213
  sharedElementForSizing.style.right = "0";
214
+ sharedElementForSizing.style.whiteSpace = "pre";
213
215
  sharedElementForSizing.ownerDocument.body.appendChild(sharedElementForSizing);
214
216
  }
215
217
  sharedElementForSizing.style.font = this.element.style.font;
@@ -256,6 +258,17 @@ JSClass("UIHTMLTextRun", JSTextRun, {
256
258
  return diff;
257
259
  },
258
260
 
261
+ copy: function(){
262
+ var run = UIHTMLTextRun.$super.copy.call(this);
263
+ run.element = this.element.cloneNode();
264
+ if (this.textNode !== null){
265
+ run.textNode = this.textNode.cloneNode();
266
+ run.element.appendChild(run.textNode);
267
+ }
268
+ run._maskFactor = this._maskFactor;
269
+ return run;
270
+ }
271
+
259
272
  });
260
273
 
261
274
  var sharedElementForSizing = null;