@itwin/core-backend 5.2.0-dev.7 → 5.3.0-dev.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 (192) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/lib/cjs/BackendHubAccess.d.ts +2 -0
  3. package/lib/cjs/BackendHubAccess.d.ts.map +1 -1
  4. package/lib/cjs/BackendHubAccess.js.map +1 -1
  5. package/lib/cjs/BackendLoggerCategory.d.ts +6 -0
  6. package/lib/cjs/BackendLoggerCategory.d.ts.map +1 -1
  7. package/lib/cjs/BackendLoggerCategory.js +6 -0
  8. package/lib/cjs/BackendLoggerCategory.js.map +1 -1
  9. package/lib/cjs/BriefcaseManager.d.ts +57 -3
  10. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  11. package/lib/cjs/BriefcaseManager.js +151 -42
  12. package/lib/cjs/BriefcaseManager.js.map +1 -1
  13. package/lib/cjs/CloudSqlite.d.ts +4 -0
  14. package/lib/cjs/CloudSqlite.d.ts.map +1 -1
  15. package/lib/cjs/CloudSqlite.js.map +1 -1
  16. package/lib/cjs/ECDb.d.ts +8 -0
  17. package/lib/cjs/ECDb.d.ts.map +1 -1
  18. package/lib/cjs/ECDb.js +22 -0
  19. package/lib/cjs/ECDb.js.map +1 -1
  20. package/lib/cjs/IModelDb.d.ts +54 -3
  21. package/lib/cjs/IModelDb.d.ts.map +1 -1
  22. package/lib/cjs/IModelDb.js +87 -9
  23. package/lib/cjs/IModelDb.js.map +1 -1
  24. package/lib/cjs/IModelHost.d.ts +11 -1
  25. package/lib/cjs/IModelHost.d.ts.map +1 -1
  26. package/lib/cjs/IModelHost.js +5 -0
  27. package/lib/cjs/IModelHost.js.map +1 -1
  28. package/lib/cjs/IModelIncrementalSchemaLocater.d.ts +1 -5
  29. package/lib/cjs/IModelIncrementalSchemaLocater.d.ts.map +1 -1
  30. package/lib/cjs/IModelIncrementalSchemaLocater.js +0 -6
  31. package/lib/cjs/IModelIncrementalSchemaLocater.js.map +1 -1
  32. package/lib/cjs/SqliteChangesetReader.d.ts +8 -0
  33. package/lib/cjs/SqliteChangesetReader.d.ts.map +1 -1
  34. package/lib/cjs/SqliteChangesetReader.js +11 -0
  35. package/lib/cjs/SqliteChangesetReader.js.map +1 -1
  36. package/lib/cjs/StashManager.d.ts +175 -0
  37. package/lib/cjs/StashManager.d.ts.map +1 -0
  38. package/lib/cjs/StashManager.js +306 -0
  39. package/lib/cjs/StashManager.js.map +1 -0
  40. package/lib/cjs/TxnManager.d.ts +226 -15
  41. package/lib/cjs/TxnManager.d.ts.map +1 -1
  42. package/lib/cjs/TxnManager.js +249 -23
  43. package/lib/cjs/TxnManager.js.map +1 -1
  44. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts +10 -1
  45. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  46. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js +15 -6
  47. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  48. package/lib/cjs/annotations/LeaderGeometry.d.ts +3 -2
  49. package/lib/cjs/annotations/LeaderGeometry.d.ts.map +1 -1
  50. package/lib/cjs/annotations/LeaderGeometry.js +5 -4
  51. package/lib/cjs/annotations/LeaderGeometry.js.map +1 -1
  52. package/lib/cjs/annotations/TextAnnotationElement.d.ts +52 -24
  53. package/lib/cjs/annotations/TextAnnotationElement.d.ts.map +1 -1
  54. package/lib/cjs/annotations/TextAnnotationElement.js +49 -59
  55. package/lib/cjs/annotations/TextAnnotationElement.js.map +1 -1
  56. package/lib/cjs/annotations/TextAnnotationGeometry.d.ts +2 -0
  57. package/lib/cjs/annotations/TextAnnotationGeometry.d.ts.map +1 -1
  58. package/lib/cjs/annotations/TextAnnotationGeometry.js +26 -19
  59. package/lib/cjs/annotations/TextAnnotationGeometry.js.map +1 -1
  60. package/lib/cjs/annotations/TextBlockGeometry.d.ts.map +1 -1
  61. package/lib/cjs/annotations/TextBlockGeometry.js +8 -0
  62. package/lib/cjs/annotations/TextBlockGeometry.js.map +1 -1
  63. package/lib/cjs/annotations/TextBlockLayout.d.ts +49 -36
  64. package/lib/cjs/annotations/TextBlockLayout.d.ts.map +1 -1
  65. package/lib/cjs/annotations/TextBlockLayout.js +204 -135
  66. package/lib/cjs/annotations/TextBlockLayout.js.map +1 -1
  67. package/lib/cjs/internal/ChannelAdmin.js +1 -1
  68. package/lib/cjs/internal/ChannelAdmin.js.map +1 -1
  69. package/lib/cjs/internal/Symbols.d.ts +1 -0
  70. package/lib/cjs/internal/Symbols.d.ts.map +1 -1
  71. package/lib/cjs/internal/Symbols.js +2 -1
  72. package/lib/cjs/internal/Symbols.js.map +1 -1
  73. package/lib/cjs/internal/annotations/fields.d.ts +2 -12
  74. package/lib/cjs/internal/annotations/fields.d.ts.map +1 -1
  75. package/lib/cjs/internal/annotations/fields.js +49 -45
  76. package/lib/cjs/internal/annotations/fields.js.map +1 -1
  77. package/lib/cjs/workspace/Workspace.d.ts +1 -1
  78. package/lib/cjs/workspace/Workspace.js.map +1 -1
  79. package/lib/esm/BackendHubAccess.d.ts +2 -0
  80. package/lib/esm/BackendHubAccess.d.ts.map +1 -1
  81. package/lib/esm/BackendHubAccess.js.map +1 -1
  82. package/lib/esm/BackendLoggerCategory.d.ts +6 -0
  83. package/lib/esm/BackendLoggerCategory.d.ts.map +1 -1
  84. package/lib/esm/BackendLoggerCategory.js +6 -0
  85. package/lib/esm/BackendLoggerCategory.js.map +1 -1
  86. package/lib/esm/BriefcaseManager.d.ts +57 -3
  87. package/lib/esm/BriefcaseManager.d.ts.map +1 -1
  88. package/lib/esm/BriefcaseManager.js +152 -43
  89. package/lib/esm/BriefcaseManager.js.map +1 -1
  90. package/lib/esm/CloudSqlite.d.ts +4 -0
  91. package/lib/esm/CloudSqlite.d.ts.map +1 -1
  92. package/lib/esm/CloudSqlite.js.map +1 -1
  93. package/lib/esm/ECDb.d.ts +8 -0
  94. package/lib/esm/ECDb.d.ts.map +1 -1
  95. package/lib/esm/ECDb.js +22 -0
  96. package/lib/esm/ECDb.js.map +1 -1
  97. package/lib/esm/IModelDb.d.ts +54 -3
  98. package/lib/esm/IModelDb.d.ts.map +1 -1
  99. package/lib/esm/IModelDb.js +88 -10
  100. package/lib/esm/IModelDb.js.map +1 -1
  101. package/lib/esm/IModelHost.d.ts +11 -1
  102. package/lib/esm/IModelHost.d.ts.map +1 -1
  103. package/lib/esm/IModelHost.js +5 -0
  104. package/lib/esm/IModelHost.js.map +1 -1
  105. package/lib/esm/IModelIncrementalSchemaLocater.d.ts +1 -5
  106. package/lib/esm/IModelIncrementalSchemaLocater.d.ts.map +1 -1
  107. package/lib/esm/IModelIncrementalSchemaLocater.js +0 -6
  108. package/lib/esm/IModelIncrementalSchemaLocater.js.map +1 -1
  109. package/lib/esm/SqliteChangesetReader.d.ts +8 -0
  110. package/lib/esm/SqliteChangesetReader.d.ts.map +1 -1
  111. package/lib/esm/SqliteChangesetReader.js +11 -0
  112. package/lib/esm/SqliteChangesetReader.js.map +1 -1
  113. package/lib/esm/StashManager.d.ts +175 -0
  114. package/lib/esm/StashManager.d.ts.map +1 -0
  115. package/lib/esm/StashManager.js +301 -0
  116. package/lib/esm/StashManager.js.map +1 -0
  117. package/lib/esm/TxnManager.d.ts +226 -15
  118. package/lib/esm/TxnManager.d.ts.map +1 -1
  119. package/lib/esm/TxnManager.js +247 -21
  120. package/lib/esm/TxnManager.js.map +1 -1
  121. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts +10 -1
  122. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  123. package/lib/esm/annotations/ElementDrivesTextAnnotation.js +13 -5
  124. package/lib/esm/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  125. package/lib/esm/annotations/LeaderGeometry.d.ts +3 -2
  126. package/lib/esm/annotations/LeaderGeometry.d.ts.map +1 -1
  127. package/lib/esm/annotations/LeaderGeometry.js +5 -4
  128. package/lib/esm/annotations/LeaderGeometry.js.map +1 -1
  129. package/lib/esm/annotations/TextAnnotationElement.d.ts +52 -24
  130. package/lib/esm/annotations/TextAnnotationElement.d.ts.map +1 -1
  131. package/lib/esm/annotations/TextAnnotationElement.js +51 -61
  132. package/lib/esm/annotations/TextAnnotationElement.js.map +1 -1
  133. package/lib/esm/annotations/TextAnnotationGeometry.d.ts +2 -0
  134. package/lib/esm/annotations/TextAnnotationGeometry.d.ts.map +1 -1
  135. package/lib/esm/annotations/TextAnnotationGeometry.js +26 -19
  136. package/lib/esm/annotations/TextAnnotationGeometry.js.map +1 -1
  137. package/lib/esm/annotations/TextBlockGeometry.d.ts.map +1 -1
  138. package/lib/esm/annotations/TextBlockGeometry.js +8 -0
  139. package/lib/esm/annotations/TextBlockGeometry.js.map +1 -1
  140. package/lib/esm/annotations/TextBlockLayout.d.ts +49 -36
  141. package/lib/esm/annotations/TextBlockLayout.d.ts.map +1 -1
  142. package/lib/esm/annotations/TextBlockLayout.js +205 -136
  143. package/lib/esm/annotations/TextBlockLayout.js.map +1 -1
  144. package/lib/esm/internal/ChannelAdmin.js +1 -1
  145. package/lib/esm/internal/ChannelAdmin.js.map +1 -1
  146. package/lib/esm/internal/Symbols.d.ts +1 -0
  147. package/lib/esm/internal/Symbols.d.ts.map +1 -1
  148. package/lib/esm/internal/Symbols.js +1 -0
  149. package/lib/esm/internal/Symbols.js.map +1 -1
  150. package/lib/esm/internal/annotations/fields.d.ts +2 -12
  151. package/lib/esm/internal/annotations/fields.d.ts.map +1 -1
  152. package/lib/esm/internal/annotations/fields.js +51 -47
  153. package/lib/esm/internal/annotations/fields.js.map +1 -1
  154. package/lib/esm/test/AnnotationTestUtils.d.ts +5 -1
  155. package/lib/esm/test/AnnotationTestUtils.d.ts.map +1 -1
  156. package/lib/esm/test/AnnotationTestUtils.js +6 -1
  157. package/lib/esm/test/AnnotationTestUtils.js.map +1 -1
  158. package/lib/esm/test/annotations/Fields.test.js +163 -46
  159. package/lib/esm/test/annotations/Fields.test.js.map +1 -1
  160. package/lib/esm/test/annotations/LeaderGeometry.test.js +12 -10
  161. package/lib/esm/test/annotations/LeaderGeometry.test.js.map +1 -1
  162. package/lib/esm/test/annotations/TextAnnotation.test.js +299 -43
  163. package/lib/esm/test/annotations/TextAnnotation.test.js.map +1 -1
  164. package/lib/esm/test/annotations/TextBlock.test.js +453 -86
  165. package/lib/esm/test/annotations/TextBlock.test.js.map +1 -1
  166. package/lib/esm/test/assets/IncrementalSchemaLocater/configs/simple.config.d.ts +46 -0
  167. package/lib/esm/test/assets/IncrementalSchemaLocater/configs/simple.config.d.ts.map +1 -1
  168. package/lib/esm/test/assets/IncrementalSchemaLocater/configs/simple.config.js +20 -2
  169. package/lib/esm/test/assets/IncrementalSchemaLocater/configs/simple.config.js.map +1 -1
  170. package/lib/esm/test/ecdb/ECDb.test.js +71 -1
  171. package/lib/esm/test/ecdb/ECDb.test.js.map +1 -1
  172. package/lib/esm/test/hubaccess/Rebase.test.d.ts +2 -0
  173. package/lib/esm/test/hubaccess/Rebase.test.d.ts.map +1 -0
  174. package/lib/esm/test/hubaccess/Rebase.test.js +640 -0
  175. package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -0
  176. package/lib/esm/test/incrementalSchemaLocater/ECSqlQueries.test.js +20 -20
  177. package/lib/esm/test/incrementalSchemaLocater/ECSqlQueries.test.js.map +1 -1
  178. package/lib/esm/test/incrementalSchemaLocater/IncrementalLoading.test.js +3 -3
  179. package/lib/esm/test/incrementalSchemaLocater/IncrementalLoading.test.js.map +1 -1
  180. package/lib/esm/test/incrementalSchemaLocater/utils/TestSqlSchemaLocater.d.ts +16 -1
  181. package/lib/esm/test/incrementalSchemaLocater/utils/TestSqlSchemaLocater.d.ts.map +1 -1
  182. package/lib/esm/test/incrementalSchemaLocater/utils/TestSqlSchemaLocater.js +47 -0
  183. package/lib/esm/test/incrementalSchemaLocater/utils/TestSqlSchemaLocater.js.map +1 -1
  184. package/lib/esm/test/standalone/ChangeMerge.test.js +15 -19
  185. package/lib/esm/test/standalone/ChangeMerge.test.js.map +1 -1
  186. package/lib/esm/test/standalone/ChangesetReader.test.js +131 -1
  187. package/lib/esm/test/standalone/ChangesetReader.test.js.map +1 -1
  188. package/lib/esm/test/standalone/MergeConflict.test.js +3 -3
  189. package/lib/esm/test/standalone/MergeConflict.test.js.map +1 -1
  190. package/lib/esm/workspace/Workspace.d.ts +1 -1
  191. package/lib/esm/workspace/Workspace.js.map +1 -1
  192. package/package.json +13 -13
