@breakside/jskit 2022.22.3 → 2022.25.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/FontKit.jsframework/Info.json +2 -2
  5. package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
  6. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  7. package/Frameworks/Foundation.jsframework/JS/JSAttributedString.js +279 -6
  8. package/Frameworks/Foundation.jsframework/JS/JSFileManager+Node.js +2 -2
  9. package/Frameworks/Foundation.jsframework/JS/JSFont.js +39 -15
  10. package/Frameworks/Foundation.jsframework/JS/JSFontDescriptor+HTML.js +8 -0
  11. package/Frameworks/Foundation.jsframework/JS/JSFontDescriptor.js +105 -27
  12. package/Frameworks/Foundation.jsframework/JS/JSParagraphStyle.js +71 -0
  13. package/Frameworks/Foundation.jsframework/JS/JSTextAttachment.js +1 -1
  14. package/Frameworks/Foundation.jsframework/JS/JSTextContainer.js +56 -29
  15. package/Frameworks/Foundation.jsframework/JS/JSTextFrame.js +2 -32
  16. package/Frameworks/Foundation.jsframework/JS/JSTextFramesetter.js +125 -22
  17. package/Frameworks/Foundation.jsframework/JS/JSTextLayoutManager.js +11 -3
  18. package/Frameworks/Foundation.jsframework/JS/JSTextLine.js +4 -3
  19. package/Frameworks/Foundation.jsframework/JS/JSTextRun.js +5 -3
  20. package/Frameworks/Foundation.jsframework/JS/JSTextTypesetter.js +41 -25
  21. package/Frameworks/Foundation.jsframework/JS/JSZip.js +97 -76
  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 +26 -22
  26. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  27. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  28. package/Info.json +2 -2
  29. package/Node/io.breakside.jskit-bundle.js +2 -2
  30. package/Node/jskit +8 -7
  31. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  32. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  33. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  34. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  35. package/Root/Frameworks/ChartKit/CHAxis.js +5 -5
  36. package/Root/Frameworks/ChartKit/CHPieChart.js +1 -1
  37. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  38. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  39. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  40. package/Root/Frameworks/DOM/Info.yaml +1 -1
  41. package/Root/Frameworks/DOM/XMLSerializer.js +9 -4
  42. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  43. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  44. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  45. package/Root/Frameworks/Foundation/JSAttributedString.js +279 -6
  46. package/Root/Frameworks/Foundation/JSFileManager+Node.js +2 -2
  47. package/Root/Frameworks/Foundation/JSFont.js +39 -15
  48. package/Root/Frameworks/Foundation/JSFontDescriptor+HTML.js +8 -0
  49. package/Root/Frameworks/Foundation/JSFontDescriptor.js +105 -27
  50. package/Root/Frameworks/Foundation/JSParagraphStyle.js +71 -0
  51. package/Root/Frameworks/Foundation/JSTextAttachment.js +1 -1
  52. package/Root/Frameworks/Foundation/JSTextContainer.js +56 -29
  53. package/Root/Frameworks/Foundation/JSTextFrame.js +2 -32
  54. package/Root/Frameworks/Foundation/JSTextFramesetter.js +125 -22
  55. package/Root/Frameworks/Foundation/JSTextLayoutManager.js +11 -3
  56. package/Root/Frameworks/Foundation/JSTextLine.js +4 -3
  57. package/Root/Frameworks/Foundation/JSTextRun.js +5 -3
  58. package/Root/Frameworks/Foundation/JSTextTypesetter.js +41 -25
  59. package/Root/Frameworks/Foundation/JSZip.js +97 -76
  60. package/Root/Frameworks/Foundation/String+JS.js +69 -0
  61. package/Root/Frameworks/Foundation/UnicodeChar.js +8 -0
  62. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  63. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  64. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  65. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  66. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  67. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  68. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  69. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  70. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  71. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  72. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  73. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  74. package/Root/Frameworks/UIKit/UIBasicAnimation.js +1 -1
  75. package/Root/Frameworks/UIKit/UIHTMLApplication.js +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/UIImageLayer.js +14 -7
  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
