@itwin/core-backend 5.1.0-dev.47 → 5.1.0-dev.51
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.
- package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
- package/lib/cjs/BriefcaseManager.js +4 -0
- package/lib/cjs/BriefcaseManager.js.map +1 -1
- package/lib/cjs/annotations/TextAnnotationGeometry.d.ts.map +1 -1
- package/lib/cjs/annotations/TextAnnotationGeometry.js +49 -0
- package/lib/cjs/annotations/TextAnnotationGeometry.js.map +1 -1
- package/lib/cjs/annotations/TextBlockGeometry.d.ts.map +1 -1
- package/lib/cjs/annotations/TextBlockGeometry.js +2 -1
- package/lib/cjs/annotations/TextBlockGeometry.js.map +1 -1
- package/lib/cjs/annotations/TextBlockLayout.d.ts +3 -1
- package/lib/cjs/annotations/TextBlockLayout.d.ts.map +1 -1
- package/lib/cjs/annotations/TextBlockLayout.js +32 -2
- package/lib/cjs/annotations/TextBlockLayout.js.map +1 -1
- package/lib/esm/BriefcaseManager.d.ts.map +1 -1
- package/lib/esm/BriefcaseManager.js +5 -1
- package/lib/esm/BriefcaseManager.js.map +1 -1
- package/lib/esm/annotations/TextAnnotationGeometry.d.ts.map +1 -1
- package/lib/esm/annotations/TextAnnotationGeometry.js +50 -1
- package/lib/esm/annotations/TextAnnotationGeometry.js.map +1 -1
- package/lib/esm/annotations/TextBlockGeometry.d.ts.map +1 -1
- package/lib/esm/annotations/TextBlockGeometry.js +2 -1
- package/lib/esm/annotations/TextBlockGeometry.js.map +1 -1
- package/lib/esm/annotations/TextBlockLayout.d.ts +3 -1
- package/lib/esm/annotations/TextBlockLayout.d.ts.map +1 -1
- package/lib/esm/annotations/TextBlockLayout.js +32 -2
- package/lib/esm/annotations/TextBlockLayout.js.map +1 -1
- package/lib/esm/test/annotations/TextBlock.test.js +570 -434
- package/lib/esm/test/annotations/TextBlock.test.js.map +1 -1
- package/package.json +13 -13
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { expect } from "chai";
|
|
6
6
|
import { computeGraphemeOffsets, layoutTextBlock } from "../../annotations/TextBlockLayout";
|
|
7
7
|
import { Geometry, Range2d } from "@itwin/core-geometry";
|
|
8
|
-
import { ColorDef, FontType, FractionRun, LineBreakRun, TextAnnotation, TextBlock, TextRun, TextStyleSettings } from "@itwin/core-common";
|
|
8
|
+
import { ColorDef, FontType, FractionRun, LineBreakRun, 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";
|
|
@@ -57,438 +57,6 @@ describe("layoutTextBlock", () => {
|
|
|
57
57
|
expect(s1.fontName).to.equal("run1");
|
|
58
58
|
expect(s1.color).to.equal("subcategory");
|
|
59
59
|
});
|
|
60
|
-
it("aligns text to center based on height of stacked fraction", () => {
|
|
61
|
-
const textBlock = TextBlock.create({ styleName: "" });
|
|
62
|
-
const fractionRun = FractionRun.create({ numerator: "1", denominator: "2", styleName: "fraction" });
|
|
63
|
-
const textRun = TextRun.create({ content: "text", styleName: "text" });
|
|
64
|
-
textBlock.appendRun(fractionRun);
|
|
65
|
-
textBlock.appendRun(textRun);
|
|
66
|
-
const layout = doLayout(textBlock);
|
|
67
|
-
const fractionLayout = layout.lines[0].runs[0];
|
|
68
|
-
const textLayout = layout.lines[0].runs[1];
|
|
69
|
-
const round = (num, numDecimalPlaces) => {
|
|
70
|
-
const multiplier = Math.pow(100, numDecimalPlaces);
|
|
71
|
-
return Math.round(num * multiplier) / multiplier;
|
|
72
|
-
};
|
|
73
|
-
expect(textLayout.range.yLength()).to.equal(1);
|
|
74
|
-
expect(round(fractionLayout.range.yLength(), 2)).to.equal(1.75);
|
|
75
|
-
expect(fractionLayout.offsetFromLine.y).to.equal(0);
|
|
76
|
-
expect(round(textLayout.offsetFromLine.y, 3)).to.equal(.375);
|
|
77
|
-
});
|
|
78
|
-
it("produces one line per paragraph if document width <= 0", () => {
|
|
79
|
-
const textBlock = TextBlock.create({ styleName: "" });
|
|
80
|
-
for (let i = 0; i < 4; i++) {
|
|
81
|
-
const layout = doLayout(textBlock);
|
|
82
|
-
if (i === 0) {
|
|
83
|
-
expect(layout.range.isNull).to.be.true;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
expect(layout.lines.length).to.equal(i);
|
|
87
|
-
expect(layout.range.low.x).to.equal(0);
|
|
88
|
-
expect(layout.range.low.y).to.equal(-i - (0.5 * (i - 1))); // lineSpacingFactor=0.5
|
|
89
|
-
expect(layout.range.high.x).to.equal(i * 3);
|
|
90
|
-
expect(layout.range.high.y).to.equal(0);
|
|
91
|
-
}
|
|
92
|
-
for (let l = 0; l < layout.lines.length; l++) {
|
|
93
|
-
const line = layout.lines[l];
|
|
94
|
-
expect(line.runs.length).to.equal(l + 1);
|
|
95
|
-
expect(line.range.low.x).to.equal(0);
|
|
96
|
-
expect(line.range.low.y).to.equal(0);
|
|
97
|
-
expect(line.range.high.y).to.equal(1);
|
|
98
|
-
expect(line.range.high.x).to.equal(3 * (l + 1));
|
|
99
|
-
for (const run of line.runs) {
|
|
100
|
-
expect(run.charOffset).to.equal(0);
|
|
101
|
-
expect(run.numChars).to.equal(3);
|
|
102
|
-
expect(run.range.low.x).to.equal(0);
|
|
103
|
-
expect(run.range.low.y).to.equal(0);
|
|
104
|
-
expect(run.range.high.x).to.equal(3);
|
|
105
|
-
expect(run.range.high.y).to.equal(1);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const p = textBlock.appendParagraph();
|
|
109
|
-
for (let j = 0; j <= i; j++) {
|
|
110
|
-
p.runs.push(TextRun.create({ styleName: "", content: "Run" }));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
it("produces a new line for each LineBreakRun", () => {
|
|
115
|
-
const lineSpacingFactor = 0.5;
|
|
116
|
-
const lineHeight = 1;
|
|
117
|
-
const textBlock = TextBlock.create({ styleName: "", styleOverrides: { lineSpacingFactor, lineHeight } });
|
|
118
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "abc" }));
|
|
119
|
-
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
120
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "def" }));
|
|
121
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "ghi" }));
|
|
122
|
-
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
123
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "jkl" }));
|
|
124
|
-
const tb = doLayout(textBlock);
|
|
125
|
-
expect(tb.lines.length).to.equal(3);
|
|
126
|
-
expect(tb.lines[0].runs.length).to.equal(2);
|
|
127
|
-
expect(tb.lines[1].runs.length).to.equal(3);
|
|
128
|
-
expect(tb.lines[2].runs.length).to.equal(1);
|
|
129
|
-
expect(tb.range.low.x).to.equal(0);
|
|
130
|
-
expect(tb.range.high.x).to.equal(6);
|
|
131
|
-
expect(tb.range.high.y).to.equal(0);
|
|
132
|
-
expect(tb.range.low.y).to.equal(-(lineSpacingFactor * 2 + lineHeight * 3));
|
|
133
|
-
});
|
|
134
|
-
it("computes ranges based on custom line spacing and line height", () => {
|
|
135
|
-
const lineSpacingFactor = 2;
|
|
136
|
-
const lineHeight = 3;
|
|
137
|
-
const textBlock = TextBlock.create({ styleName: "", styleOverrides: { lineSpacingFactor, lineHeight } });
|
|
138
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "abc" }));
|
|
139
|
-
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
140
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "def" }));
|
|
141
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "ghi" }));
|
|
142
|
-
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
143
|
-
textBlock.appendRun(TextRun.create({ styleName: "", content: "jkl" }));
|
|
144
|
-
const tb = doLayout(textBlock);
|
|
145
|
-
expect(tb.lines.length).to.equal(3);
|
|
146
|
-
expect(tb.lines[0].runs.length).to.equal(2);
|
|
147
|
-
expect(tb.lines[1].runs.length).to.equal(3);
|
|
148
|
-
expect(tb.lines[2].runs.length).to.equal(1);
|
|
149
|
-
// We have 3 lines each `lineHeight` high, plus 2 line breaks in between each `lineHeight*lineSpacingFactor` high.
|
|
150
|
-
expect(tb.range.low.x).to.equal(0);
|
|
151
|
-
expect(tb.range.high.x).to.equal(6);
|
|
152
|
-
expect(tb.range.high.y).to.equal(0);
|
|
153
|
-
expect(tb.range.low.y).to.equal(-(lineHeight * 3 + (lineHeight * lineSpacingFactor) * 2));
|
|
154
|
-
expect(tb.lines[0].offsetFromDocument.y).to.equal(-lineHeight);
|
|
155
|
-
expect(tb.lines[1].offsetFromDocument.y).to.equal(tb.lines[0].offsetFromDocument.y - (lineHeight + lineHeight * lineSpacingFactor));
|
|
156
|
-
expect(tb.lines[2].offsetFromDocument.y).to.equal(tb.lines[1].offsetFromDocument.y - (lineHeight + lineHeight * lineSpacingFactor));
|
|
157
|
-
expect(tb.lines.every((line) => line.offsetFromDocument.x === 0)).to.be.true;
|
|
158
|
-
});
|
|
159
|
-
it("splits paragraphs into multiple lines if runs exceed the document width", function () {
|
|
160
|
-
if (!isIntlSupported()) {
|
|
161
|
-
this.skip();
|
|
162
|
-
}
|
|
163
|
-
const textBlock = TextBlock.create({ styleName: "" });
|
|
164
|
-
textBlock.width = 6;
|
|
165
|
-
textBlock.appendRun(makeTextRun("ab"));
|
|
166
|
-
expect(doLayout(textBlock).lines.length).to.equal(1);
|
|
167
|
-
textBlock.appendRun(makeTextRun("cd"));
|
|
168
|
-
expect(doLayout(textBlock).lines.length).to.equal(1);
|
|
169
|
-
textBlock.appendRun(makeTextRun("ef"));
|
|
170
|
-
expect(doLayout(textBlock).lines.length).to.equal(1);
|
|
171
|
-
textBlock.appendRun(makeTextRun("ghi"));
|
|
172
|
-
expect(doLayout(textBlock).lines.length).to.equal(2);
|
|
173
|
-
textBlock.appendRun(makeTextRun("jklmnop"));
|
|
174
|
-
expect(doLayout(textBlock).lines.length).to.equal(3);
|
|
175
|
-
textBlock.appendRun(makeTextRun("q"));
|
|
176
|
-
expect(doLayout(textBlock).lines.length).to.equal(4);
|
|
177
|
-
textBlock.appendRun(makeTextRun("r"));
|
|
178
|
-
expect(doLayout(textBlock).lines.length).to.equal(4);
|
|
179
|
-
textBlock.appendRun(makeTextRun("stu"));
|
|
180
|
-
expect(doLayout(textBlock).lines.length).to.equal(4);
|
|
181
|
-
textBlock.appendRun(makeTextRun("vwxyz"));
|
|
182
|
-
expect(doLayout(textBlock).lines.length).to.equal(5);
|
|
183
|
-
});
|
|
184
|
-
function expectRange(width, height, range) {
|
|
185
|
-
expect(range.xLength()).to.equal(width);
|
|
186
|
-
expect(range.yLength()).to.equal(height);
|
|
187
|
-
}
|
|
188
|
-
it("computes range for wrapped lines", function () {
|
|
189
|
-
if (!isIntlSupported()) {
|
|
190
|
-
this.skip();
|
|
191
|
-
}
|
|
192
|
-
const block = TextBlock.create({ styleName: "", width: 3, styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
|
|
193
|
-
function expectBlockRange(width, height) {
|
|
194
|
-
const layout = doLayout(block);
|
|
195
|
-
expectRange(width, height, layout.range);
|
|
196
|
-
}
|
|
197
|
-
block.appendRun(makeTextRun("abc"));
|
|
198
|
-
expectBlockRange(3, 1);
|
|
199
|
-
block.appendRun(makeTextRun("defg"));
|
|
200
|
-
expectBlockRange(4, 2);
|
|
201
|
-
block.width = 1;
|
|
202
|
-
expectBlockRange(4, 2);
|
|
203
|
-
block.width = 8;
|
|
204
|
-
expectBlockRange(8, 1);
|
|
205
|
-
block.width = 6;
|
|
206
|
-
expectBlockRange(6, 2);
|
|
207
|
-
block.width = 10;
|
|
208
|
-
expectBlockRange(10, 1);
|
|
209
|
-
block.appendRun(makeTextRun("hijk"));
|
|
210
|
-
expectBlockRange(10, 2);
|
|
211
|
-
});
|
|
212
|
-
it("computes range for split runs", function () {
|
|
213
|
-
if (!isIntlSupported()) {
|
|
214
|
-
this.skip();
|
|
215
|
-
}
|
|
216
|
-
const block = TextBlock.create({ styleName: "", styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
|
|
217
|
-
function expectBlockRange(width, height) {
|
|
218
|
-
const layout = doLayout(block);
|
|
219
|
-
expectRange(width, height, layout.range);
|
|
220
|
-
}
|
|
221
|
-
const sentence = "a bc def ghij klmno";
|
|
222
|
-
expect(sentence.length).to.equal(19);
|
|
223
|
-
block.appendRun(makeTextRun(sentence));
|
|
224
|
-
block.width = 19;
|
|
225
|
-
expectBlockRange(19, 1);
|
|
226
|
-
block.width = 10;
|
|
227
|
-
expectBlockRange(10, 2);
|
|
228
|
-
});
|
|
229
|
-
it("justifies lines", function () {
|
|
230
|
-
if (!isIntlSupported()) {
|
|
231
|
-
this.skip();
|
|
232
|
-
}
|
|
233
|
-
const block = TextBlock.create({ styleName: "", styleOverrides: { lineSpacingFactor: 0 } });
|
|
234
|
-
function expectBlockRange(width, height) {
|
|
235
|
-
const layout = doLayout(block);
|
|
236
|
-
expectRange(width, height, layout.range);
|
|
237
|
-
}
|
|
238
|
-
function expectLineOffset(offset, lineIndex) {
|
|
239
|
-
const layout = doLayout(block);
|
|
240
|
-
expect(layout.lines.length).least(lineIndex + 1);
|
|
241
|
-
const line = layout.lines[lineIndex];
|
|
242
|
-
expect(line.offsetFromDocument.y).to.equal(-(lineIndex + 1));
|
|
243
|
-
expect(line.offsetFromDocument.x).to.equal(offset);
|
|
244
|
-
}
|
|
245
|
-
// Two text runs with 7 characters total.
|
|
246
|
-
block.appendRun(makeTextRun("abc"));
|
|
247
|
-
block.appendRun(makeTextRun("defg"));
|
|
248
|
-
// 1 line of text with width 0: left, right, center justification.
|
|
249
|
-
block.justification = "left";
|
|
250
|
-
expectBlockRange(7, 1);
|
|
251
|
-
expectLineOffset(0, 0);
|
|
252
|
-
block.justification = "right";
|
|
253
|
-
expectBlockRange(7, 1);
|
|
254
|
-
expectLineOffset(0, 0);
|
|
255
|
-
block.justification = "center";
|
|
256
|
-
expectBlockRange(7, 1);
|
|
257
|
-
expectLineOffset(0, 0);
|
|
258
|
-
// 1 line of text from a width greater than number of characters: left, right, center justification.
|
|
259
|
-
block.width = 10;
|
|
260
|
-
block.justification = "left";
|
|
261
|
-
expectBlockRange(10, 1);
|
|
262
|
-
expectLineOffset(0, 0);
|
|
263
|
-
block.justification = "right";
|
|
264
|
-
expectBlockRange(10, 1);
|
|
265
|
-
expectLineOffset(3, 0); // 3 = 10 - 7
|
|
266
|
-
block.justification = "center";
|
|
267
|
-
expectBlockRange(10, 1);
|
|
268
|
-
expectLineOffset(1.5, 0); // 1.5 = (10 - 7) / 2
|
|
269
|
-
// 2 line of text from a width less than number of characters: left, right, center justification.
|
|
270
|
-
block.justification = "left";
|
|
271
|
-
block.width = 4;
|
|
272
|
-
expectBlockRange(4, 2);
|
|
273
|
-
expectLineOffset(0, 0);
|
|
274
|
-
expectLineOffset(0, 1);
|
|
275
|
-
block.justification = "right";
|
|
276
|
-
expectBlockRange(4, 2);
|
|
277
|
-
expectLineOffset(1, 0);
|
|
278
|
-
expectLineOffset(0, 1);
|
|
279
|
-
block.justification = "center";
|
|
280
|
-
expectBlockRange(4, 2);
|
|
281
|
-
expectLineOffset(0.5, 0);
|
|
282
|
-
expectLineOffset(0, 1);
|
|
283
|
-
// Testing text longer the the width of the text block.
|
|
284
|
-
block.width = 2;
|
|
285
|
-
block.justification = "left";
|
|
286
|
-
expectBlockRange(4, 2);
|
|
287
|
-
expectLineOffset(0, 0);
|
|
288
|
-
expectLineOffset(0, 1);
|
|
289
|
-
block.justification = "right";
|
|
290
|
-
expectBlockRange(4, 2);
|
|
291
|
-
expectLineOffset(-1, 0);
|
|
292
|
-
expectLineOffset(-2, 1);
|
|
293
|
-
block.appendRun(makeTextRun("123456789"));
|
|
294
|
-
expectBlockRange(9, 3);
|
|
295
|
-
expectLineOffset(-1, 0);
|
|
296
|
-
expectLineOffset(-2, 1);
|
|
297
|
-
expectLineOffset(-7, 2);
|
|
298
|
-
block.justification = "center";
|
|
299
|
-
expectBlockRange(9, 3);
|
|
300
|
-
expectLineOffset(-0.5, 0);
|
|
301
|
-
expectLineOffset(-1, 1);
|
|
302
|
-
expectLineOffset(-3.5, 2);
|
|
303
|
-
});
|
|
304
|
-
function expectLines(input, width, expectedLines) {
|
|
305
|
-
const textBlock = TextBlock.create({ styleName: "" });
|
|
306
|
-
textBlock.width = width;
|
|
307
|
-
const run = makeTextRun(input);
|
|
308
|
-
textBlock.appendRun(run);
|
|
309
|
-
const layout = doLayout(textBlock);
|
|
310
|
-
expect(layout.lines.every((line) => line.runs.every((r) => r.source === run))).to.be.true;
|
|
311
|
-
const actual = layout.lines.map((line) => line.runs.map((runLayout) => runLayout.source.content.substring(runLayout.charOffset, runLayout.charOffset + runLayout.numChars)).join(""));
|
|
312
|
-
expect(actual).to.deep.equal(expectedLines);
|
|
313
|
-
return layout;
|
|
314
|
-
}
|
|
315
|
-
it("splits a single TextRun at word boundaries if it exceeds the document width", function () {
|
|
316
|
-
if (!isIntlSupported()) {
|
|
317
|
-
this.skip();
|
|
318
|
-
}
|
|
319
|
-
expectLines("a bc def ghij klmno pqrstu vwxyz", 5, [
|
|
320
|
-
"a bc ",
|
|
321
|
-
"def ",
|
|
322
|
-
"ghij ",
|
|
323
|
-
"klmno ",
|
|
324
|
-
"pqrstu ",
|
|
325
|
-
"vwxyz",
|
|
326
|
-
]);
|
|
327
|
-
const fox = "The quick brown fox jumped over the lazy dog";
|
|
328
|
-
expectLines(fox, 50, [fox]);
|
|
329
|
-
expectLines(fox, 40, [
|
|
330
|
-
// 1 2 3 4
|
|
331
|
-
// 34567890123456789012345678901234567890
|
|
332
|
-
"The quick brown fox jumped over the ",
|
|
333
|
-
"lazy dog",
|
|
334
|
-
]);
|
|
335
|
-
expectLines(fox, 30, [
|
|
336
|
-
// 1 2 3
|
|
337
|
-
// 3456789012345678901234567890
|
|
338
|
-
"The quick brown fox jumped ",
|
|
339
|
-
"over the lazy dog",
|
|
340
|
-
]);
|
|
341
|
-
expectLines(fox, 20, [
|
|
342
|
-
// 1 2
|
|
343
|
-
// 345678901234567890
|
|
344
|
-
"The quick brown fox ",
|
|
345
|
-
"jumped over the ",
|
|
346
|
-
"lazy dog",
|
|
347
|
-
]);
|
|
348
|
-
expectLines(fox, 10, [
|
|
349
|
-
// 1
|
|
350
|
-
// 234567890
|
|
351
|
-
"The quick ",
|
|
352
|
-
"brown fox ",
|
|
353
|
-
"jumped ",
|
|
354
|
-
"over the ",
|
|
355
|
-
"lazy dog",
|
|
356
|
-
]);
|
|
357
|
-
});
|
|
358
|
-
it("considers consecutive whitespace part of a single 'word'", function () {
|
|
359
|
-
if (!isIntlSupported()) {
|
|
360
|
-
this.skip();
|
|
361
|
-
}
|
|
362
|
-
expectLines("a b c d e f ", 3, [
|
|
363
|
-
"a ",
|
|
364
|
-
"b ",
|
|
365
|
-
"c ",
|
|
366
|
-
"d ",
|
|
367
|
-
"e ",
|
|
368
|
-
"f ",
|
|
369
|
-
]);
|
|
370
|
-
});
|
|
371
|
-
it("wraps Japanese text", function () {
|
|
372
|
-
if (!isIntlSupported()) {
|
|
373
|
-
this.skip();
|
|
374
|
-
}
|
|
375
|
-
// "I am a cat. The name is Tanuki."
|
|
376
|
-
expectLines("吾輩は猫である。名前はたぬき。", 1, ["吾", "輩", "は", "猫", "で", "あ", "る。", "名", "前", "は", "た", "ぬ", "き。"]);
|
|
377
|
-
});
|
|
378
|
-
it("performs word-wrapping with punctuation", function () {
|
|
379
|
-
if (!isIntlSupported()) {
|
|
380
|
-
this.skip();
|
|
381
|
-
}
|
|
382
|
-
expectLines("1.24 56.7 8,910", 1, ["1.24 ", "56.7 ", "8,910"]);
|
|
383
|
-
expectLines("a.bc de.f g,hij", 1, ["a.bc ", "de.f ", "g,hij"]);
|
|
384
|
-
expectLines("Let's see... can you (or anyone) predict?!", 1, [
|
|
385
|
-
"Let's ",
|
|
386
|
-
"see... ",
|
|
387
|
-
"can ",
|
|
388
|
-
"you ",
|
|
389
|
-
"(or ",
|
|
390
|
-
"anyone) ",
|
|
391
|
-
"predict?!",
|
|
392
|
-
]);
|
|
393
|
-
});
|
|
394
|
-
it("performs word-wrapping and line-splitting with multiple runs", function () {
|
|
395
|
-
if (!isIntlSupported()) {
|
|
396
|
-
this.skip();
|
|
397
|
-
}
|
|
398
|
-
const textBlock = TextBlock.create({ styleName: "" });
|
|
399
|
-
for (const str of ["The ", "quick brown", " fox jumped over ", "the lazy ", "dog"]) {
|
|
400
|
-
textBlock.appendRun(makeTextRun(str));
|
|
401
|
-
}
|
|
402
|
-
function test(width, expected) {
|
|
403
|
-
textBlock.width = width;
|
|
404
|
-
const layout = doLayout(textBlock);
|
|
405
|
-
const actual = layout.lines.map((line) => line.runs.map((runLayout) => runLayout.source.content.substring(runLayout.charOffset, runLayout.charOffset + runLayout.numChars)).join(""));
|
|
406
|
-
expect(actual).to.deep.equal(expected);
|
|
407
|
-
}
|
|
408
|
-
test(50, ["The quick brown fox jumped over the lazy dog"]);
|
|
409
|
-
test(40, [
|
|
410
|
-
// 1 2 3 4
|
|
411
|
-
// 34567890123456789012345678901234567890
|
|
412
|
-
"The quick brown fox jumped over the ",
|
|
413
|
-
"lazy dog",
|
|
414
|
-
]);
|
|
415
|
-
test(30, [
|
|
416
|
-
// 1 2 3
|
|
417
|
-
// 3456789012345678901234567890
|
|
418
|
-
"The quick brown fox jumped ",
|
|
419
|
-
"over the lazy dog",
|
|
420
|
-
]);
|
|
421
|
-
test(20, [
|
|
422
|
-
// 1 2
|
|
423
|
-
// 345678901234567890
|
|
424
|
-
"The quick brown fox ",
|
|
425
|
-
"jumped over the ",
|
|
426
|
-
"lazy dog",
|
|
427
|
-
]);
|
|
428
|
-
test(10, [
|
|
429
|
-
// 1
|
|
430
|
-
// 34567890
|
|
431
|
-
"The quick ",
|
|
432
|
-
"brown fox ",
|
|
433
|
-
"jumped ",
|
|
434
|
-
"over the ",
|
|
435
|
-
"lazy dog",
|
|
436
|
-
]);
|
|
437
|
-
});
|
|
438
|
-
it("wraps multiple runs", function () {
|
|
439
|
-
if (!isIntlSupported()) {
|
|
440
|
-
this.skip();
|
|
441
|
-
}
|
|
442
|
-
const block = TextBlock.create({ styleName: "" });
|
|
443
|
-
block.appendRun(makeTextRun("aa")); // 2 chars wide
|
|
444
|
-
block.appendRun(makeTextRun("bb ccc d ee")); // 11 chars wide
|
|
445
|
-
block.appendRun(makeTextRun("ff ggg h")); // 8 chars wide
|
|
446
|
-
function expectLayout(width, expected) {
|
|
447
|
-
block.width = width;
|
|
448
|
-
const layout = doLayout(block);
|
|
449
|
-
expect(layout.stringify()).to.equal(expected);
|
|
450
|
-
}
|
|
451
|
-
expectLayout(23, "aabb ccc d eeff ggg h");
|
|
452
|
-
expectLayout(22, "aabb ccc d eeff ggg h");
|
|
453
|
-
expectLayout(21, "aabb ccc d eeff ggg h");
|
|
454
|
-
expectLayout(20, "aabb ccc d eeff ggg \nh");
|
|
455
|
-
expectLayout(19, "aabb ccc d eeff \nggg h");
|
|
456
|
-
expectLayout(18, "aabb ccc d eeff \nggg h");
|
|
457
|
-
expectLayout(17, "aabb ccc d eeff \nggg h");
|
|
458
|
-
expectLayout(16, "aabb ccc d eeff \nggg h");
|
|
459
|
-
expectLayout(15, "aabb ccc d ee\nff ggg h");
|
|
460
|
-
expectLayout(14, "aabb ccc d ee\nff ggg h");
|
|
461
|
-
expectLayout(13, "aabb ccc d ee\nff ggg h");
|
|
462
|
-
expectLayout(12, "aabb ccc d \neeff ggg h");
|
|
463
|
-
expectLayout(11, "aabb ccc d \neeff ggg h");
|
|
464
|
-
expectLayout(10, "aabb ccc \nd eeff \nggg h");
|
|
465
|
-
expectLayout(9, "aabb ccc \nd eeff \nggg h");
|
|
466
|
-
expectLayout(8, "aabb \nccc d ee\nff ggg h");
|
|
467
|
-
expectLayout(7, "aabb \nccc d \neeff \nggg h");
|
|
468
|
-
expectLayout(6, "aabb \nccc d \neeff \nggg h");
|
|
469
|
-
expectLayout(5, "aabb \nccc \nd ee\nff \nggg h");
|
|
470
|
-
expectLayout(4, "aa\nbb \nccc \nd ee\nff \nggg \nh");
|
|
471
|
-
expectLayout(3, "aa\nbb \nccc \nd \nee\nff \nggg \nh");
|
|
472
|
-
expectLayout(2, "aa\nbb \nccc \nd \nee\nff \nggg \nh");
|
|
473
|
-
expectLayout(1, "aa\nbb \nccc \nd \nee\nff \nggg \nh");
|
|
474
|
-
expectLayout(0, "aabb ccc d eeff ggg h");
|
|
475
|
-
expectLayout(-1, "aabb ccc d eeff ggg h");
|
|
476
|
-
expectLayout(-2, "aabb ccc d eeff ggg h");
|
|
477
|
-
});
|
|
478
|
-
it("does not word wrap due to floating point rounding error", function () {
|
|
479
|
-
if (!isIntlSupported()) {
|
|
480
|
-
this.skip();
|
|
481
|
-
}
|
|
482
|
-
const block = TextBlock.create({ styleName: "", styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
|
|
483
|
-
block.appendRun(makeTextRun("abc defg"));
|
|
484
|
-
const layout1 = doLayout(block);
|
|
485
|
-
let width = layout1.range.xLength();
|
|
486
|
-
// Simulate a floating point rounding error by slightly reducing the width
|
|
487
|
-
width -= Geometry.smallFloatingPoint;
|
|
488
|
-
block.width = width;
|
|
489
|
-
const layout2 = doLayout(block);
|
|
490
|
-
expect(layout2.range.yLength()).to.equal(1);
|
|
491
|
-
});
|
|
492
60
|
it("has consistent data when converted to a layout result", function () {
|
|
493
61
|
if (!isIntlSupported()) {
|
|
494
62
|
this.skip();
|
|
@@ -627,6 +195,574 @@ describe("layoutTextBlock", () => {
|
|
|
627
195
|
layout = doLayout(block);
|
|
628
196
|
expectMargins(layout.textRange, layout.range, { top: 1, bottom: 2 });
|
|
629
197
|
});
|
|
198
|
+
describe("range", () => {
|
|
199
|
+
it("aligns text to center based on height of stacked fraction", () => {
|
|
200
|
+
const textBlock = TextBlock.create({ styleName: "" });
|
|
201
|
+
const fractionRun = FractionRun.create({ numerator: "1", denominator: "2", styleName: "fraction" });
|
|
202
|
+
const textRun = TextRun.create({ content: "text", styleName: "text" });
|
|
203
|
+
textBlock.appendRun(fractionRun);
|
|
204
|
+
textBlock.appendRun(textRun);
|
|
205
|
+
const layout = doLayout(textBlock);
|
|
206
|
+
const fractionLayout = layout.lines[0].runs[0];
|
|
207
|
+
const textLayout = layout.lines[0].runs[1];
|
|
208
|
+
const round = (num, numDecimalPlaces) => {
|
|
209
|
+
const multiplier = Math.pow(100, numDecimalPlaces);
|
|
210
|
+
return Math.round(num * multiplier) / multiplier;
|
|
211
|
+
};
|
|
212
|
+
expect(textLayout.range.yLength()).to.equal(1);
|
|
213
|
+
expect(round(fractionLayout.range.yLength(), 2)).to.equal(1.75);
|
|
214
|
+
expect(fractionLayout.offsetFromLine.y).to.equal(0);
|
|
215
|
+
expect(round(textLayout.offsetFromLine.y, 3)).to.equal(.375);
|
|
216
|
+
});
|
|
217
|
+
it("produces one line per paragraph if document width <= 0", () => {
|
|
218
|
+
const textBlock = TextBlock.create({ styleName: "" });
|
|
219
|
+
for (let i = 0; i < 4; i++) {
|
|
220
|
+
const layout = doLayout(textBlock);
|
|
221
|
+
if (i === 0) {
|
|
222
|
+
expect(layout.range.isNull).to.be.true;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
expect(layout.lines.length).to.equal(i);
|
|
226
|
+
expect(layout.range.low.x).to.equal(0);
|
|
227
|
+
expect(layout.range.low.y).to.equal(-i - (0.5 * (i - 1))); // lineSpacingFactor=0.5
|
|
228
|
+
expect(layout.range.high.x).to.equal(i * 3);
|
|
229
|
+
expect(layout.range.high.y).to.equal(0);
|
|
230
|
+
}
|
|
231
|
+
for (let l = 0; l < layout.lines.length; l++) {
|
|
232
|
+
const line = layout.lines[l];
|
|
233
|
+
expect(line.runs.length).to.equal(l + 1);
|
|
234
|
+
expect(line.range.low.x).to.equal(0);
|
|
235
|
+
expect(line.range.low.y).to.equal(0);
|
|
236
|
+
expect(line.range.high.y).to.equal(1);
|
|
237
|
+
expect(line.range.high.x).to.equal(3 * (l + 1));
|
|
238
|
+
for (const run of line.runs) {
|
|
239
|
+
expect(run.charOffset).to.equal(0);
|
|
240
|
+
expect(run.numChars).to.equal(3);
|
|
241
|
+
expect(run.range.low.x).to.equal(0);
|
|
242
|
+
expect(run.range.low.y).to.equal(0);
|
|
243
|
+
expect(run.range.high.x).to.equal(3);
|
|
244
|
+
expect(run.range.high.y).to.equal(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const p = textBlock.appendParagraph();
|
|
248
|
+
for (let j = 0; j <= i; j++) {
|
|
249
|
+
p.runs.push(TextRun.create({ styleName: "", content: "Run" }));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
it("produces a new line for each LineBreakRun", () => {
|
|
254
|
+
const lineSpacingFactor = 0.5;
|
|
255
|
+
const lineHeight = 1;
|
|
256
|
+
const textBlock = TextBlock.create({ styleName: "", styleOverrides: { lineSpacingFactor, lineHeight } });
|
|
257
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "abc" }));
|
|
258
|
+
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
259
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "def" }));
|
|
260
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "ghi" }));
|
|
261
|
+
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
262
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "jkl" }));
|
|
263
|
+
const tb = doLayout(textBlock);
|
|
264
|
+
expect(tb.lines.length).to.equal(3);
|
|
265
|
+
expect(tb.lines[0].runs.length).to.equal(2);
|
|
266
|
+
expect(tb.lines[1].runs.length).to.equal(3);
|
|
267
|
+
expect(tb.lines[2].runs.length).to.equal(1);
|
|
268
|
+
expect(tb.range.low.x).to.equal(0);
|
|
269
|
+
expect(tb.range.high.x).to.equal(6);
|
|
270
|
+
expect(tb.range.high.y).to.equal(0);
|
|
271
|
+
expect(tb.range.low.y).to.equal(-(lineSpacingFactor * 2 + lineHeight * 3));
|
|
272
|
+
});
|
|
273
|
+
it("applies tab shifts", () => {
|
|
274
|
+
const lineHeight = 1;
|
|
275
|
+
const tabInterval = 6;
|
|
276
|
+
const styleName = "";
|
|
277
|
+
const textBlock = TextBlock.create({ styleName, styleOverrides: { lineHeight, tabInterval } });
|
|
278
|
+
// Appends a line that looks like `stringOne` TAB `stringTwo` LINEBREAK
|
|
279
|
+
const appendLine = (stringOne, stringTwo, wantLineBreak = true) => {
|
|
280
|
+
if (stringOne.length > 0)
|
|
281
|
+
textBlock.appendRun(TextRun.create({ styleName, content: stringOne }));
|
|
282
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval } }));
|
|
283
|
+
if (stringTwo.length > 0)
|
|
284
|
+
textBlock.appendRun(TextRun.create({ styleName, content: stringTwo }));
|
|
285
|
+
if (wantLineBreak)
|
|
286
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
287
|
+
};
|
|
288
|
+
// The extra whitespace is intentional to show where the tab stops should be.
|
|
289
|
+
appendLine("", "a");
|
|
290
|
+
appendLine("", "bc");
|
|
291
|
+
appendLine("a", "a");
|
|
292
|
+
appendLine("bc", "bc");
|
|
293
|
+
appendLine("cde", "cde");
|
|
294
|
+
appendLine("cdefg", "cde"); // this one is the max tab distance before needing to move to the next tab stop
|
|
295
|
+
appendLine("cdefgh", "cde"); // This one should push to the next tab stop.
|
|
296
|
+
appendLine("cdefghi", "cde", false); // This one should push to the next tab stop.
|
|
297
|
+
const tb = doLayout(textBlock);
|
|
298
|
+
tb.lines.forEach((line, index) => {
|
|
299
|
+
const firstTextRun = (line.runs[0].source.type === "text") ? line.runs[0] : undefined;
|
|
300
|
+
const firstTabRun = (line.runs[0].source.type === "tab") ? line.runs[0] : line.runs[1];
|
|
301
|
+
const distance = (firstTextRun?.range.xLength() ?? 0) + firstTabRun.range.xLength();
|
|
302
|
+
const expectedDistance = ((firstTextRun?.range.xLength() || 0) >= tabInterval) ? tabInterval * 2 : tabInterval;
|
|
303
|
+
expect(distance).to.equal(expectedDistance, `Line ${index} does not have the expected tab distance. ${expectedDistance}`);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
it("applies consecutive tab shifts", () => {
|
|
307
|
+
const lineHeight = 1;
|
|
308
|
+
const tabInterval = 6;
|
|
309
|
+
const styleName = "";
|
|
310
|
+
const textBlock = TextBlock.create({ styleName, styleOverrides: { lineHeight, tabInterval } });
|
|
311
|
+
// line 0: ----->----->----->LINEBREAK
|
|
312
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval } }));
|
|
313
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval } }));
|
|
314
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval } }));
|
|
315
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
316
|
+
// line 1: abc-->----->LINEBREAK
|
|
317
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "abc" }));
|
|
318
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval } }));
|
|
319
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval } }));
|
|
320
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
321
|
+
// line 2: abc--->->------>LINEBREAK
|
|
322
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "abc" }));
|
|
323
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 7 } }));
|
|
324
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 2 } }));
|
|
325
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 7 } }));
|
|
326
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
327
|
+
// line 3: abc--->1/23->abcde->LINEBREAK
|
|
328
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "abc" }));
|
|
329
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 7 } }));
|
|
330
|
+
textBlock.appendRun(FractionRun.create({ styleName, numerator: "1", denominator: "23" }));
|
|
331
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 3 } }));
|
|
332
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "abcde" }));
|
|
333
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 7 } }));
|
|
334
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
335
|
+
const tb = doLayout(textBlock);
|
|
336
|
+
const line0 = tb.lines[0];
|
|
337
|
+
const line1 = tb.lines[1];
|
|
338
|
+
const line2 = tb.lines[2];
|
|
339
|
+
const line3 = tb.lines[3];
|
|
340
|
+
expect(line0.runs.length).to.equal(4);
|
|
341
|
+
expect(line0.range.xLength()).to.equal(3 * tabInterval, `Lines with only tabs should have the correct range length`);
|
|
342
|
+
expect(line1.runs.length).to.equal(4);
|
|
343
|
+
expect(line1.range.xLength()).to.equal(2 * tabInterval, `Tabs should be applied correctly when they are at the end of a line`);
|
|
344
|
+
expect(line2.runs.length).to.equal(5);
|
|
345
|
+
expect(line2.range.xLength()).to.equal(7 + 2 + 7, `Multiple tabs with different intervals should be applied correctly`);
|
|
346
|
+
expect(line3.runs.length).to.equal(7);
|
|
347
|
+
expect(line3.range.xLength()).to.equal(7 + 3 + 7, `Multiple tabs with different intervals should be applied correctly`);
|
|
348
|
+
});
|
|
349
|
+
it("computes ranges based on custom line spacing and line height", () => {
|
|
350
|
+
const lineSpacingFactor = 2;
|
|
351
|
+
const lineHeight = 3;
|
|
352
|
+
const textBlock = TextBlock.create({ styleName: "", styleOverrides: { lineSpacingFactor, lineHeight } });
|
|
353
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "abc" }));
|
|
354
|
+
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
355
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "def" }));
|
|
356
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "ghi" }));
|
|
357
|
+
textBlock.appendRun(LineBreakRun.create({ styleName: "" }));
|
|
358
|
+
textBlock.appendRun(TextRun.create({ styleName: "", content: "jkl" }));
|
|
359
|
+
const tb = doLayout(textBlock);
|
|
360
|
+
expect(tb.lines.length).to.equal(3);
|
|
361
|
+
expect(tb.lines[0].runs.length).to.equal(2);
|
|
362
|
+
expect(tb.lines[1].runs.length).to.equal(3);
|
|
363
|
+
expect(tb.lines[2].runs.length).to.equal(1);
|
|
364
|
+
// We have 3 lines each `lineHeight` high, plus 2 line breaks in between each `lineHeight*lineSpacingFactor` high.
|
|
365
|
+
expect(tb.range.low.x).to.equal(0);
|
|
366
|
+
expect(tb.range.high.x).to.equal(6);
|
|
367
|
+
expect(tb.range.high.y).to.equal(0);
|
|
368
|
+
expect(tb.range.low.y).to.equal(-(lineHeight * 3 + (lineHeight * lineSpacingFactor) * 2));
|
|
369
|
+
expect(tb.lines[0].offsetFromDocument.y).to.equal(-lineHeight);
|
|
370
|
+
expect(tb.lines[1].offsetFromDocument.y).to.equal(tb.lines[0].offsetFromDocument.y - (lineHeight + lineHeight * lineSpacingFactor));
|
|
371
|
+
expect(tb.lines[2].offsetFromDocument.y).to.equal(tb.lines[1].offsetFromDocument.y - (lineHeight + lineHeight * lineSpacingFactor));
|
|
372
|
+
expect(tb.lines.every((line) => line.offsetFromDocument.x === 0)).to.be.true;
|
|
373
|
+
});
|
|
374
|
+
function expectRange(width, height, range) {
|
|
375
|
+
expect(range.xLength()).to.equal(width);
|
|
376
|
+
expect(range.yLength()).to.equal(height);
|
|
377
|
+
}
|
|
378
|
+
it("computes range for wrapped lines", function () {
|
|
379
|
+
if (!isIntlSupported()) {
|
|
380
|
+
this.skip();
|
|
381
|
+
}
|
|
382
|
+
const block = TextBlock.create({ styleName: "", width: 3, styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
|
|
383
|
+
function expectBlockRange(width, height) {
|
|
384
|
+
const layout = doLayout(block);
|
|
385
|
+
expectRange(width, height, layout.range);
|
|
386
|
+
}
|
|
387
|
+
block.appendRun(makeTextRun("abc"));
|
|
388
|
+
expectBlockRange(3, 1);
|
|
389
|
+
block.appendRun(makeTextRun("defg"));
|
|
390
|
+
expectBlockRange(4, 2);
|
|
391
|
+
block.width = 1;
|
|
392
|
+
expectBlockRange(4, 2);
|
|
393
|
+
block.width = 8;
|
|
394
|
+
expectBlockRange(8, 1);
|
|
395
|
+
block.width = 6;
|
|
396
|
+
expectBlockRange(6, 2);
|
|
397
|
+
block.width = 10;
|
|
398
|
+
expectBlockRange(10, 1);
|
|
399
|
+
block.appendRun(makeTextRun("hijk"));
|
|
400
|
+
expectBlockRange(10, 2);
|
|
401
|
+
});
|
|
402
|
+
it("computes range for split runs", function () {
|
|
403
|
+
if (!isIntlSupported()) {
|
|
404
|
+
this.skip();
|
|
405
|
+
}
|
|
406
|
+
const block = TextBlock.create({ styleName: "", styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
|
|
407
|
+
function expectBlockRange(width, height) {
|
|
408
|
+
const layout = doLayout(block);
|
|
409
|
+
expectRange(width, height, layout.range);
|
|
410
|
+
}
|
|
411
|
+
const sentence = "a bc def ghij klmno";
|
|
412
|
+
expect(sentence.length).to.equal(19);
|
|
413
|
+
block.appendRun(makeTextRun(sentence));
|
|
414
|
+
block.width = 19;
|
|
415
|
+
expectBlockRange(19, 1);
|
|
416
|
+
block.width = 10;
|
|
417
|
+
expectBlockRange(10, 2);
|
|
418
|
+
});
|
|
419
|
+
it("justifies lines", function () {
|
|
420
|
+
if (!isIntlSupported()) {
|
|
421
|
+
this.skip();
|
|
422
|
+
}
|
|
423
|
+
const block = TextBlock.create({ styleName: "", styleOverrides: { lineSpacingFactor: 0 } });
|
|
424
|
+
function expectBlockRange(width, height) {
|
|
425
|
+
const layout = doLayout(block);
|
|
426
|
+
expectRange(width, height, layout.range);
|
|
427
|
+
}
|
|
428
|
+
function expectLineOffset(offset, lineIndex) {
|
|
429
|
+
const layout = doLayout(block);
|
|
430
|
+
expect(layout.lines.length).least(lineIndex + 1);
|
|
431
|
+
const line = layout.lines[lineIndex];
|
|
432
|
+
expect(line.offsetFromDocument.y).to.equal(-(lineIndex + 1));
|
|
433
|
+
expect(line.offsetFromDocument.x).to.equal(offset);
|
|
434
|
+
}
|
|
435
|
+
// Two text runs with 7 characters total.
|
|
436
|
+
block.appendRun(makeTextRun("abc"));
|
|
437
|
+
block.appendRun(makeTextRun("defg"));
|
|
438
|
+
// 1 line of text with width 0: left, right, center justification.
|
|
439
|
+
block.justification = "left";
|
|
440
|
+
expectBlockRange(7, 1);
|
|
441
|
+
expectLineOffset(0, 0);
|
|
442
|
+
block.justification = "right";
|
|
443
|
+
expectBlockRange(7, 1);
|
|
444
|
+
expectLineOffset(0, 0);
|
|
445
|
+
block.justification = "center";
|
|
446
|
+
expectBlockRange(7, 1);
|
|
447
|
+
expectLineOffset(0, 0);
|
|
448
|
+
// 1 line of text from a width greater than number of characters: left, right, center justification.
|
|
449
|
+
block.width = 10;
|
|
450
|
+
block.justification = "left";
|
|
451
|
+
expectBlockRange(10, 1);
|
|
452
|
+
expectLineOffset(0, 0);
|
|
453
|
+
block.justification = "right";
|
|
454
|
+
expectBlockRange(10, 1);
|
|
455
|
+
expectLineOffset(3, 0); // 3 = 10 - 7
|
|
456
|
+
block.justification = "center";
|
|
457
|
+
expectBlockRange(10, 1);
|
|
458
|
+
expectLineOffset(1.5, 0); // 1.5 = (10 - 7) / 2
|
|
459
|
+
// 2 line of text from a width less than number of characters: left, right, center justification.
|
|
460
|
+
block.justification = "left";
|
|
461
|
+
block.width = 4;
|
|
462
|
+
expectBlockRange(4, 2);
|
|
463
|
+
expectLineOffset(0, 0);
|
|
464
|
+
expectLineOffset(0, 1);
|
|
465
|
+
block.justification = "right";
|
|
466
|
+
expectBlockRange(4, 2);
|
|
467
|
+
expectLineOffset(1, 0);
|
|
468
|
+
expectLineOffset(0, 1);
|
|
469
|
+
block.justification = "center";
|
|
470
|
+
expectBlockRange(4, 2);
|
|
471
|
+
expectLineOffset(0.5, 0);
|
|
472
|
+
expectLineOffset(0, 1);
|
|
473
|
+
// Testing text longer the the width of the text block.
|
|
474
|
+
block.width = 2;
|
|
475
|
+
block.justification = "left";
|
|
476
|
+
expectBlockRange(4, 2);
|
|
477
|
+
expectLineOffset(0, 0);
|
|
478
|
+
expectLineOffset(0, 1);
|
|
479
|
+
block.justification = "right";
|
|
480
|
+
expectBlockRange(4, 2);
|
|
481
|
+
expectLineOffset(-1, 0);
|
|
482
|
+
expectLineOffset(-2, 1);
|
|
483
|
+
block.appendRun(makeTextRun("123456789"));
|
|
484
|
+
expectBlockRange(9, 3);
|
|
485
|
+
expectLineOffset(-1, 0);
|
|
486
|
+
expectLineOffset(-2, 1);
|
|
487
|
+
expectLineOffset(-7, 2);
|
|
488
|
+
block.justification = "center";
|
|
489
|
+
expectBlockRange(9, 3);
|
|
490
|
+
expectLineOffset(-0.5, 0);
|
|
491
|
+
expectLineOffset(-1, 1);
|
|
492
|
+
expectLineOffset(-3.5, 2);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
describe("word-wrapping", () => {
|
|
496
|
+
function expectLines(input, width, expectedLines) {
|
|
497
|
+
const textBlock = TextBlock.create({ styleName: "" });
|
|
498
|
+
textBlock.width = width;
|
|
499
|
+
const run = makeTextRun(input);
|
|
500
|
+
textBlock.appendRun(run);
|
|
501
|
+
const layout = doLayout(textBlock);
|
|
502
|
+
expect(layout.lines.every((line) => line.runs.every((r) => r.source === run))).to.be.true;
|
|
503
|
+
const actual = layout.lines.map((line) => line.runs.map((runLayout) => runLayout.source.content.substring(runLayout.charOffset, runLayout.charOffset + runLayout.numChars)).join(""));
|
|
504
|
+
expect(actual).to.deep.equal(expectedLines);
|
|
505
|
+
return layout;
|
|
506
|
+
}
|
|
507
|
+
it("splits paragraphs into multiple lines if runs exceed the document width", function () {
|
|
508
|
+
if (!isIntlSupported()) {
|
|
509
|
+
this.skip();
|
|
510
|
+
}
|
|
511
|
+
const textBlock = TextBlock.create({ styleName: "" });
|
|
512
|
+
textBlock.width = 6;
|
|
513
|
+
textBlock.appendRun(makeTextRun("ab"));
|
|
514
|
+
expect(doLayout(textBlock).lines.length).to.equal(1);
|
|
515
|
+
textBlock.appendRun(makeTextRun("cd"));
|
|
516
|
+
expect(doLayout(textBlock).lines.length).to.equal(1);
|
|
517
|
+
textBlock.appendRun(makeTextRun("ef"));
|
|
518
|
+
expect(doLayout(textBlock).lines.length).to.equal(1);
|
|
519
|
+
textBlock.appendRun(makeTextRun("ghi"));
|
|
520
|
+
expect(doLayout(textBlock).lines.length).to.equal(2);
|
|
521
|
+
textBlock.appendRun(makeTextRun("jklmnop"));
|
|
522
|
+
expect(doLayout(textBlock).lines.length).to.equal(3);
|
|
523
|
+
textBlock.appendRun(makeTextRun("q"));
|
|
524
|
+
expect(doLayout(textBlock).lines.length).to.equal(4);
|
|
525
|
+
textBlock.appendRun(makeTextRun("r"));
|
|
526
|
+
expect(doLayout(textBlock).lines.length).to.equal(4);
|
|
527
|
+
textBlock.appendRun(makeTextRun("stu"));
|
|
528
|
+
expect(doLayout(textBlock).lines.length).to.equal(4);
|
|
529
|
+
textBlock.appendRun(makeTextRun("vwxyz"));
|
|
530
|
+
expect(doLayout(textBlock).lines.length).to.equal(5);
|
|
531
|
+
});
|
|
532
|
+
it("splits a single TextRun at word boundaries if it exceeds the document width", function () {
|
|
533
|
+
if (!isIntlSupported()) {
|
|
534
|
+
this.skip();
|
|
535
|
+
}
|
|
536
|
+
expectLines("a bc def ghij klmno pqrstu vwxyz", 5, [
|
|
537
|
+
"a bc ",
|
|
538
|
+
"def ",
|
|
539
|
+
"ghij ",
|
|
540
|
+
"klmno ",
|
|
541
|
+
"pqrstu ",
|
|
542
|
+
"vwxyz",
|
|
543
|
+
]);
|
|
544
|
+
const fox = "The quick brown fox jumped over the lazy dog";
|
|
545
|
+
expectLines(fox, 50, [fox]);
|
|
546
|
+
expectLines(fox, 40, [
|
|
547
|
+
// 1 2 3 4
|
|
548
|
+
// 34567890123456789012345678901234567890
|
|
549
|
+
"The quick brown fox jumped over the ",
|
|
550
|
+
"lazy dog",
|
|
551
|
+
]);
|
|
552
|
+
expectLines(fox, 30, [
|
|
553
|
+
// 1 2 3
|
|
554
|
+
// 3456789012345678901234567890
|
|
555
|
+
"The quick brown fox jumped ",
|
|
556
|
+
"over the lazy dog",
|
|
557
|
+
]);
|
|
558
|
+
expectLines(fox, 20, [
|
|
559
|
+
// 1 2
|
|
560
|
+
// 345678901234567890
|
|
561
|
+
"The quick brown fox ",
|
|
562
|
+
"jumped over the ",
|
|
563
|
+
"lazy dog",
|
|
564
|
+
]);
|
|
565
|
+
expectLines(fox, 10, [
|
|
566
|
+
// 1
|
|
567
|
+
// 234567890
|
|
568
|
+
"The quick ",
|
|
569
|
+
"brown fox ",
|
|
570
|
+
"jumped ",
|
|
571
|
+
"over the ",
|
|
572
|
+
"lazy dog",
|
|
573
|
+
]);
|
|
574
|
+
});
|
|
575
|
+
it("considers consecutive whitespace part of a single 'word'", function () {
|
|
576
|
+
if (!isIntlSupported()) {
|
|
577
|
+
this.skip();
|
|
578
|
+
}
|
|
579
|
+
expectLines("a b c d e f ", 3, [
|
|
580
|
+
"a ",
|
|
581
|
+
"b ",
|
|
582
|
+
"c ",
|
|
583
|
+
"d ",
|
|
584
|
+
"e ",
|
|
585
|
+
"f ",
|
|
586
|
+
]);
|
|
587
|
+
});
|
|
588
|
+
it("wraps Japanese text", function () {
|
|
589
|
+
if (!isIntlSupported()) {
|
|
590
|
+
this.skip();
|
|
591
|
+
}
|
|
592
|
+
// "I am a cat. The name is Tanuki."
|
|
593
|
+
expectLines("吾輩は猫である。名前はたぬき。", 1, ["吾", "輩", "は", "猫", "で", "あ", "る。", "名", "前", "は", "た", "ぬ", "き。"]);
|
|
594
|
+
});
|
|
595
|
+
it("wraps tabs", function () {
|
|
596
|
+
if (!isIntlSupported()) {
|
|
597
|
+
this.skip();
|
|
598
|
+
}
|
|
599
|
+
const lineHeight = 1;
|
|
600
|
+
const styleName = "";
|
|
601
|
+
const textBlock = TextBlock.create({ styleName, styleOverrides: { lineHeight } });
|
|
602
|
+
// line 0: -->-->------> LINEBREAK
|
|
603
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 3 } }));
|
|
604
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 3 } }));
|
|
605
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 7 } }));
|
|
606
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
607
|
+
// line 1: a->b->cd-----> LINEBREAK
|
|
608
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "a" }));
|
|
609
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 3 } }));
|
|
610
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "b" }));
|
|
611
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 3 } }));
|
|
612
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "cd" }));
|
|
613
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 7 } }));
|
|
614
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
615
|
+
// line 2: -->a->b------>cd LINEBREAK
|
|
616
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 3 } }));
|
|
617
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "a" }));
|
|
618
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 3 } }));
|
|
619
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "b" }));
|
|
620
|
+
textBlock.appendRun(TabRun.create({ styleName, styleOverrides: { tabInterval: 7 } }));
|
|
621
|
+
textBlock.appendRun(TextRun.create({ styleName, content: "cd" }));
|
|
622
|
+
textBlock.appendRun(LineBreakRun.create({ styleName }));
|
|
623
|
+
/* Full Width:
|
|
624
|
+
* -->-->------>
|
|
625
|
+
* a->b->cd---->
|
|
626
|
+
* -->a->b----->cd
|
|
627
|
+
*/
|
|
628
|
+
let tb = doLayout(textBlock);
|
|
629
|
+
expect(tb.lines.length).to.equal(3, ``);
|
|
630
|
+
expect(tb.lines[0].range.xLength()).to.equal(13, ``);
|
|
631
|
+
expect(tb.lines[1].range.xLength()).to.equal(13, ``);
|
|
632
|
+
expect(tb.lines[2].range.xLength()).to.equal(15, ``);
|
|
633
|
+
/* Width of 10:
|
|
634
|
+
* -->-->
|
|
635
|
+
* ------>
|
|
636
|
+
* a->b->cd
|
|
637
|
+
* ------>
|
|
638
|
+
* -->a->b
|
|
639
|
+
* ------>cd
|
|
640
|
+
*/
|
|
641
|
+
textBlock.width = 10;
|
|
642
|
+
tb = doLayout(textBlock);
|
|
643
|
+
expect(tb.lines.length).to.equal(6, ``);
|
|
644
|
+
expect(tb.lines[0].range.xLength()).to.equal(6, ``);
|
|
645
|
+
expect(tb.lines[1].range.xLength()).to.equal(7, ``);
|
|
646
|
+
expect(tb.lines[2].range.xLength()).to.equal(8, ``);
|
|
647
|
+
expect(tb.lines[3].range.xLength()).to.equal(7, ``);
|
|
648
|
+
expect(tb.lines[4].range.xLength()).to.equal(7, ``);
|
|
649
|
+
expect(tb.lines[5].range.xLength()).to.equal(9, ``);
|
|
650
|
+
});
|
|
651
|
+
it("performs word-wrapping with punctuation", function () {
|
|
652
|
+
if (!isIntlSupported()) {
|
|
653
|
+
this.skip();
|
|
654
|
+
}
|
|
655
|
+
expectLines("1.24 56.7 8,910", 1, ["1.24 ", "56.7 ", "8,910"]);
|
|
656
|
+
expectLines("a.bc de.f g,hij", 1, ["a.bc ", "de.f ", "g,hij"]);
|
|
657
|
+
expectLines("Let's see... can you (or anyone) predict?!", 1, [
|
|
658
|
+
"Let's ",
|
|
659
|
+
"see... ",
|
|
660
|
+
"can ",
|
|
661
|
+
"you ",
|
|
662
|
+
"(or ",
|
|
663
|
+
"anyone) ",
|
|
664
|
+
"predict?!",
|
|
665
|
+
]);
|
|
666
|
+
});
|
|
667
|
+
it("performs word-wrapping and line-splitting with multiple runs", function () {
|
|
668
|
+
if (!isIntlSupported()) {
|
|
669
|
+
this.skip();
|
|
670
|
+
}
|
|
671
|
+
const textBlock = TextBlock.create({ styleName: "" });
|
|
672
|
+
for (const str of ["The ", "quick brown", " fox jumped over ", "the lazy ", "dog"]) {
|
|
673
|
+
textBlock.appendRun(makeTextRun(str));
|
|
674
|
+
}
|
|
675
|
+
function test(width, expected) {
|
|
676
|
+
textBlock.width = width;
|
|
677
|
+
const layout = doLayout(textBlock);
|
|
678
|
+
const actual = layout.lines.map((line) => line.runs.map((runLayout) => runLayout.source.content.substring(runLayout.charOffset, runLayout.charOffset + runLayout.numChars)).join(""));
|
|
679
|
+
expect(actual).to.deep.equal(expected);
|
|
680
|
+
}
|
|
681
|
+
test(50, ["The quick brown fox jumped over the lazy dog"]);
|
|
682
|
+
test(40, [
|
|
683
|
+
// 1 2 3 4
|
|
684
|
+
// 34567890123456789012345678901234567890
|
|
685
|
+
"The quick brown fox jumped over the ",
|
|
686
|
+
"lazy dog",
|
|
687
|
+
]);
|
|
688
|
+
test(30, [
|
|
689
|
+
// 1 2 3
|
|
690
|
+
// 3456789012345678901234567890
|
|
691
|
+
"The quick brown fox jumped ",
|
|
692
|
+
"over the lazy dog",
|
|
693
|
+
]);
|
|
694
|
+
test(20, [
|
|
695
|
+
// 1 2
|
|
696
|
+
// 345678901234567890
|
|
697
|
+
"The quick brown fox ",
|
|
698
|
+
"jumped over the ",
|
|
699
|
+
"lazy dog",
|
|
700
|
+
]);
|
|
701
|
+
test(10, [
|
|
702
|
+
// 1
|
|
703
|
+
// 34567890
|
|
704
|
+
"The quick ",
|
|
705
|
+
"brown fox ",
|
|
706
|
+
"jumped ",
|
|
707
|
+
"over the ",
|
|
708
|
+
"lazy dog",
|
|
709
|
+
]);
|
|
710
|
+
});
|
|
711
|
+
it("wraps multiple runs", function () {
|
|
712
|
+
if (!isIntlSupported()) {
|
|
713
|
+
this.skip();
|
|
714
|
+
}
|
|
715
|
+
const block = TextBlock.create({ styleName: "" });
|
|
716
|
+
block.appendRun(makeTextRun("aa")); // 2 chars wide
|
|
717
|
+
block.appendRun(makeTextRun("bb ccc d ee")); // 11 chars wide
|
|
718
|
+
block.appendRun(makeTextRun("ff ggg h")); // 8 chars wide
|
|
719
|
+
function expectLayout(width, expected) {
|
|
720
|
+
block.width = width;
|
|
721
|
+
const layout = doLayout(block);
|
|
722
|
+
expect(layout.stringify()).to.equal(expected);
|
|
723
|
+
}
|
|
724
|
+
expectLayout(23, "aabb ccc d eeff ggg h");
|
|
725
|
+
expectLayout(22, "aabb ccc d eeff ggg h");
|
|
726
|
+
expectLayout(21, "aabb ccc d eeff ggg h");
|
|
727
|
+
expectLayout(20, "aabb ccc d eeff ggg \nh");
|
|
728
|
+
expectLayout(19, "aabb ccc d eeff \nggg h");
|
|
729
|
+
expectLayout(18, "aabb ccc d eeff \nggg h");
|
|
730
|
+
expectLayout(17, "aabb ccc d eeff \nggg h");
|
|
731
|
+
expectLayout(16, "aabb ccc d eeff \nggg h");
|
|
732
|
+
expectLayout(15, "aabb ccc d ee\nff ggg h");
|
|
733
|
+
expectLayout(14, "aabb ccc d ee\nff ggg h");
|
|
734
|
+
expectLayout(13, "aabb ccc d ee\nff ggg h");
|
|
735
|
+
expectLayout(12, "aabb ccc d \neeff ggg h");
|
|
736
|
+
expectLayout(11, "aabb ccc d \neeff ggg h");
|
|
737
|
+
expectLayout(10, "aabb ccc \nd eeff \nggg h");
|
|
738
|
+
expectLayout(9, "aabb ccc \nd eeff \nggg h");
|
|
739
|
+
expectLayout(8, "aabb \nccc d ee\nff ggg h");
|
|
740
|
+
expectLayout(7, "aabb \nccc d \neeff \nggg h");
|
|
741
|
+
expectLayout(6, "aabb \nccc d \neeff \nggg h");
|
|
742
|
+
expectLayout(5, "aabb \nccc \nd ee\nff \nggg h");
|
|
743
|
+
expectLayout(4, "aa\nbb \nccc \nd ee\nff \nggg \nh");
|
|
744
|
+
expectLayout(3, "aa\nbb \nccc \nd \nee\nff \nggg \nh");
|
|
745
|
+
expectLayout(2, "aa\nbb \nccc \nd \nee\nff \nggg \nh");
|
|
746
|
+
expectLayout(1, "aa\nbb \nccc \nd \nee\nff \nggg \nh");
|
|
747
|
+
expectLayout(0, "aabb ccc d eeff ggg h");
|
|
748
|
+
expectLayout(-1, "aabb ccc d eeff ggg h");
|
|
749
|
+
expectLayout(-2, "aabb ccc d eeff ggg h");
|
|
750
|
+
});
|
|
751
|
+
it("does not word wrap due to floating point rounding error", function () {
|
|
752
|
+
if (!isIntlSupported()) {
|
|
753
|
+
this.skip();
|
|
754
|
+
}
|
|
755
|
+
const block = TextBlock.create({ styleName: "", styleOverrides: { lineHeight: 1, lineSpacingFactor: 0 } });
|
|
756
|
+
block.appendRun(makeTextRun("abc defg"));
|
|
757
|
+
const layout1 = doLayout(block);
|
|
758
|
+
let width = layout1.range.xLength();
|
|
759
|
+
// Simulate a floating point rounding error by slightly reducing the width
|
|
760
|
+
width -= Geometry.smallFloatingPoint;
|
|
761
|
+
block.width = width;
|
|
762
|
+
const layout2 = doLayout(block);
|
|
763
|
+
expect(layout2.range.yLength()).to.equal(1);
|
|
764
|
+
});
|
|
765
|
+
});
|
|
630
766
|
describe("grapheme offsets", () => {
|
|
631
767
|
it("should return an empty array if source type is not text", function () {
|
|
632
768
|
const textBlock = TextBlock.create({ styleName: "" });
|
|
@@ -965,5 +1101,5 @@ describe("produceTextBlockGeometry", () => {
|
|
|
965
1101
|
});
|
|
966
1102
|
});
|
|
967
1103
|
// Ignoring the text strings from the spell checker
|
|
968
|
-
// 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 अनुच्छेद
|
|
1104
|
+
// 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
|
|
969
1105
|
//# sourceMappingURL=TextBlock.test.js.map
|