@@ -5,7 +5,7 @@
5
5
  import { expect } from "chai";
6
6
  import { computeGraphemeOffsets, layoutTextBlock, TextStyleResolver } from "../../annotations/TextBlockLayout";
7
7
  import { Geometry } from "@itwin/core-geometry";
8
- import { ColorDef, FontType, FractionRun, LineBreakRun, Paragraph, TabRun, TextAnnotation, TextBlock, TextRun, TextStyleSettings } from "@itwin/core-common";
8
+ import { ColorDef, FontType, FractionRun, LineBreakRun, List, ListMarkerEnumerator, Paragraph, TabRun, TextAnnotation, TextBlock, TextRun, TextStyleSettings } from "@itwin/core-common";
9
9
  import { IModelTestUtils } from "../IModelTestUtils";
10
10
  import { ProcessDetector } from "@itwin/core-bentley";
11
11
  import { produceTextBlockGeometry } from "../../core-backend";
@@ -28,11 +28,12 @@ function findTextStyleImpl(id) {
28
28
  describe("layoutTextBlock", () => {
29
29
  describe("resolves TextStyleSettings", () => {
30
30
  it("inherits styling from TextBlock when Paragraph and Run have no style overrides", () => {
31
- const textBlock = TextBlock.create({ styleId: "0x42" });
31
+ const textBlock = TextBlock.create();
32
32
  const run = TextRun.create({ content: "test" });
33
33
  textBlock.appendParagraph();
34
34
  textBlock.appendRun(run);
35
35
  const tb = doLayout(textBlock, {
36
+ textStyleId: "0x42",
36
37
  findTextStyle: findTextStyleImpl,
37
38
  });
38
39
  expect(tb.lines.length).to.equal(1);
@@ -43,12 +44,11 @@ describe("layoutTextBlock", () => {
43
44
  expect(runStyle.isBold).to.be.true;
44
45
  });
45
46
  it("inherits style overrides from Paragraph when Run has no style overrides", () => {
46
- const textBlock = TextBlock.create({ styleId: "0x42" });
47
- const paragraph = Paragraph.create({ styleOverrides: { fontName: "paragraph" } });
48
- const run = TextRun.create({ content: "test" });
49
- textBlock.paragraphs.push(paragraph);
50
- textBlock.appendRun(run);
47
+ const textBlock = TextBlock.create();
48
+ textBlock.appendParagraph({ styleOverrides: { fontName: "paragraph" } });
49
+ textBlock.appendRun(TextRun.create({ content: "test" }));
51
50
  const tb = doLayout(textBlock, {
51
+ textStyleId: "0x42",
52
52
  findTextStyle: findTextStyleImpl,
53
53
  });
54
54
  expect(tb.lines.length).to.equal(1);
@@ -58,12 +58,11 @@ describe("layoutTextBlock", () => {
58
58
  expect(runStyle.isBold).to.be.true;
59
59
  });
60
60
  it("uses Run style overrides when Run has overrides", () => {
61
- const textBlock = TextBlock.create({ styleId: "0x42" });
62
- const paragraph = Paragraph.create({ styleOverrides: { lineSpacingFactor: 55, fontName: "paragraph" } });
63
- const run = TextRun.create({ content: "test", styleOverrides: { lineSpacingFactor: 99, fontName: "run" } });
64
- textBlock.paragraphs.push(paragraph);
65
- textBlock.appendRun(run);
61
+ const textBlock = TextBlock.create();
62
+ textBlock.appendParagraph({ styleOverrides: { lineSpacingFactor: 55, fontName: "paragraph" } });
63
+ textBlock.appendRun(TextRun.create({ content: "test", styleOverrides: { lineSpacingFactor: 99, fontName: "run" } }));
66
64
  const tb = doLayout(textBlock, {
65
+ textStyleId: "0x42",
67
66
  findTextStyle: findTextStyleImpl,
68
67
  });
69
68
  expect(tb.lines.length).to.equal(1);
@@ -73,12 +72,13 @@ describe("layoutTextBlock", () => {
73
72
  expect(runStyle.isBold).to.be.true;
74
73
  });
75
74
  it("still uses TextBlock specific styles when Run has style overrides", () => {
76
- // Some style settings only make sense on a TextBlock, so they are always applied from the TextBlock, even if the Run has a style override.
77
- const textBlock = TextBlock.create({ styleId: "0x42" });
75
+ // Some style settings make sense on a TextBlock, so they are always applied from the TextBlock, even if the Run has a style override.
76
+ const textBlock = TextBlock.create();
78
77
  const run = TextRun.create({ content: "test", styleOverrides: { lineSpacingFactor: 99, fontName: "run" } });
79
78
  textBlock.appendParagraph();
80
79
  textBlock.appendRun(run);
81
80
  const tb = doLayout(textBlock, {
81
+ textStyleId: "0x42",
82
82
  findTextStyle: findTextStyleImpl,
83
83
  });
84
84
  expect(tb.lines.length).to.equal(1);
@@ -87,10 +87,9 @@ describe("layoutTextBlock", () => {
87
87
  expect(runStyle.lineSpacingFactor).to.equal(12);
88
88
  });
89
89
  it("inherits overrides from TextBlock, Paragraph and Run when there is no styleId", () => {
90
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { widthFactor: 34, lineHeight: 3, lineSpacingFactor: 12, isBold: true } });
91
- const paragraph = Paragraph.create({ styleOverrides: { lineHeight: 56, color: 0xff0000, frame: { shape: "octagon" } } });
90
+ const textBlock = TextBlock.create({ styleOverrides: { widthFactor: 34, lineHeight: 3, lineSpacingFactor: 12, isBold: true } });
92
91
  const run = TextRun.create({ content: "test", styleOverrides: { widthFactor: 78, fontName: "override", leader: { wantElbow: true } } });
93
- textBlock.paragraphs.push(paragraph);
92
+ textBlock.appendParagraph({ styleOverrides: { lineHeight: 56, color: 0xff0000, frame: { shape: "octagon" } } });
94
93
  textBlock.appendRun(run);
95
94
  const tb = doLayout(textBlock, {
96
95
  findTextStyle: findTextStyleImpl,
@@ -113,12 +112,12 @@ describe("layoutTextBlock", () => {
113
112
  expect(runStyle.isBold).to.be.true;
114
113
  });
115
114
  it("does not inherit overrides in TextBlock or Paragraph when Run has same propertied overriden - unless they are TextBlock specific settings", () => {
116
- const textBlock = TextBlock.create({ styleId: "0x42", styleOverrides: { widthFactor: 34, lineHeight: 3, lineSpacingFactor: 12, isBold: true } });
117
- const paragraph = Paragraph.create({ styleOverrides: { lineHeight: 56, color: 0xff0000 } });
115
+ const textBlock = TextBlock.create({ styleOverrides: { widthFactor: 34, lineHeight: 3, lineSpacingFactor: 12, isBold: true } });
118
116
  const run = TextRun.create({ content: "test", styleOverrides: { widthFactor: 78, lineHeight: 6, lineSpacingFactor: 24, fontName: "override", isBold: false } });
119
- textBlock.paragraphs.push(paragraph);
117
+ textBlock.appendParagraph({ styleOverrides: { lineHeight: 56, color: 0xff0000 } });
120
118
  textBlock.appendRun(run);
121
119
  const tb = doLayout(textBlock, {
120
+ textStyleId: "0x42",
122
121
  findTextStyle: findTextStyleImpl,
123
122
  });
124
123
  expect(tb.lines.length).to.equal(1);
@@ -136,10 +135,9 @@ describe("layoutTextBlock", () => {
136
135
  });
137
136
  it("takes child overrides over parent overrides", () => {
138
137
  //...unless they are TextBlock specific as covered in other tests
139
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { fontName: "grandparent" } });
140
- const paragraph = Paragraph.create({ styleOverrides: { fontName: "parent" } });
138
+ const textBlock = TextBlock.create({ styleOverrides: { fontName: "grandparent" } });
141
139
  const run = TextRun.create({ content: "test", styleOverrides: { fontName: "child" } });
142
- textBlock.paragraphs.push(paragraph);
140
+ textBlock.appendParagraph({ styleOverrides: { fontName: "parent" } });
143
141
  textBlock.appendRun(run);
144
142
  const tb = doLayout(textBlock, {
145
143
  findTextStyle: findTextStyleImpl,
@@ -155,7 +153,7 @@ describe("layoutTextBlock", () => {
155
153
  this.skip();
156
154
  }
157
155
  // Initialize a new TextBlockLayout object
158
- const textBlock = TextBlock.create({ width: 50, styleId: "", styleOverrides: { widthFactor: 34, color: 0x00ff00, fontName: "arial" } });
156
+ const textBlock = TextBlock.create({ width: 50, styleOverrides: { widthFactor: 34, color: 0x00ff00, fontName: "arial" } });
159
157
  const run0 = TextRun.create({
160
158
  content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus pretium mi sit amet magna malesuada, at venenatis ante eleifend.",
161
159
  styleOverrides: { lineHeight: 56, color: 0xff0000 },
@@ -194,8 +192,6 @@ describe("layoutTextBlock", () => {
194
192
  for (let i = 0; i < result.lines.length; i++) {
195
193
  const resultLine = result.lines[i];
196
194
  const originalLine = textBlockLayout.lines[i];
197
- // Source paragraph index matches
198
- expect(resultLine.sourceParagraphIndex).to.equal(textBlock.paragraphs.indexOf(originalLine.source));
199
195
  // Ranges match
200
196
  expect(resultLine.range).to.deep.equal(originalLine.range.toJSON());
201
197
  expect(resultLine.justificationRange).to.deep.equal(originalLine.justificationRange.toJSON());
@@ -204,8 +200,6 @@ describe("layoutTextBlock", () => {
204
200
  for (let j = 0; j < resultLine.runs.length; j++) {
205
201
  const resultRun = resultLine.runs[j];
206
202
  const originalRun = originalLine.runs[j];
207
- // Source run index matches
208
- expect(resultRun.sourceRunIndex).to.equal(textBlock.paragraphs[resultLine.sourceParagraphIndex].runs.indexOf(originalRun.source));
209
203
  // FontId matches
210
204
  expect(resultRun.fontId).to.equal(originalRun.fontId);
211
205
  // Offsets match
@@ -245,7 +239,7 @@ describe("layoutTextBlock", () => {
245
239
  expect(resultRun.denominatorRange).to.deep.equal(originalRun.denominatorRange.toJSON());
246
240
  }
247
241
  // Check that the result string matches what we expect
248
- const inputRun = textBlock.paragraphs[resultLine.sourceParagraphIndex].runs[resultRun.sourceRunIndex].clone();
242
+ const inputRun = originalRun.source;
249
243
  if (inputRun.type === "text") {
250
244
  const resultText = inputRun.content.substring(resultRun.characterOffset, resultRun.characterOffset + resultRun.characterCount);
251
245
  const originalText = inputRun.content.substring(originalRun.charOffset, originalRun.charOffset + originalRun.numChars);
@@ -262,7 +256,7 @@ describe("layoutTextBlock", () => {
262
256
  expect(marginRange.high.y).to.equal(layoutRange.high.y + (margins.top ?? 0));
263
257
  };
264
258
  const makeTextBlock = (margins) => {
265
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { lineSpacingFactor: 0 }, margins });
259
+ const textBlock = TextBlock.create({ styleOverrides: { lineSpacingFactor: 0 }, margins });
266
260
  textBlock.appendRun(makeTextRun("abc"));
267
261
  textBlock.appendRun(makeTextRun("defg"));
268
262
  return textBlock;
@@ -287,7 +281,7 @@ describe("layoutTextBlock", () => {
287
281
  });
288
282
  describe("range", () => {
289
283
  it("aligns text to center based on height of stacked fraction", () => {
290
- const textBlock = TextBlock.create({ styleId: "" });
284
+ const textBlock = TextBlock.create();
291
285
  const fractionRun = FractionRun.create({ numerator: "1", denominator: "2" });
292
286
  const textRun = TextRun.create({ content: "text" });
293
287
  textBlock.appendRun(fractionRun);
@@ -305,7 +299,9 @@ describe("layoutTextBlock", () => {
305
299
  expect(round(textLayout.offsetFromLine.y, 3)).to.equal(.375);
306
300
  });
307
301
  it("produces one line per paragraph if document width <= 0", () => {
308
- const textBlock = TextBlock.create({ styleId: "" });
302
+ const lineSpacingFactor = 0.5;
303
+ const paragraphSpacingFactor = 0.25;
304
+ const textBlock = TextBlock.create({ styleOverrides: { paragraphSpacingFactor, lineSpacingFactor } });
309
305
  for (let i = 0; i < 4; i++) {
310
306
  const layout = doLayout(textBlock);
311
307
  if (i === 0) {
@@ -314,7 +310,7 @@ describe("layoutTextBlock", () => {
314
310
  else {
315
311
  expect(layout.lines.length).to.equal(i);
316
312
  expect(layout.range.low.x).to.equal(0);
317
- expect(layout.range.low.y).to.equal(-i - (0.5 * (i - 1))); // lineSpacingFactor=0.5
313
+ expect(layout.range.low.y).to.equal(-i - ((i - 1) * (lineSpacingFactor + paragraphSpacingFactor)));
318
314
  expect(layout.range.high.x).to.equal(i * 3);
319
315
  expect(layout.range.high.y).to.equal(0);
320
316
  }
@@ -336,14 +332,14 @@ describe("layoutTextBlock", () => {
336
332
  }
337
333
  const p = textBlock.appendParagraph();
338
334
  for (let j = 0; j <= i; j++) {
339
- p.runs.push(TextRun.create({ content: "Run" }));
335
+ p.children.push(TextRun.create({ content: "Run" }));
340
336
  }
341
337
  }
342
338
  });
343
339
  it("produces a new line for each LineBreakRun", () => {
344
340
  const lineSpacingFactor = 0.5;
345
341
  const lineHeight = 1;
346
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { lineSpacingFactor, lineHeight } });
342
+ const textBlock = TextBlock.create({ styleOverrides: { lineSpacingFactor, lineHeight } });
347
343
  textBlock.appendRun(TextRun.create({ content: "abc" }));
348
344
  textBlock.appendRun(LineBreakRun.create());
349
345
  textBlock.appendRun(TextRun.create({ content: "def" }));
@@ -358,12 +354,13 @@ describe("layoutTextBlock", () => {
358
354
  expect(tb.range.low.x).to.equal(0);
359
355
  expect(tb.range.high.x).to.equal(6);
360
356
  expect(tb.range.high.y).to.equal(0);
357
+ // paragraphSpacingFactor should not be applied to linebreaks, but lineSpacingFactor should.
361
358
  expect(tb.range.low.y).to.equal(-(lineSpacingFactor * 2 + lineHeight * 3));
362
359
  });
363
360
  it("applies tab shifts", () => {
364
361
  const lineHeight = 1;
365
362
  const tabInterval = 6;
366
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { lineHeight, tabInterval } });
363
+ const textBlock = TextBlock.create({ styleOverrides: { lineHeight, tabInterval } });
367
364
  // Appends a line that looks like `stringOne` TAB `stringTwo` LINEBREAK
368
365
  const appendLine = (stringOne, stringTwo, wantLineBreak = true) => {
369
366
  if (stringOne.length > 0)
@@ -374,15 +371,15 @@ describe("layoutTextBlock", () => {
374
371
  if (wantLineBreak)
375
372
  textBlock.appendRun(LineBreakRun.create());
376
373
  };
377
- // The extra whitespace is intentional to show where the tab stops should be.
378
- appendLine("", "a");
379
- appendLine("", "bc");
380
- appendLine("a", "a");
381
- appendLine("bc", "bc");
382
- appendLine("cde", "cde");
383
- appendLine("cdefg", "cde"); // this one is the max tab distance before needing to move to the next tab stop
384
- appendLine("cdefgh", "cde"); // This one should push to the next tab stop.
385
- appendLine("cdefghi", "cde", false); // This one should push to the next tab stop.
374
+ // The extra comments are intentional to show where the tab stops should be.
375
+ appendLine("", /*______*/ "a");
376
+ appendLine("", /*______*/ "bc");
377
+ appendLine("a", /*_____*/ "a");
378
+ appendLine("bc", /*____*/ "bc");
379
+ appendLine("cde", /*___*/ "cde");
380
+ appendLine("cdefg", /*_*/ "cde"); // this one is the max tab distance before needing to move to the next tab stop
381
+ appendLine("cdefgh", /*______*/ "cde"); // This one should push to the next tab stop.
382
+ appendLine("cdefghi", /*_____*/ "cde", false); // This one should push to the next tab stop.
386
383
  const tb = doLayout(textBlock);
387
384
  tb.lines.forEach((line, index) => {
388
385
  const firstTextRun = (line.runs[0].source.type === "text") ? line.runs[0] : undefined;
@@ -395,7 +392,7 @@ describe("layoutTextBlock", () => {
395
392
  it("applies consecutive tab shifts", () => {
396
393
  const lineHeight = 1;
397
394
  const tabInterval = 6;
398
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { lineHeight, tabInterval } });
395
+ const textBlock = TextBlock.create({ styleOverrides: { lineHeight, tabInterval } });
399
396
  // line 0: ----->----->----->LINEBREAK
400
397
  textBlock.appendRun(TabRun.create({ styleOverrides: { tabInterval } }));
401
398
  textBlock.appendRun(TabRun.create({ styleOverrides: { tabInterval } }));
@@ -426,7 +423,7 @@ describe("layoutTextBlock", () => {
426
423
  const line2 = tb.lines[2];
427
424
  const line3 = tb.lines[3];
428
425
  expect(line0.runs.length).to.equal(4);
429
- expect(line0.range.xLength()).to.equal(3 * tabInterval, `Lines with only tabs should have the correct range length`);
426
+ expect(line0.range.xLength()).to.equal(3 * tabInterval, `Lines with tabs should have the correct range length`);
430
427
  expect(line1.runs.length).to.equal(4);
431
428
  expect(line1.range.xLength()).to.equal(2 * tabInterval, `Tabs should be applied correctly when they are at the end of a line`);
432
429
  expect(line2.runs.length).to.equal(5);
@@ -434,10 +431,12 @@ describe("layoutTextBlock", () => {
434
431
  expect(line3.runs.length).to.equal(7);
435
432
  expect(line3.range.xLength()).to.equal(7 + 3 + 7, `Multiple tabs with different intervals should be applied correctly`);
436
433
  });
437
- it("computes ranges based on custom line spacing and line height", () => {
434
+ it("computes ranges based on custom line spacing, line height, and indentation", () => {
438
435
  const lineSpacingFactor = 2;
439
436
  const lineHeight = 3;
440
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { lineSpacingFactor, lineHeight } });
437
+ const paragraphSpacingFactor = 13;
438
+ const indentation = 7;
439
+ const textBlock = TextBlock.create({ styleOverrides: { lineSpacingFactor, lineHeight, paragraphSpacingFactor, indentation } });
441
440
  textBlock.appendRun(TextRun.create({ content: "abc" }));
442
441
  textBlock.appendRun(LineBreakRun.create());
443
442
  textBlock.appendRun(TextRun.create({ content: "def" }));
@@ -449,15 +448,85 @@ describe("layoutTextBlock", () => {
449
448
  expect(tb.lines[0].runs.length).to.equal(2);
450
449
  expect(tb.lines[1].runs.length).to.equal(3);
451
450
  expect(tb.lines[2].runs.length).to.equal(1);
452
- // We have 3 lines each `lineHeight` high, plus 2 line breaks in between each `lineHeight*lineSpacingFactor` high.
453
- expect(tb.range.low.x).to.equal(0);
454
- expect(tb.range.high.x).to.equal(6);
451
+ /* Final TextBlock should look like:
452
+ ⇥abc↵
453
+ ⇥defghi↵
454
+ ⇥jkl
455
+
456
+ Where ↵ = LineBreak, ¶ = ParagraphBreak, ⇥ = indentation
457
+
458
+ We have 3 lines each `lineHeight` high, plus 2 line breaks in between each `lineHeight*lineSpacingFactor` high.
459
+ No paragraph spacing should be applied since there is one paragraph.
460
+ */
461
+ expect(tb.range.low.x).to.equal(7);
462
+ expect(tb.range.high.x).to.equal(6 + 7); // 7 for indentation, 6 for the length of "defghi"
455
463
  expect(tb.range.high.y).to.equal(0);
456
464
  expect(tb.range.low.y).to.equal(-(lineHeight * 3 + (lineHeight * lineSpacingFactor) * 2));
457
465
  expect(tb.lines[0].offsetFromDocument.y).to.equal(-lineHeight);
458
466
  expect(tb.lines[1].offsetFromDocument.y).to.equal(tb.lines[0].offsetFromDocument.y - (lineHeight + lineHeight * lineSpacingFactor));
459
467
  expect(tb.lines[2].offsetFromDocument.y).to.equal(tb.lines[1].offsetFromDocument.y - (lineHeight + lineHeight * lineSpacingFactor));
460
- expect(tb.lines.every((line) => line.offsetFromDocument.x === 0)).to.be.true;
468
+ tb.lines.forEach((line) => expect(line.offsetFromDocument.x).to.equal(7));
469
+ });
470
+ it("computes paragraph spacing and indentation", () => {
471
+ const lineSpacingFactor = 2;
472
+ const lineHeight = 3;
473
+ const paragraphSpacingFactor = 13;
474
+ const indentation = 7;
475
+ const tabInterval = 5;
476
+ const textBlock = TextBlock.create({ styleOverrides: { lineSpacingFactor, lineHeight, paragraphSpacingFactor, indentation, tabInterval } });
477
+ const p1 = textBlock.appendParagraph();
478
+ p1.children.push(TextRun.create({ content: "abc" })); // Line 1
479
+ p1.children.push(LineBreakRun.create());
480
+ p1.children.push(TextRun.create({ content: "def" })); // Line 2
481
+ const p2 = textBlock.appendParagraph();
482
+ p2.children.push(TextRun.create({ content: "ghi" })); // Line 3
483
+ const list = List.create();
484
+ list.children.push(Paragraph.create({ children: [{ type: "text", content: "list item 1" }] })); // Line 4
485
+ list.children.push(Paragraph.create({ children: [{ type: "text", content: "list item 2" }] })); // Line 5
486
+ list.children.push(Paragraph.create({ children: [{ type: "text", content: "list item 3" }] })); // Line 6
487
+ p2.children.push(list);
488
+ const tb = doLayout(textBlock);
489
+ expect(tb.lines.length).to.equal(6);
490
+ /* Final TextBlock should look like:
491
+ ⇥abc↵
492
+ ⇥def¶
493
+ ⇥ghi¶
494
+ ⇥→1. list item 1¶
495
+ ⇥→2. list item 2¶
496
+ ⇥→3. list item 3
497
+
498
+ Where ↵ = LineBreak, ¶ = ParagraphBreak, → = tabInterval/2, ⇥ = indentation
499
+
500
+ We have:
501
+ 6 lines each `lineHeight` high
502
+ 5 line breaks in between each `lineHeight*lineSpacingFactor` high
503
+ 4 paragraph breaks in between each `lineHeight*paragraphSpacingFactor` high
504
+ */
505
+ expect(tb.range.low.x).to.equal(7); // 7 for indentation
506
+ expect(tb.range.high.x).to.equal(7 + 5 + 11); // 7 for indentation, 5 for the tab stop, 11 for the length of "list item 1"
507
+ expect(tb.range.high.y).to.equal(0);
508
+ expect(tb.range.low.y).to.equal(-(lineHeight * 6 + (lineHeight * lineSpacingFactor) * 5 + (lineHeight * paragraphSpacingFactor) * 4));
509
+ // Cumulative vertical offsets to help make the test more readable.
510
+ let offsetY = -lineHeight;
511
+ let offsetX = indentation;
512
+ expect(tb.lines[0].offsetFromDocument.y).to.equal(offsetY);
513
+ expect(tb.lines[0].offsetFromDocument.x).to.equal(offsetX);
514
+ offsetY -= (lineHeight + lineHeight * lineSpacingFactor);
515
+ expect(tb.lines[1].offsetFromDocument.y).to.equal(offsetY);
516
+ expect(tb.lines[1].offsetFromDocument.x).to.equal(offsetX);
517
+ offsetY -= (lineHeight + lineHeight * lineSpacingFactor + lineHeight * paragraphSpacingFactor);
518
+ expect(tb.lines[2].offsetFromDocument.y).to.equal(offsetY);
519
+ expect(tb.lines[2].offsetFromDocument.x).to.equal(offsetX);
520
+ offsetX += tabInterval; // List items are indented using tabInterval.
521
+ offsetY -= (lineHeight + lineHeight * lineSpacingFactor + lineHeight * paragraphSpacingFactor);
522
+ expect(tb.lines[3].offsetFromDocument.y).to.equal(offsetY);
523
+ expect(tb.lines[3].offsetFromDocument.x).to.equal(offsetX);
524
+ offsetY -= (lineHeight + lineHeight * lineSpacingFactor + lineHeight * paragraphSpacingFactor);
525
+ expect(tb.lines[4].offsetFromDocument.y).to.equal(offsetY);
526
+ expect(tb.lines[4].offsetFromDocument.x).to.equal(offsetX);
527
+ offsetY -= (lineHeight + lineHeight * lineSpacingFactor + lineHeight * paragraphSpacingFactor);
528
+ expect(tb.lines[5].offsetFromDocument.y).to.equal(offsetY);
529
+ expect(tb.lines[5].offsetFromDocument.x).to.equal(offsetX);
461
530
  });
462
531
  function expectRange(width, height, range) {
463
532
  expect(range.xLength()).to.equal(width);
@@ -467,7 +536,7 @@ describe("layoutTextBlock", () => {
467
536
  if (!isIntlSupported()) {
468
537
  this.skip();
469
538
  }
470
- const block = TextBlock.create({ styleId: "", width: 3, styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
539
+ const block = TextBlock.create({ width: 3, styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
471
540
  function expectBlockRange(width, height) {
472
541
  const layout = doLayout(block);
473
542
  expectRange(width, height, layout.range);
@@ -491,7 +560,7 @@ describe("layoutTextBlock", () => {
491
560
  if (!isIntlSupported()) {
492
561
  this.skip();
493
562
  }
494
- const block = TextBlock.create({ styleId: "", styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
563
+ const block = TextBlock.create({ styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
495
564
  function expectBlockRange(width, height) {
496
565
  const layout = doLayout(block);
497
566
  expectRange(width, height, layout.range);
@@ -504,11 +573,145 @@ describe("layoutTextBlock", () => {
504
573
  block.width = 10;
505
574
  expectBlockRange(10, 2);
506
575
  });
576
+ it("computes range for list markers and list items based on indentation", function () {
577
+ const lineSpacingFactor = 2;
578
+ const lineHeight = 3;
579
+ const paragraphSpacingFactor = 13;
580
+ const indentation = 7;
581
+ const tabInterval = 5;
582
+ const listChildren = [
583
+ {
584
+ children: [
585
+ {
586
+ type: "text",
587
+ content: "Oranges",
588
+ }
589
+ ]
590
+ },
591
+ {
592
+ children: [
593
+ {
594
+ type: "text",
595
+ content: "Apples",
596
+ },
597
+ {
598
+ type: "list",
599
+ styleOverrides: { listMarker: { enumerator: ListMarkerEnumerator.Bullet } },
600
+ children: [
601
+ {
602
+ children: [
603
+ {
604
+ type: "text",
605
+ content: "Red",
606
+ }
607
+ ]
608
+ },
609
+ {
610
+ children: [
611
+ {
612
+ type: "text",
613
+ content: "Green",
614
+ },
615
+ {
616
+ type: "list",
617
+ styleOverrides: { listMarker: { enumerator: ListMarkerEnumerator.RomanNumeral, case: "lower", terminator: "period" } },
618
+ children: [
619
+ {
620
+ children: [
621
+ {
622
+ type: "text",
623
+ content: "Granny Smith",
624
+ }
625
+ ]
626
+ },
627
+ {
628
+ children: [
629
+ {
630
+ type: "text",
631
+ content: "Rhode Island Greening",
632
+ }
633
+ ]
634
+ }
635
+ ]
636
+ }
637
+ ]
638
+ },
639
+ {
640
+ children: [
641
+ {
642
+ type: "text",
643
+ content: "Yellow",
644
+ }
645
+ ]
646
+ }
647
+ ]
648
+ }
649
+ ]
650
+ }
651
+ ];
652
+ const textBlock = TextBlock.create({ styleOverrides: { lineSpacingFactor, lineHeight, paragraphSpacingFactor, indentation, tabInterval } });
653
+ const p1 = textBlock.appendParagraph();
654
+ p1.children.push(List.create({ children: listChildren }));
655
+ /* Final TextBlock should look like:
656
+ →1.→Oranges¶
657
+ →2.→Apples¶
658
+ →→•→Red¶
659
+ →→•→Green¶
660
+ → →→i. →Granny Smith¶
661
+ → →→ii.→Rhode Island Greening¶
662
+ →→•→Yellow
663
+
664
+ Where ↵ = LineBreak, ¶ = ParagraphBreak, → = tab, → = tabInterval/2, ⇥ = indentation
665
+
666
+ We have:
667
+ 7 lines each `lineHeight` high
668
+ 6 line breaks in between each `lineHeight*lineSpacingFactor` high
669
+ 6 paragraph breaks in between each `lineHeight*paragraphSpacingFactor` high
670
+ */
671
+ const tb = doLayout(textBlock);
672
+ expect(tb.lines.length).to.equal(7);
673
+ expect(tb.range.low.x).to.equal(7 + 5 - 5 / 2 - 2); // indentation + tabInterval - tabInterval/2 (for marker offset) + 2 (for the marker "1." justification, it's 2 characters wide)
674
+ expect(tb.range.high.x).to.equal(7 + 3 * 5 + 21); // 7 for indentation, 3 * 5 for the most nested tab stops, 21 for the length of "Rhode Island Greening"
675
+ expect(tb.range.high.y).to.equal(0);
676
+ expect(tb.range.low.y).to.equal(-(lineHeight * 7 + (lineHeight * lineSpacingFactor) * 6 + (lineHeight * paragraphSpacingFactor) * 6));
677
+ // Cumulative vertical offsets to help make the test more readable.
678
+ let offsetY = -lineHeight;
679
+ for (const line of tb.lines) {
680
+ expect(line.offsetFromDocument.y).to.equal(offsetY);
681
+ expect(line.marker).to.not.be.undefined;
682
+ expect(line.marker?.offsetFromLine.y).to.equal((lineHeight - line.marker.range.yLength()) / 2);
683
+ offsetY -= (lineHeight + lineHeight * lineSpacingFactor + lineHeight * paragraphSpacingFactor);
684
+ }
685
+ let markerXLength = tb.lines[0].marker.range.xLength();
686
+ let inset = indentation + tabInterval;
687
+ expect(tb.lines[0].offsetFromDocument.x).to.equal(inset); // →Oranges
688
+ expect(markerXLength).to.equal(2); // "1." is 2 characters wide
689
+ expect(tb.lines[0].marker.offsetFromLine.x).to.equal(0 - markerXLength - (tabInterval / 2));
690
+ markerXLength = tb.lines[1].marker.range.xLength();
691
+ expect(tb.lines[1].offsetFromDocument.x).to.equal(inset); // →Apples
692
+ expect(tb.lines[1].marker.offsetFromLine.x).to.equal(0 - markerXLength - (tabInterval / 2));
693
+ markerXLength = tb.lines[2].marker.range.xLength();
694
+ inset = indentation + tabInterval * 2;
695
+ expect(tb.lines[2].offsetFromDocument.x).to.equal(indentation + tabInterval * 2); // →→Red
696
+ expect(tb.lines[2].marker.offsetFromLine.x).to.equal(0 - markerXLength - (tabInterval / 2));
697
+ markerXLength = tb.lines[3].marker.range.xLength();
698
+ expect(tb.lines[3].offsetFromDocument.x).to.equal(indentation + tabInterval * 2); // →→Green
699
+ expect(tb.lines[3].marker.offsetFromLine.x).to.equal(0 - markerXLength - (tabInterval / 2));
700
+ markerXLength = tb.lines[4].marker.range.xLength();
701
+ expect(tb.lines[4].offsetFromDocument.x).to.equal(indentation + tabInterval * 3); // →→→Granny Smith
702
+ expect(tb.lines[4].marker.offsetFromLine.x).to.equal(0 - markerXLength - (tabInterval / 2));
703
+ markerXLength = tb.lines[5].marker.range.xLength();
704
+ expect(tb.lines[5].offsetFromDocument.x).to.equal(indentation + tabInterval * 3); // →→→Rhode Island Greening
705
+ expect(tb.lines[5].marker.offsetFromLine.x).to.equal(0 - markerXLength - (tabInterval / 2));
706
+ markerXLength = tb.lines[6].marker.range.xLength();
707
+ expect(tb.lines[6].offsetFromDocument.x).to.equal(indentation + tabInterval * 2); // →→Yellow
708
+ expect(tb.lines[6].marker.offsetFromLine.x).to.equal(0 - markerXLength - (tabInterval / 2));
709
+ });
507
710
  it("justifies lines", function () {
508
711
  if (!isIntlSupported()) {
509
712
  this.skip();
510
713
  }
511
- const block = TextBlock.create({ styleId: "", styleOverrides: { lineSpacingFactor: 0 } });
714
+ const block = TextBlock.create({ styleOverrides: { lineSpacingFactor: 0 } });
512
715
  function expectBlockRange(width, height) {
513
716
  const layout = doLayout(block);
514
717
  expectRange(width, height, layout.range);
@@ -582,12 +785,13 @@ describe("layoutTextBlock", () => {
582
785
  });
583
786
  describe("word-wrapping", () => {
584
787
  function expectLines(input, width, expectedLines) {
585
- const textBlock = TextBlock.create({ styleId: "" });
788
+ const textBlock = TextBlock.create({ styleOverrides: { paragraphSpacingFactor: 0, lineSpacingFactor: 0, lineHeight: 1 } });
586
789
  textBlock.width = width;
587
790
  const run = makeTextRun(input);
588
791
  textBlock.appendRun(run);
589
792
  const layout = doLayout(textBlock);
590
- expect(layout.lines.every((line) => line.runs.every((r) => r.source === run))).to.be.true;
793
+ const content = run.stringify();
794
+ expect(layout.lines.every((line) => line.runs.every((r) => r.source.stringify() === content))).to.be.true;
591
795
  const actual = layout.lines.map((line) => line.runs.map((runLayout) => runLayout.source.content.substring(runLayout.charOffset, runLayout.charOffset + runLayout.numChars)).join(""));
592
796
  expect(actual).to.deep.equal(expectedLines);
593
797
  return layout;
@@ -596,7 +800,7 @@ describe("layoutTextBlock", () => {
596
800
  if (!isIntlSupported()) {
597
801
  this.skip();
598
802
  }
599
- const textBlock = TextBlock.create({ styleId: "" });
803
+ const textBlock = TextBlock.create();
600
804
  textBlock.width = 6;
601
805
  textBlock.appendRun(makeTextRun("ab"));
602
806
  expect(doLayout(textBlock).lines.length).to.equal(1);
@@ -685,7 +889,7 @@ describe("layoutTextBlock", () => {
685
889
  this.skip();
686
890
  }
687
891
  const lineHeight = 1;
688
- const textBlock = TextBlock.create({ styleId: "", styleOverrides: { lineHeight } });
892
+ const textBlock = TextBlock.create({ styleOverrides: { lineHeight } });
689
893
  // line 0: -->-->------> LINEBREAK
690
894
  textBlock.appendRun(TabRun.create({ styleOverrides: { tabInterval: 3 } }));
691
895
  textBlock.appendRun(TabRun.create({ styleOverrides: { tabInterval: 3 } }));
@@ -755,7 +959,7 @@ describe("layoutTextBlock", () => {
755
959
  if (!isIntlSupported()) {
756
960
  this.skip();
757
961
  }
758
- const textBlock = TextBlock.create({ styleId: "" });
962
+ const textBlock = TextBlock.create();
759
963
  for (const str of ["The ", "quick brown", " fox jumped over ", "the lazy ", "dog"]) {
760
964
  textBlock.appendRun(makeTextRun(str));
761
965
  }
@@ -799,7 +1003,7 @@ describe("layoutTextBlock", () => {
799
1003
  if (!isIntlSupported()) {
800
1004
  this.skip();
801
1005
  }
802
- const block = TextBlock.create({ styleId: "" });
1006
+ const block = TextBlock.create();
803
1007
  block.appendRun(makeTextRun("aa")); // 2 chars wide
804
1008
  block.appendRun(makeTextRun("bb ccc d ee")); // 11 chars wide
805
1009
  block.appendRun(makeTextRun("ff ggg h")); // 8 chars wide
@@ -839,7 +1043,7 @@ describe("layoutTextBlock", () => {
839
1043
  if (!isIntlSupported()) {
840
1044
  this.skip();
841
1045
  }
842
- const block = TextBlock.create({ styleId: "", styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
1046
+ const block = TextBlock.create({ styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
843
1047
  block.appendRun(makeTextRun("abc defg"));
844
1048
  const layout1 = doLayout(block);
845
1049
  let width = layout1.range.xLength();
@@ -849,6 +1053,56 @@ describe("layoutTextBlock", () => {
849
1053
  const layout2 = doLayout(block);
850
1054
  expect(layout2.range.yLength()).to.equal(1);
851
1055
  });
1056
+ it("wraps list items and applies indentation/insets for narrow text block width", function () {
1057
+ if (!isIntlSupported()) {
1058
+ this.skip();
1059
+ }
1060
+ const textBlock = TextBlock.create({ styleOverrides: { indentation: 2, tabInterval: 3, lineHeight: 1, lineSpacingFactor: 0, paragraphSpacingFactor: 0 } });
1061
+ /* Final TextBlock should look like:
1062
+ ⇥→1.→Lorem ipsum dolor sit amet, consectetur adipiscing elit¶ | Inset by 5
1063
+ ⇥→2.→sed do¶ | Inset by 5
1064
+ ⇥→→a.→eiusmod tempor¶ | Inset by 8
1065
+ ⇥→→b.→incididunt ut labore et dolore magna aliqua | Inset by 8
1066
+
1067
+ Where ↵ = LineBreak, ¶ = ParagraphBreak, → = tab, → = tabInterval/2, ⇥ = indentation
1068
+ */
1069
+ // Create nested list structure
1070
+ const list = List.create();
1071
+ list.children.push(Paragraph.create({ children: [TextRun.create({ content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" })] }));
1072
+ const apples = Paragraph.create({ children: [TextRun.create({ content: "sed do" })] });
1073
+ const subList = List.create({ styleOverrides: { listMarker: { enumerator: ListMarkerEnumerator.Letter, case: "lower", terminator: "period" } } });
1074
+ subList.children.push(Paragraph.create({ children: [TextRun.create({ content: "eiusmod tempor" })] }));
1075
+ subList.children.push(Paragraph.create({ children: [TextRun.create({ content: "incididunt ut labore et dolore magna aliqua" })] }));
1076
+ apples.children.push(subList);
1077
+ list.children.push(apples);
1078
+ textBlock.appendParagraph().children.push(list);
1079
+ function expectLayout(width, expected) {
1080
+ textBlock.width = width;
1081
+ const layout = doLayout(textBlock);
1082
+ // Check that each line is wrapped to width
1083
+ const minWidth = Math.max(19, width); // 19 for the width of the longest word with inset: "⇥→→b.→incididunt "
1084
+ if (width > 0) {
1085
+ layout.lines.forEach((line) => {
1086
+ expect(line.justificationRange.xLength() + line.offsetFromDocument.x).to.be.at.most(minWidth);
1087
+ });
1088
+ }
1089
+ expect(layout.stringify()).to.equal(expected);
1090
+ // Top-level items should have indentation + tabInterval
1091
+ let inset = 2 + 3;
1092
+ layout.lines.forEach((line) => {
1093
+ if (line.stringify().includes("eiusmod"))
1094
+ inset += 3; // SubList items should have increased indentation
1095
+ expect(line.offsetFromDocument.x).to.equal(inset);
1096
+ });
1097
+ }
1098
+ // Check indentation/insets for each line, indentation: 2, tabInterval: 5
1099
+ expectLayout(0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit\nsed do\neiusmod tempor\nincididunt ut labore et dolore magna aliqua");
1100
+ expectLayout(70, "Lorem ipsum dolor sit amet, consectetur adipiscing elit\nsed do\neiusmod tempor\nincididunt ut labore et dolore magna aliqua");
1101
+ expectLayout(40, "Lorem ipsum dolor sit amet, \nconsectetur adipiscing elit\nsed do\neiusmod tempor\nincididunt ut labore et dolore \nmagna aliqua");
1102
+ // TODO: layout should not pay attention to trailing whitespace when wrapping. I'll do this in another PR.
1103
+ expectLayout(21, "Lorem ipsum \ndolor sit amet, \nconsectetur \nadipiscing elit\nsed do\neiusmod \ntempor\nincididunt \nut labore et \ndolore magna \naliqua");
1104
+ expectLayout(15, "Lorem \nipsum \ndolor sit \namet, \nconsectetur \nadipiscing \nelit\nsed do\neiusmod \ntempor\nincididunt \nut \nlabore \net \ndolore \nmagna \naliqua");
1105
+ });
852
1106
  });
853
1107
  describe("grapheme offsets", () => {
854
1108
  function getLayoutResultAndStyleResolver(textBlock) {
@@ -856,24 +1110,24 @@ describe("layoutTextBlock", () => {
856
1110
  const result = layout.toResult();
857
1111
  const textStyleResolver = new TextStyleResolver({
858
1112
  textBlock,
1113
+ textStyleId: "",
859
1114
  iModel: {},
860
- modelId: undefined,
861
1115
  findTextStyle: () => TextStyleSettings.defaults
862
1116
  });
863
1117
  return { textStyleResolver, result };
864
1118
  }
865
1119
  it("should return an empty array if source type is not text", function () {
866
- const textBlock = TextBlock.create({ styleId: "" });
1120
+ const textBlock = TextBlock.create();
867
1121
  const fractionRun = FractionRun.create({ numerator: "1", denominator: "2" });
868
1122
  textBlock.appendRun(fractionRun);
869
1123
  const { textStyleResolver, result } = getLayoutResultAndStyleResolver(textBlock);
1124
+ const source = textBlock.children[0]; // FractionRun is not a TextRun
870
1125
  const args = {
871
- textBlock,
1126
+ source,
872
1127
  iModel: {},
873
1128
  textStyleResolver,
874
1129
  findFontId: () => 0,
875
1130
  computeTextRange: computeTextRangeAsStringLength,
876
- paragraphIndex: result.lines[0].sourceParagraphIndex,
877
1131
  runLayoutResult: result.lines[0].runs[0],
878
1132
  graphemeCharIndexes: [0],
879
1133
  };
@@ -881,17 +1135,17 @@ describe("layoutTextBlock", () => {
881
1135
  expect(graphemeRanges).to.be.an("array").that.is.empty;
882
1136
  });
883
1137
  it("should handle empty text content", function () {
884
- const textBlock = TextBlock.create({ styleId: "" });
1138
+ const textBlock = TextBlock.create();
885
1139
  const textRun = TextRun.create({ content: "" });
886
1140
  textBlock.appendRun(textRun);
887
1141
  const { textStyleResolver, result } = getLayoutResultAndStyleResolver(textBlock);
1142
+ const source = textBlock.children[0]; // FractionRun is not a TextRun
888
1143
  const args = {
889
- textBlock,
1144
+ source,
890
1145
  iModel: {},
891
1146
  textStyleResolver,
892
1147
  findFontId: () => 0,
893
1148
  computeTextRange: computeTextRangeAsStringLength,
894
- paragraphIndex: result.lines[0].sourceParagraphIndex,
895
1149
  runLayoutResult: result.lines[0].runs[0],
896
1150
  graphemeCharIndexes: [0], // Supply a grapheme index even though there is no text
897
1151
  };
@@ -899,17 +1153,17 @@ describe("layoutTextBlock", () => {
899
1153
  expect(graphemeRanges).to.be.an("array").that.is.empty;
900
1154
  });
901
1155
  it("should compute grapheme offsets correctly for a given text", function () {
902
- const textBlock = TextBlock.create({ styleId: "" });
1156
+ const textBlock = TextBlock.create();
903
1157
  const textRun = TextRun.create({ content: "hello" });
904
1158
  textBlock.appendRun(textRun);
905
1159
  const { textStyleResolver, result } = getLayoutResultAndStyleResolver(textBlock);
1160
+ const source = textBlock.children[0].children[0];
906
1161
  const args = {
907
- textBlock,
1162
+ source,
908
1163
  iModel: {},
909
1164
  textStyleResolver,
910
1165
  findFontId: () => 0,
911
1166
  computeTextRange: computeTextRangeAsStringLength,
912
- paragraphIndex: result.lines[0].sourceParagraphIndex,
913
1167
  runLayoutResult: result.lines[0].runs[0],
914
1168
  graphemeCharIndexes: [0, 1, 2, 3, 4],
915
1169
  };
@@ -919,18 +1173,18 @@ describe("layoutTextBlock", () => {
919
1173
  expect(graphemeRanges[4].high.x).to.equal(5);
920
1174
  });
921
1175
  it("should compute grapheme offsets correctly for non-English text", function () {
922
- const textBlock = TextBlock.create({ styleId: "" });
1176
+ const textBlock = TextBlock.create();
923
1177
  // Hindi - "Paragraph"
924
1178
  const textRun = TextRun.create({ content: "अनुच्छेद" });
925
1179
  textBlock.appendRun(textRun);
926
1180
  const { textStyleResolver, result } = getLayoutResultAndStyleResolver(textBlock);
1181
+ const source = textBlock.children[0].children[0];
927
1182
  const args = {
928
- textBlock,
1183
+ source,
929
1184
  iModel: {},
930
1185
  textStyleResolver,
931
1186
  findFontId: () => 0,
932
1187
  computeTextRange: computeTextRangeAsStringLength,
933
- paragraphIndex: result.lines[0].sourceParagraphIndex,
934
1188
  runLayoutResult: result.lines[0].runs[0],
935
1189
  graphemeCharIndexes: [0, 1, 3, 7],
936
1190
  };
@@ -942,17 +1196,17 @@ describe("layoutTextBlock", () => {
942
1196
  expect(graphemeRanges[3].high.x).to.equal(8);
943
1197
  });
944
1198
  it("should compute grapheme offsets correctly for emoji content", function () {
945
- const textBlock = TextBlock.create({ styleId: "" });
1199
+ const textBlock = TextBlock.create();
946
1200
  const textRun = TextRun.create({ content: "👨‍👦" });
947
1201
  textBlock.appendRun(textRun);
948
1202
  const { textStyleResolver, result } = getLayoutResultAndStyleResolver(textBlock);
1203
+ const source = textBlock.children[0].children[0];
949
1204
  const args = {
950
- textBlock,
1205
+ source,
951
1206
  iModel: {},
952
1207
  textStyleResolver,
953
1208
  findFontId: () => 0,
954
1209
  computeTextRange: computeTextRangeAsStringLength,
955
- paragraphIndex: result.lines[0].sourceParagraphIndex,
956
1210
  runLayoutResult: result.lines[0].runs[0],
957
1211
  graphemeCharIndexes: [0],
958
1212
  };
@@ -979,9 +1233,9 @@ describe("layoutTextBlock", () => {
979
1233
  expect(comic).to.equal(3);
980
1234
  expect(iModel.fonts.findId({ name: "Consolas" })).to.be.undefined;
981
1235
  function test(fontName, expectedFontId) {
982
- const textBlock = TextBlock.create({ styleId: "" });
1236
+ const textBlock = TextBlock.create();
983
1237
  textBlock.appendRun(TextRun.create({ styleOverrides: { fontName } }));
984
- const textStyleResolver = new TextStyleResolver({ textBlock, iModel });
1238
+ const textStyleResolver = new TextStyleResolver({ textBlock, textStyleId: "", iModel });
985
1239
  const layout = layoutTextBlock({ textBlock, iModel, textStyleResolver });
986
1240
  const run = layout.lines[0].runs[0];
987
1241
  expect(run).not.to.be.undefined;
@@ -995,7 +1249,6 @@ describe("layoutTextBlock", () => {
995
1249
  });
996
1250
  function computeDimensions(args) {
997
1251
  const textBlock = TextBlock.create({
998
- styleId: "",
999
1252
  styleOverrides: {
1000
1253
  lineHeight: args.height,
1001
1254
  widthFactor: args.width,
@@ -1009,7 +1262,7 @@ describe("layoutTextBlock", () => {
1009
1262
  fontName: args.font ?? "Vera",
1010
1263
  },
1011
1264
  }));
1012
- const textStyleResolver = new TextStyleResolver({ textBlock, iModel });
1265
+ const textStyleResolver = new TextStyleResolver({ textBlock, textStyleId: "", iModel });
1013
1266
  const range = layoutTextBlock({ textBlock, iModel, textStyleResolver }).range;
1014
1267
  return { x: range.high.x - range.low.x, y: range.high.y - range.low.y };
1015
1268
  }
@@ -1085,7 +1338,7 @@ describe("produceTextBlockGeometry", () => {
1085
1338
  return LineBreakRun.create({ styleOverrides });
1086
1339
  }
1087
1340
  function makeTextBlock(runs) {
1088
- const block = TextBlock.create({ styleId: "" });
1341
+ const block = TextBlock.create();
1089
1342
  for (const run of runs) {
1090
1343
  block.appendRun(run);
1091
1344
  }
@@ -1097,10 +1350,18 @@ describe("produceTextBlockGeometry", () => {
1097
1350
  const layout = doLayout(block);
1098
1351
  return produceTextBlockGeometry(layout, annotation.computeTransform(layout.range)).entries;
1099
1352
  }
1353
+ function makeListGeometry(children) {
1354
+ const textBlock = TextBlock.create();
1355
+ const p1 = textBlock.appendParagraph();
1356
+ p1.children.push(List.create({ children }));
1357
+ const annotation = TextAnnotation.fromJSON({ textBlock: textBlock.toJSON() });
1358
+ const layout = doLayout(textBlock);
1359
+ return produceTextBlockGeometry(layout, annotation.computeTransform(layout.range)).entries;
1360
+ }
1100
1361
  it("produces an empty array for an empty text block", () => {
1101
1362
  expect(makeGeometry([])).to.deep.equal([]);
1102
1363
  });
1103
- it("produces an empty array for a block consisting only of line breaks", () => {
1364
+ it("produces an empty array for a block consisting of line breaks", () => {
1104
1365
  expect(makeGeometry([makeBreak(), makeBreak(), makeBreak()])).to.deep.equal([]);
1105
1366
  });
1106
1367
  it("produces one appearance entry if all runs use subcategory color", () => {
@@ -1153,6 +1414,112 @@ describe("produceTextBlockGeometry", () => {
1153
1414
  "text",
1154
1415
  ]);
1155
1416
  });
1417
+ it("produces entries for list markers", () => {
1418
+ /* Final TextBlock should look like:
1419
+ 1. Oranges // Oranges -> default "subcategory" text
1420
+ 2. Apples // Apples -> Switch to red text
1421
+ • Red
1422
+ • Green // Green -> Switch to green text, not including the bullet.
1423
+ i. Granny Smith
1424
+ ii. Rhode Island Greening
1425
+ • Yellow // Yellow -> Back to red text
1426
+
1427
+ We have:
1428
+ 7 lines each containing one TextString for the list marker and one for the text,
1429
+ 4 appearance overrides
1430
+ */
1431
+ const listChildren = [
1432
+ {
1433
+ children: [
1434
+ {
1435
+ type: "text",
1436
+ content: "Oranges",
1437
+ }
1438
+ ]
1439
+ },
1440
+ {
1441
+ children: [
1442
+ {
1443
+ type: "text",
1444
+ content: "Apples",
1445
+ },
1446
+ {
1447
+ type: "list",
1448
+ styleOverrides: { listMarker: { enumerator: ListMarkerEnumerator.Bullet }, color: ColorDef.red.tbgr },
1449
+ children: [
1450
+ {
1451
+ children: [
1452
+ {
1453
+ type: "text",
1454
+ content: "Red",
1455
+ }
1456
+ ]
1457
+ },
1458
+ {
1459
+ styleOverrides: { color: ColorDef.green.tbgr },
1460
+ children: [
1461
+ {
1462
+ type: "text",
1463
+ content: "Green",
1464
+ },
1465
+ {
1466
+ type: "list",
1467
+ styleOverrides: { listMarker: { enumerator: ListMarkerEnumerator.RomanNumeral, case: "lower", terminator: "period" } },
1468
+ children: [
1469
+ {
1470
+ children: [
1471
+ {
1472
+ type: "text",
1473
+ content: "Granny Smith",
1474
+ }
1475
+ ]
1476
+ },
1477
+ {
1478
+ children: [
1479
+ {
1480
+ type: "text",
1481
+ content: "Rhode Island Greening",
1482
+ }
1483
+ ]
1484
+ }
1485
+ ]
1486
+ }
1487
+ ]
1488
+ },
1489
+ {
1490
+ children: [
1491
+ {
1492
+ type: "text",
1493
+ content: "Yellow",
1494
+ }
1495
+ ]
1496
+ }
1497
+ ]
1498
+ }
1499
+ ]
1500
+ }
1501
+ ];
1502
+ const entries = makeListGeometry(listChildren);
1503
+ expect(entries.length).to.equal(14 + 4); // 14 text strings + 4 appearance entry
1504
+ expect(entries[0].color).to.equal("subcategory");
1505
+ expect(entries[1].text?.text).to.equal("1.");
1506
+ expect(entries[2].text?.text).to.equal("Oranges");
1507
+ expect(entries[3].text?.text).to.equal("2.");
1508
+ expect(entries[4].text?.text).to.equal("Apples");
1509
+ expect(entries[5].color).to.equal(ColorDef.red.tbgr);
1510
+ expect(entries[6].text?.text).to.equal("•");
1511
+ expect(entries[7].text?.text).to.equal("Red");
1512
+ expect(entries[8].text?.text).to.equal("•");
1513
+ expect(entries[9].color).to.equal(ColorDef.green.tbgr);
1514
+ expect(entries[10].text?.text).to.equal("Green");
1515
+ expect(entries[11].text?.text).to.equal("i.");
1516
+ expect(entries[12].text?.text).to.equal("Granny Smith");
1517
+ expect(entries[13].text?.text).to.equal("ii.");
1518
+ expect(entries[14].text?.text).to.equal("Rhode Island Greening");
1519
+ expect(entries[15].color).to.equal(ColorDef.red.tbgr);
1520
+ expect(entries[16].text?.text).to.equal("•");
1521
+ expect(entries[17].text?.text).to.equal("Yellow");
1522
+ });
1156
1523
  it("offsets geometry entries by margins", () => {
1157
1524
  function makeGeometryWithMargins(anchor, margins) {
1158
1525
  const runs = [makeText()];
@@ -1195,5 +1562,5 @@ describe("produceTextBlockGeometry", () => {
1195
1562
  });
1196
1563
  });
1197
1564
  // Ignoring the text strings from the spell checker
1198
- // cspell:ignore jklmnop vwxyz defg hijk ghij klmno pqrstu Tanuki aabb eeff nggg amet adipiscing elit Phasellus pretium malesuada venenatis eleifend Donec sapien Nullam commodo accumsan lacinia metus enim pharetra lacus facilisis Duis suscipit quis feugiat fermentum ut augue Mauris iaculis odio rhoncus lorem viverra turpis elementum posuere Consolas अनुच्छेद cdefg cdefgh cdefghi
1565
+ // cspell:ignore jklmnop vwxyz defg hijk ghij klmno pqrstu Tanuki aabb eeff nggg amet adipiscing elit Phasellus pretium malesuada venenatis eleifend Donec sapien Nullam commodo accumsan lacinia metus enim pharetra lacus facilisis Duis suscipit quis feugiat fermentum ut augue Mauris iaculis odio rhoncus lorem viverra turpis elementum posuere Consolas अनुच्छेद cdefg cdefgh cdefghi eiusmod tempor incididunt ut labore et dolore magna aliqua sed defghi
1199
1566
  //# sourceMappingURL=TextBlock.test.js.map