@@ -0,0 +1,71 @@
1
+ // #import "JSObject.js"
2
+ // #import "CoreTypes.js"
3
+ "use strict";
4
+
5
+ JSClass("JSParagraphStyle", JSObject, {
6
+
7
+ textAlignment: JSTextAlignment.left,
8
+ lineBreakMode: JSLineBreakMode.truncateTail,
9
+ lineSpacing: 0,
10
+ minimumLineHeight: 0,
11
+ lineHeightMultiple: 0,
12
+ paragraphSpacing: 0,
13
+ beforeParagraphSpacing: 0,
14
+ firstLineHeadIndent: 0,
15
+ headIndent: 0,
16
+ tailIndent: 0,
17
+
18
+ init: function(){
19
+ },
20
+
21
+ styleWithAttributes: function(attributes){
22
+ var style = this.copy();
23
+ if (attributes.textAlignment !== undefined){
24
+ style.textAlignment = attributes.textAlignment;
25
+ }
26
+ if (attributes.lineBreakMode !== undefined){
27
+ style.lineBreakMode = attributes.lineBreakMode;
28
+ }
29
+ if (attributes.lineSpacing !== undefined){
30
+ style.lineSpacing = attributes.lineSpacing;
31
+ }
32
+ if (attributes.minimumLineHeight !== undefined){
33
+ style.minimumLineHeight = attributes.minimumLineHeight;
34
+ }
35
+ if (attributes.lineHeightMultiple !== undefined){
36
+ style.lineHeightMultiple = attributes.lineHeightMultiple;
37
+ }
38
+ if (attributes.paragraphSpacing !== undefined){
39
+ style.paragraphSpacing = attributes.paragraphSpacing;
40
+ }
41
+ if (attributes.beforeParagraphSpacing !== undefined){
42
+ style.beforeParagraphSpacing = attributes.beforeParagraphSpacing;
43
+ }
44
+ if (attributes.firstLineHeadIndent !== undefined){
45
+ style.firstLineHeadIndent = attributes.firstLineHeadIndent;
46
+ }
47
+ if (attributes.headIndent !== undefined){
48
+ style.headIndent = attributes.headIndent;
49
+ }
50
+ if (attributes.tailIndent !== undefined){
51
+ style.tailIndent = attributes.tailIndent;
52
+ }
53
+ return style;
54
+ },
55
+
56
+ copy: function(){
57
+ var style = JSParagraphStyle.init();
58
+ style.textAlignment = this.textAlignment;
59
+ style.lineBreakMode = this.lineBreakMode;
60
+ style.lineSpacing = this.lineSpacing;
61
+ style.minimumLineHeight = this.minimumLineHeight;
62
+ style.lineHeightMultiple = this.lineHeightMultiple;
63
+ style.paragraphSpacing = this.paragraphSpacing;
64
+ style.beforeParagraphSpacing = this.beforeParagraphSpacing;
65
+ style.firstLineHeadIndent = this.firstLineHeadIndent;
66
+ style.headIndent = this.headIndent;
67
+ style.tailIndent = this.tailIndent;
68
+ return style;
69
+ }
70
+
71
+ });
@@ -32,7 +32,7 @@ JSClass("JSTextAttachment", JSObject, {
32
32
  this._image = image;
33
33
  var ratio = image.size.width / image.size.height;
34
34
  this._size = JSSize(ratio * font.displayLineHeight, font.displayLineHeight);
35
- this.baselineAdjustment = font.displayDescender;
35
+ this.baselineAdjustment = -font.displayDescender;
36
36
  },
37
37
 
38
38
  initWithImage: function(image, size){
@@ -21,37 +21,13 @@
21
21
 
22
22
  (function(){
23
23
 
24
- var JSTextContainerAttributeProperty = function(){
25
- if (this === undefined){
26
- return new JSTextContainerAttributeProperty();
27
- }
28
- };
29
-
30
- JSTextContainerAttributeProperty.prototype = Object.create(JSCustomProperty.prototype);
31
-
32
- JSTextContainerAttributeProperty.prototype.define = function(C, key, extensions){
33
- Object.defineProperty(C.prototype, key, {
34
- configurable: false,
35
- enumerable: false,
36
- set: function JSTextContainer_setAttributeProperty(value){
37
- this.framesetter.attributes[key] = value;
38
- this._notifyLayoutManager();
39
- },
40
- get: function JSTextContainer_getAttributeProperty(){
41
- return this.framesetter.attributes[key];
42
- }
43
- });
44
- };
24
+ var logger = JSLog("foundation", "text");
45
25
 
46
26
  JSClass("JSTextContainer", JSObject, {
47
27
 
48
28
  origin: JSDynamicProperty('_origin', JSPoint.Zero),
49
29
  size: JSDynamicProperty('_size', null),
50
30
  range: JSReadOnlyProperty(),
51
- lineBreakMode: JSTextContainerAttributeProperty(),
52
- textAlignment: JSTextContainerAttributeProperty(),
53
- lineSpacing: JSTextContainerAttributeProperty(),
54
- minimumLineHeight: JSTextContainerAttributeProperty(),
55
31
  textLayoutManager: JSDynamicProperty('_textLayoutManager', null),
56
32
  maximumNumberOfLines: JSDynamicProperty('_maximumNumberOfLines', 0),
57
33
  framesetter: null,
@@ -83,10 +59,10 @@ JSClass("JSTextContainer", JSObject, {
83
59
  // If we are a single line label and we have all of the string, we *may not* need to do a new layout
84
60
  if (this._maximumNumberOfLines == 1 && this._textFrame.range.location === 0 && this._textFrame.range.length >= this._textLayoutManager.textStorage.string.length){
85
61
  // If the new size can still fit the entire layout, then no need to do a new layout manager layout
86
- if (size.width >= this._textFrame.usedSize.width && size.height >= this._textFrame.usedSize.height){
62
+ if (size.width < Number.MAX_VALUE && size.height < Number.MAX_VALUE && size.width >= this._textFrame.usedSize.width && size.height >= this._textFrame.usedSize.height){
87
63
  // Text frame can do a shortcut update by trimming excess size without affecting truncation
88
64
  shouldNotify = false;
89
- this._textFrame.adjustSize(size);
65
+ this.framesetter._resizeFrame(this._textFrame, size);
90
66
  }
91
67
  }
92
68
  if (shouldNotify){
@@ -110,6 +86,7 @@ JSClass("JSTextContainer", JSObject, {
110
86
 
111
87
  createTextFrame: function(attributedString, range){
112
88
  this.framesetter.attributedString = attributedString;
89
+ this.framesetter.defaultParagraphStyle = this._textLayoutManager.defaultParagraphStyle;
113
90
  this._textFrame = this.framesetter.createFrame(this.size, range, this._maximumNumberOfLines);
114
91
  },
115
92
 
@@ -125,7 +102,7 @@ JSClass("JSTextContainer", JSObject, {
125
102
  rectForCharacterAtIndex: function(index, useLineHeight){
126
103
  var line = this.lineForCharacterAtIndex(index);
127
104
  if (line !== null){
128
- var rect = line.rectForCharacterAtIndex(index - (line.range.location - this._textFrame.range.location));
105
+ var rect = line.rectForCharacterAtIndex(index);
129
106
  if (useLineHeight){
130
107
  rect.origin.y = line.origin.y;
131
108
  rect.size.height = line.size.height;
@@ -167,7 +144,57 @@ JSClass("JSTextContainer", JSObject, {
167
144
  if (this._textLayoutManager !== null){
168
145
  this._textLayoutManager.setNeedsLayout();
169
146
  }
170
- }
147
+ },
148
+
149
+ // MARK: - Deprecated
150
+
151
+ lineBreakMode: JSDynamicProperty(),
152
+
153
+ getLineBreakMode: function(){
154
+ logger.warn("JSTextContainer.lineBreakMode is deprecated");
155
+ return this._textLayoutManager.defaultParagraphStyle.lineBreakMode;
156
+ },
157
+
158
+ setLineBreakMode: function(lineBreakMode){
159
+ logger.warn("JSTextContainer.lineBreakMode is deprecated");
160
+ this._textLayoutManager.defaultParagraphStyle = this._textLayoutManager.defaultParagraphStyle.styleWithAttributes({lineBreakMode: lineBreakMode});
161
+ },
162
+
163
+ textAlignment: JSDynamicProperty(),
164
+
165
+ getTextAlignment: function(){
166
+ logger.warn("JSTextContainer.textAlignment is deprecated");
167
+ return this._textLayoutManager.defaultParagraphStyle.textAlignment;
168
+ },
169
+
170
+ setTextAlignment: function(textAlignment){
171
+ logger.warn("JSTextContainer.textAlignment is deprecated");
172
+ this._textLayoutManager.defaultParagraphStyle = this._textLayoutManager.defaultParagraphStyle.styleWithAttributes({textAlignment: textAlignment});
173
+ },
174
+
175
+ lineSpacing: JSDynamicProperty(),
176
+
177
+ getLineSpacing: function(){
178
+ logger.warn("JSTextContainer.lineSpacing is deprecated");
179
+ return this._textLayoutManager.defaultParagraphStyle.lineSpacing;
180
+ },
181
+
182
+ setLineSpacing: function(lineSpacing){
183
+ logger.warn("JSTextContainer.lineSpacing is deprecated");
184
+ this._textLayoutManager.defaultParagraphStyle = this._textLayoutManager.defaultParagraphStyle.styleWithAttributes({lineSpacing: lineSpacing});
185
+ },
186
+
187
+ minimumLineHeight: JSDynamicProperty(),
188
+
189
+ getMinimumLineHeight: function(){
190
+ logger.warn("JSTextContainer.minimumLineHeight is deprecated");
191
+ return this._textLayoutManager.defaultParagraphStyle.minimumLineHeight;
192
+ },
193
+
194
+ setMinimumLineHeight: function(minimumLineHeight){
195
+ logger.warn("JSTextContainer.minimumLineHeight is deprecated");
196
+ this._textLayoutManager.defaultParagraphStyle = this._textLayoutManager.defaultParagraphStyle.styleWithAttributes({minimumLineHeight: minimumLineHeight});
197
+ },
171
198
 
172
199
  });
173
200
 
@@ -25,9 +25,8 @@ JSClass("JSTextFrame", JSObject, {
25
25
  usedSize: JSReadOnlyProperty('_usedSize', null),
26
26
  range: JSReadOnlyProperty('_range', null),
27
27
  lines: JSReadOnlyProperty('_lines', null),
28
- _alignment: JSTextAlignment.left,
29
28
 
30
- initWithLines: function(lines, size, attributes){
29
+ initWithLines: function(lines, size){
31
30
  if (lines.length > 0){
32
31
  this._range = JSRange(lines[0].range.location, lines[lines.length - 1].range.end - lines[0].range.location);
33
32
  }else{
@@ -52,35 +51,6 @@ JSClass("JSTextFrame", JSObject, {
52
51
  if (this._size.height === 0 || this._size.height === Number.MAX_VALUE){
53
52
  this._size.height = y;
54
53
  }
55
- this._alignment = attributes.textAlignment;
56
- this._alignLines();
57
- },
58
-
59
- adjustSize: function(newSize){
60
- if (newSize.width < this._usedSize.width || newSize.height < this._usedSize.height){
61
- throw new Error("Cannot adjust text frame to smaller than its used size");
62
- }
63
- this._size = JSSize(newSize);
64
- this._alignLines();
65
- },
66
-
67
- _alignLines: function(){
68
- var i, l;
69
- var line;
70
- switch (this._alignment){
71
- case JSTextAlignment.center:
72
- for (i = 0, l = this.lines.length; i < l; ++i){
73
- line = this.lines[i];
74
- line.origin.x = (this._size.width - line.size.width + line.trailingWhitespaceWidth) / 2.0;
75
- }
76
- break;
77
- case JSTextAlignment.right:
78
- for (i = 0, l = this.lines.length; i < l; ++i){
79
- line = this.lines[i];
80
- line.origin.x = (this._size.width - line.size.width + line.trailingWhitespaceWidth);
81
- }
82
- break;
83
- }
84
54
  },
85
55
 
86
56
  drawInContextAtPoint: function(context, point){
@@ -107,7 +77,7 @@ JSClass("JSTextFrame", JSObject, {
107
77
  while (min < max){
108
78
  mid = Math.floor(min + (max - min) / 2);
109
79
  line = this._lines[mid];
110
- i = line.range.location - this.range.location;
80
+ i = line.range.location;
111
81
  l = line.range.length;
112
82
  if (index < i){
113
83
  max = mid;
@@ -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 = [];