From 2462590b540ca38f92047254577b17882b23e59d Mon Sep 17 00:00:00 2001 From: Diego Muracciole Date: Sun, 25 Mar 2018 20:07:37 -0300 Subject: [PATCH 1/5] Fix insert glyph --- out.pdf | Bin 6254 -> 18638 bytes src/layout/LayoutEngine.js | 5 +-- src/layout/LineBreaker.js | 61 ++++++++++++++++---------------- src/models/GlyphString.js | 9 +++-- temp.js | 13 +++---- test/models/GlyphString.test.js | 38 ++++++++++++++++++++ test/utils/glyphStrings.js | 2 +- 7 files changed, 86 insertions(+), 42 deletions(-) diff --git a/out.pdf b/out.pdf index b8a9254f5b4f4683242e19a54b763fbbc1774f7b..924614b918d2b7d4b4bfe7574e4ab2fab28beda6 100644 GIT binary patch delta 12338 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 diff --git a/src/layout/LayoutEngine.js b/src/layout/LayoutEngine.js index d18d426..95d238d 100644 --- a/src/layout/LayoutEngine.js +++ b/src/layout/LayoutEngine.js @@ -97,9 +97,10 @@ export default class LayoutEngine { const fragments = []; while (lineRect.y < rect.maxY && pos < glyphString.length && lines < maxLines) { + const lineString = glyphString.slice(pos, glyphString.length); const lineFragments = this.typesetter.layoutLineFragments( lineRect, - glyphString.slice(pos, glyphString.length), + lineString, container, paragraphStyle ); @@ -108,7 +109,7 @@ export default class LayoutEngine { if (lineFragments.length > 0) { fragments.push(...lineFragments); - pos += lineFragments[lineFragments.length - 1].end; + pos = lineFragments[lineFragments.length - 1].end; lines++; if (firstLine) { diff --git a/src/layout/LineBreaker.js b/src/layout/LineBreaker.js index dea83b2..6ba5f5f 100644 --- a/src/layout/LineBreaker.js +++ b/src/layout/LineBreaker.js @@ -21,39 +21,38 @@ export default class LineBreaker { } let stringIndex = glyphString.stringIndexForGlyphIndex(glyphIndex); - const bk = this.findBreakPreceeding(glyphString.string, stringIndex); - // if (bk) { - // let breakIndex = glyphString.glyphIndexForStringIndex(bk.position); - // - // if ( - // bk.next != null && - // this.shouldHyphenate(glyphString, breakIndex, width, hyphenationFactor) - // ) { - // const lineWidth = glyphString.offsetAtGlyphIndex(glyphIndex); - // const shrunk = lineWidth + lineWidth * SHRINK_FACTOR; - // - // const shrunkIndex = glyphString.glyphIndexAtOffset(shrunk); - // stringIndex = Math.min(bk.next, glyphString.stringIndexForGlyphIndex(shrunkIndex)); - // - // const point = this.findHyphenationPoint( - // glyphString.string.slice(bk.position, bk.next), - // stringIndex - bk.position - // ); - // - // if (point > 0) { - // bk.position += point; - // breakIndex = glyphString.glyphIndexForStringIndex(bk.position); - // - // if (bk.position < bk.next) { - // glyphString.insertGlyph(breakIndex++, HYPHEN); - // } - // } - // } - // - // bk.position = breakIndex; - // } + if (bk) { + let breakIndex = glyphString.glyphIndexForStringIndex(bk.position); + + if ( + bk.next != null && + this.shouldHyphenate(glyphString, breakIndex, width, hyphenationFactor) + ) { + const lineWidth = glyphString.offsetAtGlyphIndex(glyphIndex); + const shrunk = lineWidth + lineWidth * SHRINK_FACTOR; + + const shrunkIndex = glyphString.glyphIndexAtOffset(shrunk); + stringIndex = Math.min(bk.next, glyphString.stringIndexForGlyphIndex(shrunkIndex)); + + const point = this.findHyphenationPoint( + glyphString.string.slice(bk.position, bk.next), + stringIndex - bk.position + ); + + if (point > 0) { + bk.position += point; + breakIndex = glyphString.glyphIndexForStringIndex(bk.position); + + if (bk.position < bk.next) { + glyphString.insertGlyph(breakIndex++, HYPHEN); + } + } + } + + bk.position = breakIndex; + } return bk; } diff --git a/src/models/GlyphString.js b/src/models/GlyphString.js index 5f7a6ad..d819b70 100644 --- a/src/models/GlyphString.js +++ b/src/models/GlyphString.js @@ -112,7 +112,7 @@ class GlyphString { const idx = index + this._glyphRuns[0].stringStart + this.start; for (let i = 0; i < this._glyphRuns.length; i++) { - if (this._glyphRuns[i].stringStart <= idx && idx < this._glyphRuns[i].stringEnd + 1) { + if (this._glyphRuns[i].start <= idx && idx < this._glyphRuns[i].end) { return i; } } @@ -233,9 +233,14 @@ class GlyphString { insertGlyph(index, codePoint) { const runIndex = this.runIndexAtGlyphIndex(index); const run = this._glyphRuns[runIndex]; + const char = String.fromCharCode(codePoint); const glyph = run.attributes.font.glyphForCodePoint(codePoint); - glyph.inserted = true; // TODO: don't do this + // Should we edit the string value? + // Otherwise, the glyph runs and string would be inconsistent + this._string = + this._string.slice(0, this.start + index) + char + this._string.slice(this.start + index); + run.glyphs.splice(this.start + index - run.start, 0, glyph); run.positions.splice(this.start + index - run.start, 0, { xAdvance: glyph.advanceWidth, diff --git a/temp.js b/temp.js index 7d34fab..2eba6da 100644 --- a/temp.js +++ b/temp.js @@ -11,7 +11,7 @@ const path = new Path(); path.rect(0, 0, 300, 400); const exclusion = new Path(); -exclusion.circle(200, 200, 50); +exclusion.circle(135, 30, 20); const doc = new PDFDocument(); doc.pipe(fs.createWriteStream('out.pdf')); @@ -30,8 +30,8 @@ const string = AttributedString.fromFragments([ fontSize: 14, bold: true, // align: 'justify', - // hyphenationFactor: 0.9, - // hangingPunctuation: true, + hyphenationFactor: 0.9, + hangingPunctuation: true, lineSpacing: 5, underline: true, underlineStyle: 'wavy', @@ -44,13 +44,14 @@ const string = AttributedString.fromFragments([ attributes: { font: 'Arial', fontSize: 14, color: 'red' } }, { - string: 'sed do eiusmod tempor incididunt ut labore', + string: + 'sed 🎉 do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea volupt\u0301ate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?”', attributes: { font: 'Arial', fontSize: 14, // align: 'justify', - // hyphenationFactor: 0.9, - // hangingPunctuation: true, + hyphenationFactor: 0.9, + hangingPunctuation: true, lineSpacing: 5, truncate: true } diff --git a/test/models/GlyphString.test.js b/test/models/GlyphString.test.js index 2a53717..358b6fa 100644 --- a/test/models/GlyphString.test.js +++ b/test/models/GlyphString.test.js @@ -483,4 +483,42 @@ describe('GlyphString', () => { expect(sliced.isWhiteSpace(0)).toBeFalsy(); expect(sliced.isWhiteSpace(1)).toBeTruthy(); }); + + test('should successfully insert glyph', () => { + const string = createString({ + value: 'Lorem ipsum', + runs: [[0, 6], [6, 11]] + }); + + string.insertGlyph(2, 0x002d); + + expect(string.start).toBe(0); + expect(string.end).toBe(12); + expect(string.string).toBe('Lo-rem ipsum'); + expect(string._glyphRuns[0].start).toBe(0); + expect(string._glyphRuns[0].end).toBe(7); + expect(string._glyphRuns[1].start).toBe(7); + expect(string._glyphRuns[1].end).toBe(12); + expect(string._glyphRuns[0].glyphs[2].id).toBe(16); + }); + + test('should successfully insert glyph for sliced strings', () => { + const string = createString({ + value: 'Lorem ipsum', + runs: [[0, 6], [6, 11]] + }); + + const sliced = string.slice(4, string.length); + + sliced.insertGlyph(2, 0x002d); + + expect(sliced.start).toBe(4); + expect(sliced.end).toBe(12); + expect(sliced.string).toBe('m -ipsum'); + expect(sliced._glyphRuns[0].start).toBe(0); + expect(sliced._glyphRuns[0].end).toBe(6); + expect(sliced._glyphRuns[1].start).toBe(6); + expect(sliced._glyphRuns[1].end).toBe(12); + expect(sliced._glyphRuns[1].glyphs[0].id).toBe(16); + }); }); diff --git a/test/utils/glyphStrings.js b/test/utils/glyphStrings.js index fbf4bfb..f24be4b 100644 --- a/test/utils/glyphStrings.js +++ b/test/utils/glyphStrings.js @@ -17,5 +17,5 @@ export const glyphStringFactory = font => ({ }).glyphRun ); - return new GlyphString(value, glyphRuns); + return new GlyphString(value, glyphRuns, 0, value.length); }; From 0a8862b091ce317f9280535015eb0ad6307111b2 Mon Sep 17 00:00:00 2001 From: Diego Muracciole Date: Mon, 26 Mar 2018 00:47:39 -0300 Subject: [PATCH 2/5] Fix GlyphRun and add tests --- out.pdf | Bin 18638 -> 27683 bytes src/models/GlyphRun.js | 15 +- src/models/GlyphString.js | 26 +-- test/data/Khmer.ttf | Bin 0 -> 77684 bytes test/models/GlyphRun.test.js | 230 ++++++++++++++++++++----- test/models/GlyphString.test.js | 296 +++++++++++++++++++++++++------- 6 files changed, 447 insertions(+), 120 deletions(-) create mode 100644 test/data/Khmer.ttf diff --git a/out.pdf b/out.pdf index 924614b918d2b7d4b4bfe7574e4ab2fab28beda6..b248413312e88935eccb43fb34a95d42adf38c55 100644 GIT binary patch delta 25551 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 delta 16449 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 KkO;{L!~7o|{Dckw diff --git a/src/models/GlyphRun.js b/src/models/GlyphRun.js index e5585df..c21e3c4 100644 --- a/src/models/GlyphRun.js +++ b/src/models/GlyphRun.js @@ -7,8 +7,6 @@ class GlyphRun extends Run { this.positions = positions; this.stringIndices = stringIndices; this.scale = attributes.fontSize / attributes.font.unitsPerEm; - this.stringStart = Math.min(...stringIndices); - this.stringEnd = Math.max(...stringIndices); this.glyphIndices = []; for (let i = 0; i < stringIndices.length; i++) { @@ -61,15 +59,18 @@ class GlyphRun extends Run { } slice(start, end) { + const glyphStart = this.glyphIndices[start]; + const glyphEnd = this.glyphIndices[end] || this.end; + start += this.start; end += this.start; - end = Math.min(end, this.start + this.glyphs.length); + end = Math.min(end, this.end); - const glyphs = this.glyphs.slice(start - this.start, end - this.start); - const positions = this.positions.slice(start - this.start, end - this.start); + const glyphs = this.glyphs.slice(glyphStart - this.start, glyphEnd - this.start); + const positions = this.positions.slice(glyphStart - this.start, glyphEnd - this.start); const stringIndices = this.stringIndices - .slice(start - this.start, end - this.start) - .map(index => index - this.stringIndices[start - this.start]); + .slice(glyphStart - this.start, glyphEnd - this.start) + .map(index => index - this.stringIndices[glyphStart - this.start]); return new GlyphRun(start, end, this.attributes, glyphs, positions, stringIndices, true); } diff --git a/src/models/GlyphString.js b/src/models/GlyphString.js index d819b70..a535cdf 100644 --- a/src/models/GlyphString.js +++ b/src/models/GlyphString.js @@ -67,8 +67,8 @@ class GlyphString { return this._glyphRunsCache; } - const startRunIndex = this.runIndexAtGlyphIndex(0); - const endRunIndex = this.runIndexAtGlyphIndex(this.length); + const startRunIndex = this.runIndexAtStringIndex(0); + const endRunIndex = this.runIndexAtStringIndex(this.length); const startRun = this._glyphRuns[startRunIndex]; const endRun = this._glyphRuns[endRunIndex]; const runs = []; @@ -94,11 +94,16 @@ class GlyphString { runIndexAtGlyphIndex(index) { const idx = index + this.start; + let count = 0; for (let i = 0; i < this._glyphRuns.length; i++) { - if (this._glyphRuns[i].start <= idx && idx < this._glyphRuns[i].end) { + const run = this._glyphRuns[i]; + + if (count <= idx && idx < count + run.glyphs.length) { return i; } + + count += run.glyphs.length; } return this._glyphRuns.length - 1; @@ -109,7 +114,7 @@ class GlyphString { } runIndexAtStringIndex(index) { - const idx = index + this._glyphRuns[0].stringStart + this.start; + const idx = index + this._glyphRuns[0].start + this.start; for (let i = 0; i < this._glyphRuns.length; i++) { if (this._glyphRuns[i].start <= idx && idx < this._glyphRuns[i].end) { @@ -233,16 +238,13 @@ class GlyphString { insertGlyph(index, codePoint) { const runIndex = this.runIndexAtGlyphIndex(index); const run = this._glyphRuns[runIndex]; - const char = String.fromCharCode(codePoint); const glyph = run.attributes.font.glyphForCodePoint(codePoint); + const idx = this.start + index - run.start; - // Should we edit the string value? - // Otherwise, the glyph runs and string would be inconsistent - this._string = - this._string.slice(0, this.start + index) + char + this._string.slice(this.start + index); - - run.glyphs.splice(this.start + index - run.start, 0, glyph); - run.positions.splice(this.start + index - run.start, 0, { + run.glyphs.splice(idx, 0, glyph); + run.glyphIndices.splice(idx, 0, run.glyphIndices[idx]); + run.stringIndices.splice(idx, 0, run.stringIndices[idx]); + run.positions.splice(idx, 0, { xAdvance: glyph.advanceWidth, yAdvance: 0, xOffset: 0, diff --git a/test/data/Khmer.ttf b/test/data/Khmer.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e127df46607865834d20f916fd27d9c92437c4ad GIT binary patch literal 77684 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 literal 0 HcmV?d00001 diff --git a/test/models/GlyphRun.test.js b/test/models/GlyphRun.test.js index cc6e912..3d52965 100644 --- a/test/models/GlyphRun.test.js +++ b/test/models/GlyphRun.test.js @@ -3,100 +3,250 @@ import fontkit from 'fontkit'; import Attachment from '../../src/models/Attachment'; import { glyphRunFactory } from '../utils/glyphRuns'; -const font = fontkit.openSync(path.resolve(__dirname, '../data/OpenSans-Regular.ttf')); +const khmer = fontkit.openSync(path.resolve(__dirname, '../data/Khmer.ttf')); +const openSans = fontkit.openSync(path.resolve(__dirname, '../data/OpenSans-Regular.ttf')); -const createRun = glyphRunFactory(font); +const createKhmerRun = glyphRunFactory(khmer); +const createLatinRun = glyphRunFactory(openSans); + +const round = num => Math.round(num * 100) / 100; describe('GlyphRun', () => { test('should get correct length', () => { - const { glyphRun, glyphs } = createRun(); + const { glyphRun, glyphs } = createLatinRun(); expect(glyphRun.length).toBe(glyphs.length); }); test('should get ascent correctly when no attachments', () => { - const { glyphRun, attrs } = createRun(); - const scale = attrs.fontSize / font.unitsPerEm; + const { glyphRun, attrs } = createLatinRun(); + const scale = attrs.fontSize / openSans.unitsPerEm; - expect(glyphRun.ascent).toBe(font.ascent * scale); + expect(glyphRun.ascent).toBe(openSans.ascent * scale); }); test('should get ascent correctly when higher attachments', () => { const attachment = new Attachment(20, 50); - const { glyphRun } = createRun({ attributes: { attachment } }); + const { glyphRun } = createLatinRun({ attributes: { attachment } }); expect(glyphRun.ascent).toBe(50); }); test('should get ascent correctly when lower attachments', () => { const attachment = new Attachment(20, 10); - const { glyphRun, attrs } = createRun({ attributes: { attachment } }); - const scale = attrs.fontSize / font.unitsPerEm; + const { glyphRun, attrs } = createLatinRun({ attributes: { attachment } }); + const scale = attrs.fontSize / openSans.unitsPerEm; - expect(glyphRun.ascent).toBe(font.ascent * scale); + expect(glyphRun.ascent).toBe(openSans.ascent * scale); }); test('should get descent correctly when no attachments', () => { const fontSize = 20; - const { glyphRun } = createRun({ attributes: { fontSize } }); - const scale = fontSize / font.unitsPerEm; + const { glyphRun } = createLatinRun({ attributes: { fontSize } }); + const scale = fontSize / openSans.unitsPerEm; - expect(glyphRun.descent).toBe(font.descent * scale); + expect(glyphRun.descent).toBe(openSans.descent * scale); }); test('should get descent correctly when no attachments', () => { const fontSize = 20; - const { glyphRun } = createRun({ attributes: { fontSize } }); - const scale = fontSize / font.unitsPerEm; + const { glyphRun } = createLatinRun({ attributes: { fontSize } }); + const scale = fontSize / openSans.unitsPerEm; - expect(glyphRun.descent).toBe(font.descent * scale); + expect(glyphRun.descent).toBe(openSans.descent * scale); }); test('should get lineGap correctly when no attachments', () => { const fontSize = 20; - const { glyphRun } = createRun({ attributes: { fontSize } }); - const scale = fontSize / font.unitsPerEm; + const { glyphRun } = createLatinRun({ attributes: { fontSize } }); + const scale = fontSize / openSans.unitsPerEm; - expect(glyphRun.lineGap).toBe(font.lineGap * scale); + expect(glyphRun.lineGap).toBe(openSans.lineGap * scale); }); test('should get height correctly when no attachments', () => { const fontSize = 20; - const { glyphRun } = createRun({ attributes: { fontSize } }); - const scale = fontSize / font.unitsPerEm; + const { glyphRun } = createLatinRun({ attributes: { fontSize } }); + const scale = fontSize / openSans.unitsPerEm; - const expectedHeight = (font.ascent - font.descent + font.lineGap) * scale; + const expectedHeight = (openSans.ascent - openSans.descent + openSans.lineGap) * scale; expect(glyphRun.height).toBe(expectedHeight); }); + test('should exact slice range return same run', () => { + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const sliced = glyphRun.slice(0, 11); + + expect(sliced.start).toBe(0); + expect(sliced.end).toBe(11); + }); + test('should slice containing range', () => { - const { glyphRun } = createRun(); + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); const sliced = glyphRun.slice(2, 5); - const getId = g => g.id; - const expectedGlyphs = glyphRun.glyphs.slice(2, 5); - const expectedPositions = glyphRun.positions.slice(2, 5); - - expect(sliced.end).toBe(5); expect(sliced.start).toBe(2); - expect(sliced.stringIndices[0]).toEqual(0); - expect(sliced.positions).toEqual(expectedPositions); - expect(sliced.glyphs.map(getId)).toEqual(expectedGlyphs.map(getId)); + expect(sliced.end).toBe(5); }); test('should slice exceeding range', () => { - const { glyphRun } = createRun(); + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); const sliced = glyphRun.slice(2, 20); - const getId = g => g.id; - const expectedGlyphs = glyphRun.glyphs.slice(2); - const expectedPositions = glyphRun.positions.slice(2); - expect(sliced.start).toBe(2); expect(sliced.end).toBe(11); - expect(sliced.end).toBe(glyphRun.end); - expect(sliced.stringIndices[0]).toEqual(0); - expect(sliced.glyphs.map(getId)).toEqual(expectedGlyphs.map(getId)); - expect(sliced.positions).toEqual(expectedPositions); + }); + + test('should slice containing range when start not zero', () => { + const { glyphRun } = createLatinRun({ start: 5, end: 11 }); + const sliced = glyphRun.slice(2, 5); + + expect(sliced.start).toBe(7); + expect(sliced.end).toBe(10); + }); + + test('should slice exceeding range when start not zero', () => { + const { glyphRun } = createLatinRun({ start: 5, end: 11 }); + const sliced = glyphRun.slice(2, 20); + + expect(sliced.start).toBe(7); + expect(sliced.end).toBe(11); + }); + + test('should correctly slice glyphs', () => { + // 47 82 85 72 80 3 918 83 86 88 80 + // l o r e m i p s u m + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphs } = glyphRun.slice(2, 8); + + expect(glyphs.map(g => g.id)).toEqual([85, 72, 80, 3, 918, 83]); + }); + + test('should correctly slice glyphs (non latin)', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphs } = glyphRun.slice(1, 8); + + expect(glyphs.map(g => g.id)).toEqual([153, 177, 112, 248, 188, 49]); + }); + + test('should exact slice return same glyphs', () => { + // 47 82 85 72 80 3 918 83 86 88 80 + // l o r e m i p s u m + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphs } = glyphRun.slice(0, 11); + + expect(glyphs.map(g => g.id)).toEqual([47, 82, 85, 72, 80, 3, 918, 83, 86, 88, 80]); + }); + + test('should exact slice return same glyphs (non latin)', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphs } = glyphRun.slice(0, 21); + + expect(glyphs.map(g => g.id)).toEqual([ + 45, + 153, + 177, + 112, + 248, + 188, + 49, + 296, + 44, + 187, + 149, + 44, + 117, + 236, + 188, + 63 + ]); + }); + + test('should correctly slice positions', () => { + // 6.23 7.25 4.66 6.73 11.16 3.12 3.35 7.35 5.72 7.37 11.16 + // l o r e m i p s u m + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const sliced = glyphRun.slice(2, 8); + const positions = sliced.positions.map(p => round(p.xAdvance)); + + expect(positions).toEqual([4.66, 6.73, 11.16, 3.12, 3.35, 7.35]); + }); + + test('should correctly slice positions (non latin)', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const sliced = glyphRun.slice(1, 8); + const positions = sliced.positions.map(p => round(p.xAdvance)); + + expect(positions).toEqual([0, 0, 0, 9.08, 4.54, 9.08]); + }); + + test('should exact slice return same positions', () => { + // 6.23 7.25 4.66 6.73 11.16 3.12 3.35 7.35 5.72 7.37 11.16 + // l o r e m i p s u m + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const sliced = glyphRun.slice(0, 11); + const positions = sliced.positions.map(p => round(p.xAdvance)); + + expect(positions).toEqual([6.23, 7.25, 4.66, 6.73, 11.16, 3.12, 3.35, 7.35, 5.72, 7.37, 11.16]); + }); + + test('should exact slice return same positions (non latin)', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const sliced = glyphRun.slice(0, 21); + const positions = sliced.positions.map(p => round(p.xAdvance)); + + expect(positions).toEqual([ + 9.08, + 0, + 0, + 0, + 9.08, + 4.54, + 9.08, + 18.16, + 9.08, + 13.62, + 0, + 9.08, + 0, + 9.08, + 4.54, + 9.08 + ]); + }); + + test('should correctly slice string indices', () => { + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { stringIndices } = glyphRun.slice(2, 8); + + expect(stringIndices).toEqual([0, 1, 2, 3, 4, 5]); + }); + + test('should correctly slice string indices (non latin)', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { stringIndices } = glyphRun.slice(1, 8); + + expect(stringIndices).toEqual([0, 2, 3, 4, 5, 6]); + }); + + test('should exact slice return same string indices', () => { + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { stringIndices } = glyphRun.slice(0, 11); + + expect(stringIndices).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + }); + + test('should exact slice return same string indices (non latin)', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { stringIndices } = glyphRun.slice(0, 21); + + expect(stringIndices).toEqual([0, 1, 3, 4, 5, 6, 7, 8, 12, 13, 14, 16, 17, 18, 19, 20]); + }); + + test('should correctly slice glyph indices', () => { + const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphIndices } = glyphRun.slice(2, 8); + + expect(glyphIndices).toEqual([0, 1, 2, 3, 4, 5]); }); }); diff --git a/test/models/GlyphString.test.js b/test/models/GlyphString.test.js index 358b6fa..d63c21c 100644 --- a/test/models/GlyphString.test.js +++ b/test/models/GlyphString.test.js @@ -2,67 +2,125 @@ import path from 'path'; import fontkit from 'fontkit'; import { glyphStringFactory } from '../utils/glyphStrings'; -const font = fontkit.openSync(path.resolve(__dirname, '../data/OpenSans-Regular.ttf')); +const openSans = fontkit.openSync(path.resolve(__dirname, '../data/OpenSans-Regular.ttf')); +const khmer = fontkit.openSync(path.resolve(__dirname, '../data/Khmer.ttf')); -const createString = glyphStringFactory(font); +const createLatinString = glyphStringFactory(openSans); +const createKhmerString = glyphStringFactory(khmer); describe('GlyphString', () => { test('should get string value', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.string).toBe('Lorem ipsum'); }); test('should get string end', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.end).toBe(11); }); test('should get string length', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.length).toBe(11); }); test('should get string advance width', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.advanceWidth).toBeCloseTo(74, 0); }); test('should get string height', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.height).toBeCloseTo(16, 0); }); test('should get string ascent', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.ascent).toBeCloseTo(13, 0); }); test('should get string descent', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.descent).toBeCloseTo(-3.5, 1); }); + test('should get glyphs run', () => { + const string = createLatinString({ + value: 'Lorem ipsum', + runs: [[0, 6], [6, 11]] + }); + + expect(string.glyphRuns).toHaveLength(2); + expect(string.glyphRuns[0].start).toBe(0); + expect(string.glyphRuns[0].end).toBe(6); + expect(string.glyphRuns[1].start).toBe(6); + expect(string.glyphRuns[1].end).toBe(11); + }); + + test('should get glyphs run for sliced string', () => { + const string = createLatinString({ + value: 'Lorem ipsum', + runs: [[0, 6], [6, 11]] + }); + + const sliced = string.slice(2, 8); + + expect(sliced.glyphRuns).toHaveLength(2); + expect(sliced.glyphRuns[0].start).toBe(2); + expect(sliced.glyphRuns[0].end).toBe(6); + expect(sliced.glyphRuns[1].start).toBe(6); + expect(sliced.glyphRuns[1].end).toBe(8); + }); + + test('should get glyphs run (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.glyphRuns).toHaveLength(2); + expect(string.glyphRuns[0].start).toBe(0); + expect(string.glyphRuns[0].end).toBe(8); + expect(string.glyphRuns[1].start).toBe(8); + expect(string.glyphRuns[1].end).toBe(21); + }); + + test('should get glyphs run for sliced string (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(2, 8); + + expect(sliced.glyphRuns).toHaveLength(2); + expect(sliced.glyphRuns[0].start).toBe(2); + expect(sliced.glyphRuns[0].end).toBe(6); + expect(sliced.glyphRuns[1].start).toBe(6); + expect(sliced.glyphRuns[1].end).toBe(8); + }); + test('should isWhiteSpace return true if white space', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.isWhiteSpace(5)).toBeTruthy(); }); test('should isWhiteSpace return false if non white space', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.isWhiteSpace(3)).toBeFalsy(); }); test('should slice containing range', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); const sliced = string.slice(2, 6); expect(sliced.string).toBe('rem '); @@ -72,7 +130,7 @@ describe('GlyphString', () => { }); test('should slice exceeding range', () => { - const string = createString({ value: 'Lorem ipsum' }); + const string = createLatinString({ value: 'Lorem ipsum' }); const sliced = string.slice(2, 14); expect(sliced.string).toBe('rem ipsum'); @@ -82,7 +140,7 @@ describe('GlyphString', () => { }); test('should ignore unnecesary trailing runs when slice', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -95,7 +153,7 @@ describe('GlyphString', () => { }); test('should ignore unnecesary leading runs when slice', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -108,7 +166,7 @@ describe('GlyphString', () => { }); test('should return correct run index at glyph index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -118,7 +176,7 @@ describe('GlyphString', () => { }); test('should return correct run index at glyph index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -131,8 +189,40 @@ describe('GlyphString', () => { expect(sliced.runIndexAtGlyphIndex(9)).toBe(1); }); + test('should return correct run index at glyph index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.runIndexAtGlyphIndex(0)).toBe(0); + expect(string.runIndexAtGlyphIndex(4)).toBe(0); + expect(string.runIndexAtGlyphIndex(6)).toBe(0); + expect(string.runIndexAtGlyphIndex(7)).toBe(1); + expect(string.runIndexAtGlyphIndex(11)).toBe(1); + expect(string.runIndexAtGlyphIndex(15)).toBe(1); + }); + + test.skip('should return correct run index at glyph index for sliced strings (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 11); + + console.log(sliced); + + expect(sliced.runIndexAtGlyphIndex(0)).toBe(0); + expect(sliced.runIndexAtGlyphIndex(4)).toBe(0); + expect(sliced.runIndexAtGlyphIndex(6)).toBe(0); + expect(sliced.runIndexAtGlyphIndex(7)).toBe(1); + expect(sliced.runIndexAtGlyphIndex(11)).toBe(1); + expect(sliced.runIndexAtGlyphIndex(15)).toBe(1); + }); + test('should return correct run at glyph index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -142,7 +232,7 @@ describe('GlyphString', () => { }); test('should return correct run at glyph index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -156,7 +246,7 @@ describe('GlyphString', () => { }); test('should return correct run index at string index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -166,7 +256,7 @@ describe('GlyphString', () => { }); test('should return correct run index at string index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -179,8 +269,38 @@ describe('GlyphString', () => { expect(sliced.runIndexAtStringIndex(5)).toBe(1); }); + test('should return correct run index at string index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.runIndexAtStringIndex(0)).toBe(0); + expect(string.runIndexAtStringIndex(4)).toBe(0); + expect(string.runIndexAtStringIndex(7)).toBe(0); + expect(string.runIndexAtStringIndex(8)).toBe(1); + expect(string.runIndexAtStringIndex(14)).toBe(1); + expect(string.runIndexAtStringIndex(20)).toBe(1); + }); + + test('should return correct run index at string index for sliced strings (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 11); + + expect(sliced.runIndexAtStringIndex(0)).toBe(0); + expect(sliced.runIndexAtStringIndex(2)).toBe(0); + expect(sliced.runIndexAtStringIndex(3)).toBe(0); + expect(sliced.runIndexAtStringIndex(4)).toBe(1); + expect(sliced.runIndexAtStringIndex(5)).toBe(1); + expect(sliced.runIndexAtStringIndex(6)).toBe(1); + }); + test('should return correct run at string index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -190,7 +310,7 @@ describe('GlyphString', () => { }); test('should return correct run at string index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -203,8 +323,32 @@ describe('GlyphString', () => { expect(sliced.runAtStringIndex(5).start).toBe(6); }); + test('should return correct run at string index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.runAtStringIndex(2).start).toBe(0); + expect(string.runAtStringIndex(9).start).toBe(8); + }); + + test('should return correct run at string index for sliced strings (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 11); + + expect(sliced.runAtStringIndex(0).start).toBe(0); + expect(sliced.runAtStringIndex(1).start).toBe(0); + expect(sliced.runAtStringIndex(4).start).toBe(8); + expect(sliced.runAtStringIndex(6).start).toBe(8); + }); + test('should return correct glyph at index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -217,7 +361,7 @@ describe('GlyphString', () => { }); test('should return correct glyph at index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -232,8 +376,39 @@ describe('GlyphString', () => { expect(sliced.glyphAtIndex(5).id).toBe(secondRunGlyphs[3].id); }); + test.skip('should return correct glyph at index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const firstRunGlyphs = string._glyphRuns[0].glyphs; + const secondRunGlyphs = string._glyphRuns[1].glyphs; + + expect(string.glyphAtIndex(2).id).toBe(firstRunGlyphs[2].id); + expect(string.glyphAtIndex(6).id).toBe(firstRunGlyphs[6].id); + expect(string.glyphAtIndex(7).id).toBe(secondRunGlyphs[0].id); + expect(string.glyphAtIndex(15).id).toBe(secondRunGlyphs[8].id); + }); + + test.skip('should return correct glyph at index for sliced strings (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 11); + const firstRunGlyphs = sliced._glyphRuns[0].glyphs; + const secondRunGlyphs = sliced._glyphRuns[1].glyphs; + + const runs = sliced.glyphRuns; + // expect(sliced.glyphAtIndex(0).id).toBe(firstRunGlyphs[3].id); + // expect(sliced.glyphAtIndex(3).id).toBe(firstRunGlyphs[6].id); + // expect(sliced.glyphAtIndex(4).id).toBe(secondRunGlyphs[0].id); + }); + test('should return correct glyph width at index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -246,7 +421,7 @@ describe('GlyphString', () => { }); test('should return correct glyph width at index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -264,7 +439,7 @@ describe('GlyphString', () => { test('should return correct glyph index at offset', () => { // l o r e m i p s u m // 6.22 7.24 4.65 6.73 11.16 3.11 3.03 7.35 5.72 7.36 11.16 - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -276,7 +451,7 @@ describe('GlyphString', () => { }); test('should return correct glyph index at offset for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -294,7 +469,7 @@ describe('GlyphString', () => { }); test('should return correct string index for glyph index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -306,7 +481,7 @@ describe('GlyphString', () => { }); test('should return correct string index for glyph index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -319,7 +494,7 @@ describe('GlyphString', () => { }); test('should return correct glyph index for string index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -331,7 +506,7 @@ describe('GlyphString', () => { }); test('should return correct glyph index for string index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -344,7 +519,7 @@ describe('GlyphString', () => { }); test('should return correct glyph code at glyph index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -354,7 +529,7 @@ describe('GlyphString', () => { }); test('should return correct glyph code at glyph index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -368,7 +543,7 @@ describe('GlyphString', () => { }); test('should return correct char at glyph index', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -378,7 +553,7 @@ describe('GlyphString', () => { }); test('should return correct char at glyph index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -394,7 +569,7 @@ describe('GlyphString', () => { test('should return correct offset at glyph index', () => { // l o r e m i p s u m // 6.22 7.24 4.65 6.73 11.16 3.11 3.03 7.35 5.72 7.36 11.16 - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -406,7 +581,7 @@ describe('GlyphString', () => { }); test('should return correct offset at glyph index for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -423,7 +598,7 @@ describe('GlyphString', () => { }); test('should return correct index of', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -437,7 +612,7 @@ describe('GlyphString', () => { }); test('should return correct index of for sliced strings', () => { - const string = createString({ + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); @@ -452,7 +627,7 @@ describe('GlyphString', () => { }); test('should return correct unicode category', () => { - const string = createString({ + const string = createLatinString({ value: 'Lo1.+ ' }); @@ -465,18 +640,14 @@ describe('GlyphString', () => { }); test('should return if is white space for index', () => { - const string = createString({ - value: 'L ' - }); + const string = createLatinString({ value: 'L ' }); expect(string.isWhiteSpace(0)).toBeFalsy(); expect(string.isWhiteSpace(1)).toBeTruthy(); }); test('should return if is white space for index for sliced string', () => { - const string = createString({ - value: 'someL ' - }); + const string = createLatinString({ value: 'someL ' }); const sliced = string.slice(4, string.length); @@ -485,40 +656,43 @@ describe('GlyphString', () => { }); test('should successfully insert glyph', () => { - const string = createString({ - value: 'Lorem ipsum', - runs: [[0, 6], [6, 11]] - }); + const char = 0x002d; // "-" + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); - string.insertGlyph(2, 0x002d); + string.insertGlyph(2, char); expect(string.start).toBe(0); expect(string.end).toBe(12); - expect(string.string).toBe('Lo-rem ipsum'); expect(string._glyphRuns[0].start).toBe(0); expect(string._glyphRuns[0].end).toBe(7); expect(string._glyphRuns[1].start).toBe(7); expect(string._glyphRuns[1].end).toBe(12); expect(string._glyphRuns[0].glyphs[2].id).toBe(16); + + // Test string indices. + // The new glyph shouldn't interfer with current indices + expect(string._glyphRuns[0].stringIndices[2]).toBe(2); + expect(string._glyphRuns[0].stringIndices[3]).toBe(2); }); test('should successfully insert glyph for sliced strings', () => { - const string = createString({ - value: 'Lorem ipsum', - runs: [[0, 6], [6, 11]] - }); - + const char = 0x002d; // "-" + const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); const sliced = string.slice(4, string.length); - sliced.insertGlyph(2, 0x002d); + sliced.insertGlyph(2, char); expect(sliced.start).toBe(4); expect(sliced.end).toBe(12); - expect(sliced.string).toBe('m -ipsum'); expect(sliced._glyphRuns[0].start).toBe(0); expect(sliced._glyphRuns[0].end).toBe(6); expect(sliced._glyphRuns[1].start).toBe(6); expect(sliced._glyphRuns[1].end).toBe(12); expect(sliced._glyphRuns[1].glyphs[0].id).toBe(16); + + // Test string indices. + // The new glyph shouldn't interfer with current indices + expect(string._glyphRuns[1].stringIndices[0]).toBe(0); + expect(string._glyphRuns[1].stringIndices[1]).toBe(0); }); }); From 8ba8cc9adb54adffa6401527cfbb9dbfcdb0a2a9 Mon Sep 17 00:00:00 2001 From: Diego Muracciole Date: Mon, 26 Mar 2018 05:07:25 -0300 Subject: [PATCH 3/5] Add some more GlyphString tests --- out.pdf | Bin 27683 -> 10520 bytes src/models/GlyphRun.js | 68 ++++++-- src/models/GlyphString.js | 84 +++++++-- test/models/GlyphString.test.js | 292 ++++++++++++++++++++++---------- 4 files changed, 324 insertions(+), 120 deletions(-) diff --git a/out.pdf b/out.pdf index b248413312e88935eccb43fb34a95d42adf38c55..2c4104c8963dbd68852149d78122ec363f9d3179 100644 GIT binary patch delta 9128 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 V7q@?R^|x|1b`UkSw2BPq{{d%NyFUN` delta 25703 zcmagE1C%Du(kDD^P209@+wN)Gw%z?q+qP}nwr$&X_jLEp|L%Kt@4Nf$`JOmWRb*tG zh>ZM2Rc2L1s$#(^Q0xFHy%-AtI{^cMA~YW#G`+gKp{0qDGXW0|G`%Dv0mpxJBoG3j z$8GXKFfgz)F>6vL=z(G+@Bl+62mzsi%d4rY2rDJ%0`(y?GW^?+iLJ4np(QjUQvxF} z$G=tVfX#s!85354!`KO!nVISSTH|13CSYP`r{iE|Vqj+cPbn)a8yzDn6Eh3Te<&mv zfbjp@S}_PaDbqjxbfD>#?Nn?njO>g}2>#mpuUP&)1lGSpK>cT=(#k^eg0d>qf{qpj z*0Rdb^kUWqW=;ewe;GSlXCWawcLFV1HZ~SIR@Q$8MnKEV#6ri;!2Zv`m>3us2pAa{ zSm`)eIhom66H0-^Alc}c8U8j|5}tu5kr`S3PseQk#QyU?ohEn!>i`&8|D{N;Mxez& zpvB0_%tpZa*Z6d?-6NtIN8k7_Wn34To zXC=meQtzJ>`X>{WJ?u>g{+*l9^hz#<&i|4~+8CIbK+{W@SeThR|HTNJUd_VT+5BIe zkcG38f{CNBosGSnt%nrX*v{I{QOVxG=&wZ)6ITl(6LCia59q)Azw+lV zCX9a*O2)+2?5`na28Msaa&mSwF|dJl&s>N&HrCe<)vlrFg)PJ^dc^=zp2yE8aMA51>$p?Au-_-p?J}EbE&%p<@T=U<{5;^o~q0@ly_s zwg`kx9esbNz$;>btf`?;PfwWY&py>Z>D}W)Mjts!ZapYP+=F8FaUJkuY&x5AUrhf1 zO7`*<2JJD{f^?!>2mgCP+S9#4Abxz8lz+YkIhCw1Sz73Rz(c?%*q{b+a+XwoL?d|R z!!t!mx}E???D;Iw&9MSrm5l4WA|4j2FqvXSvgH_!^+z+t8bRppJq&=+L3%18Yp=~p zM`ut~Wsoy58&kp=m4!sUs@*`+X=g@yE6IlKf6_Egg&>|&2Uo)d7YM|45z_p$*Ebl} zuX9r)fdHZU@uLv8yD|X)h9~y_gWUfO(tkquzi|D3A*U!V^fw!14Vo)HueYPL%kbSsSPcUJrU8yqP1a;2~89n&D(&sV7AgHx+W@*n<|w3I8UFh?hM_S%~rAp}lsoqhd z&R!<6mHlD~;0Tak7aTT7?_BW|j4)Y?TXt*K($OjPG50^+ka>Zj%r?05D~Cqor83>Q zKc;7aJXdSbwKz@~B96WUAmVB0!Df6NArsHoH1oDi9^YY2xjB60bPQzMq+ClLN-8Pz zWUx6UM6-ji^_HZcTQj@pxQ(4fsg9tf?`YV)7x$lh{^|XezXa&iX=~7RPcHMxv&*h| zHb;KL6`dldpRK46y9z&+m#r7O56R2(H+re=A2n&JsiyO7>wg{ra<^in>ArKg06pi4 zSsOIypZ#TLEqV_jy~*r|S2G|N*Jlh3$F~&Z)OR$eY+1>TR*I|DFcUu4cFXNOYx`S# zxvmvc6WBFCB-iwk``zwV9ABTYPqE?o%j&4;I-~2C-gyF~75+DOa@>bKEP5 z%zbAM(fzWLjfL;6+RtyM-S6+g@0tR@_3U?#qz9mCv|O^DnR{?|U8U8gL{&|D$NEV> z>3YD1>!hTF7QUmK_-5*3q2cRW{_C6n^ZPq9gxfm0IidzYhoH%-<@P1B#U$>>M<0tV zWOGS2h|9;vdNqaCG?FpJhtKHIK>l{0h%ImS^$mF2iy>!acUz;YPOOSPbYM0JQD4EV z>~7gy6iP7g?0U|REL!l9+f+|~JxiUtd6@p@|M>dO`S_k5#=a`y&g?*%1i8WiUQ!|X z-QKy#>FEP_z0e*S*Fo)~5q+U!<7nwMGB|A}MSRJ&z54tn20tC?$qA6FoU4pNa7tQ2 zIC4yY-5xhs1zyzJr|~G(Al`^X9gVJ!;p$obm~%ZEx#*H>ylG=R`Ys_}nYNS4Nu|fP z83~qfm-_lMoFI74D(YGJbKw=`_a*dAcNOb{?oA8e?R)e2duNrLTZ`N0JX3TalLfW5 zSb#wNO3!n&26{AA`LKudV1H|1;u zLrvdlZE9)}q*is-w7Mi3&Bj`;mmID*Mm?Bi03=7tHWq&CP2LLhL` zt8RuAfhZJh$snsF9vJRh(g{Kv5RE>sR>B(L{PQma3ZX^ccGa0bZtk{Ux^C#-Cex1o zm`-PMI+==%HBWpjC!|@HMj-*PS88;{(`G<&)3ARrs`#QRJFX=+nT?l-ylqs}oR6WT z;Y-?Ip>Fsq9j%EBC-J7#wOe~qygjAzS{NbAk+kzUtdR}!@1c3wd|opvA;&cy52^%cq>#s9ofOmM zzQMg;Hq110o0oq@#3)ThFVE$yf_bO0!0u$t7aAxyP_t7FjC&1N$bJ?h;vN-#=aRJt zq3j>#T58G_dJpRnfjA62DCaI5C=-a6{-*HR|5?OVBp=Gw_x7QX%OI?jL&7WiJv^=+ z8@y(Am?xhWBU=(gvOo?XnG;Me9O8nl!ImrT zN$`bGoyuc6Z4BEsda_0Y$TSz96z>#9m?!#1zlGnVddK}~-OE>YdhdIbPL!TugyHVj zWm&|s&F&=ow0{R!)C=XD)sBK;n8Etw=oaeD@}Kg*PCv?N3P>~F%c~Q@)I-vIL0lhj zzrvldrU$RN3UBbOi({!CdT_~z8)Wd$b97B@(&S`~EZ1tS0qm*uMq(siq(cgK#$>(_ zFL8?%SIx0`#b)Mf&QQ6Af1dUPyVTp&(O%j<6JVcUeg6gkh%yduCUPbcf+Z|@o;=?D zA4d*J`LpCVEXb`tgt3Z+AF5qB;(0yTi*E#z+ zS3Krx)6XQ(C+WrQu>9$*9cf)pZ9bCkW&7^EjaNQH_dW*G*Q&k7y~cjj9>rW`>K#Qm3IHS3aN4lk0qpP& z=J&$l0JJ(73%oDqp*{YyJ|e)s<0#7-7&SXCSy4U3dJb;Ou}yv& zQ9?9{b+@H%KzYVjh#QrTkZJ57(HO9Qa1WUHgY(WS&5-k_wQ=oM8ZVz5G3xoA_zy#d zc!)c)ULSsJXx9|n?8fa&#VMRC<%X1eJrrApPYi2q)+2W|IjzZxqS8Ae9W%4ms2mbY zuGOK-qwKpLe`Va4$W0Mmyv@BEVvg7;UFE6LBiaX|&+yMPc!Vtyk%FaACiecF8e#zA zQ+wTvt0Dh8f*_qz^c-O^fhjwM)6tc4{FmenW_F19lrV`8d6T5C#Fte&p-vEPu#Ol+ z?G4*}+bW~ofOK>|jTE|3i57yiWnBfg1*zf33&NAFtkoIB{Eoa{*0xX&@H4aO7@twV zDk8A}%iPReSJ1Y;S1tU^=g!F+zYjq1!aQ=@OjmW>vJRi@ry9mHRyG3;)1QbvR1>aB zAKESWm@k;lHGj;?qMiAVM<`|k8*gPvmsvM7=bQWqcWo}I2Z#7){h4IK=$VZ4G1bR< zm}RdYA8!=T!;*tx5oda*F2iXM%$gB*vZo~=uDW1npXz-#4F-#q!i&|pJw|}-HEXn; z;5+31F-BOF_C4}vz1ymD5qH- z)k!=sIzQZW*`P)9SLy@hv~~jC2)p>`{Yc3IUxX4hzN`Vyydd0%)I#m%)N>{2+yN_@ zc-H6g>Y*-gAI}zNlXIqb)X%?ZpFggKp%s$+)UsMwa!LR>LN`xfjzAJe#mj2lxKvKO zOV=`;;8C|e_lUvNV0QYX;J~MY;P?IQLQlH`jMoaF#LFubE&ALMnjd5P?Ea+f&4o z;|!8RlV)DH5qYraV{{q=Z1#h90B(!z1#ZL1atSTOncT&n!(yHY3&g?Mi(0=0Da2j8 z-m=}cKiWXrwF>0kD;MyHM^O(U3`b<*sR0J{#yi02k^gZ+ekF(dl4UHbL&ki0yX@Kz zt%jJeES=$?431#PK7a@^uX!tU%R0|hf96>};3{szKWr83&P*i7nXXmHb9wN4k?L(v zSg4XdEeo@RUPJX)1m;XiXM3nly#WjuNrJJ=GPr*TsS5ko(*WR@CU56SQr!0H*BRGX zD7Boxc}OC;K0lR4Z9SR~*+lSE^LiJpss(0o+fP#{0da6@yJhb6Oc8NfxiVR*-a7nP zx+G`^Fer&D#kgtw6ue;r>{vXUXcW+4IO&#)(hZRk3}4&frenB`daDu5$$9NY%lRqw z-x*JRFL2_fg!%x{LZ)&chgElSM{buAd;!}Z6>PtguzICn1=FkHANjr8mmN z+vA`8p9$$Vso0?cqR-Rf>(_sck$V_mj2NWG=PmPFPot%KBvj+*OmbUKRon1U*#za- ziRP3y+N>T_Z8@r!&z2~1UQVI^&L-J>qJUHIvG_5GEbRzTrut17XaHT5Z)_`I1(i#K z!zmE?9gmMLwqJ9%6LEy*2ATD38Q#M*1W4FBWAx=|pnR?Hk7+@dpnZUG?lK!CWIy>0AW`rlrCBM71ZxUZizBT-zxKSYDSsriYt zGx0_KuzjC>Xb?S^K*snZUq@cCYznahUIJ9+^gV=>?B=uysR^m*CBB6Lo^*bV;xl{4 zg3%3Crm$qIsr<)^gD3xaHk!)H)pIH0zSFah_ar)Pl56wLh*3t zKturW2)qK1r=fU0k9@HLB)=to#Qh-3BS!k4>@1?t4k!W??i`c|IYO3z^LeNo0hiw? zXuguTGw@$wNwk9BzsMyJ`d?H|tW5BiaDRb_z#^~;ES`p95m^2%1h})|ASxyH7x>GcLqZ_H5#R`LI0u6KPnN4cuSOnUf?(JW1Hup|)B%?f zZjb}vKp^rW)ZriDi5i^bh?Xz#sDnT>h@*B04#Xh`!3Yp%wea62PV9k_(oQjUz<`bG zZ&&|!L11q1ON?5h-srPc#;vY*0pJRtyuSpbF`~ zTW8#7U;XFoXE|&ST}|rhC}&Kgz*W{G^Pw`LJmc;uF=tIQ&an)WMJ7cYij-68bpx`TOf-L^&cE56;+u1fTDvH&Vv`% zNSR!$>=oI4?uGdMjT;=~)rp7`*B6}>U5i>yYF~Ix2-yM}Pm4wv^I%{eZspI9ysGCZ zyzBcoB$WfZiJgkp+GYtD3_Q=seSv*P80)S6dkrg2K|MlP0&kSdfi{Cr!&Ji2Ksl%6 z^I^7f*o%}(IxG+6c(cuh!J!+X6^-?_6%n_GU-?ylSQ22F|HMOlF49Qz#V(JAgi)|D zeUw^l$#)iceZ3DxJXWTTB#-inLdQJ8Ji!n>}S~lYs*$HIG@=r^=8Gkz}-n z$y~=hwqhao?6;wQj_`=0l~tbLY8Uc6G~k<&$(_U{6T93-7d@zlur()hErT{GNe>WD zI_^nLA!LuRs6n{UWRy&WyF_9vNH+$nhnQY=mRg0@WgL_)y*?2~Cc8w{HkP*2={Ze_ z6YIDseNiwTcU%LC-7`Bq)L6VSbeyuHuB!ABNQ&oI(eZCm8iLk!aHTsM$)OK4t40f9 z4sh35r3292q8zzfnwp~Tt^4+M3LN2ET4)iU9ObQW49T4t-JRG5DX1_8WgaLf1`*_s zp%^QFiO~Ub918ghP8$}4(xKO_I&-E$wdJh@*i;Gk<&l*25JQeFX(-xAE$^uJlbC%bZs_QTAiA>_*gl%b9K>nwzKkWZZ0m}C)L?< zaI2~y(KS6?Dk=a*IV_)ay4$828~HOp4L3VUS8z4~gz zH~U_hZLIjbzrQz!7t%K;l?qp=ujfs5BBWzPVNlIsiElMByTli+z@|;v5T7x!4CjJw zkO6{`+b|;f{L8^==!o%^!K1W!P@7>tV60kjhM>BU1Kn_Xm^7;RbKa5$hKlw->up-w z`3CN^QF+ebmka~ zZ?d7-{dq(*Lgt0klupKxvf{B~7#w_7+H~uD(?i^nIGGwKPT^@q5)Oz{X9XCe7)GyPbF>wwD080_pX?>%x319QoDoi{!YI^9aXb}gM-IL{}W<2En4Yl_JPWJ@P#aJz<|* z8?LwCbjm#vi~_8Lx5;WI+YbWAGr3kKF`E)EitQaF3m3E43tUZobV#%-X#kk1-n;mf zbWaiRw$eUKz5Z@C+ukQx8SFbl5q8Go0_SG~I9zi&B`Z(FbXeY3UzeAd~@ zGhn2Gc}+XpW;xTm0t*PQhz7B&acmQbjBC)=-D4-!5QDBe-<;jIPQI$4(^P3JGS`dK zHil1A&ghyxq~1)tD@e63yjI$ZAmc;oCNa$1s7P+h=(3E%mFxzQ^lQ+t-gbW|gk(GP zM~bzFG9f5*8imT{A%6emR@1%b@*}Tri6T+X6LcYXD!I+Gjufz136b&|mb$RCS_Pj| z)i1SXIBU)5-3)DX21KE|#ex~3gBQWdc=EGuziScK2wW5jq9D!_qHn55A=~~o6B2Es z)cw_LlKY`6psYu)C9F*^L!Zt>G0C+myM>8Lr)ht`m~1#%dFSPpx#cS@O`YbcVR7!} zN~4!;BZS$nt8b4n95_Bt87~Nqzb0xo=qvEh1D8X zYUxOND(wF2-sdQWDhB&SE9X~so#oNU#K=TLS!a7EGAw;!k4kl7#g~7(Y2wF|SO6ml z_GggCdpCRV;5+J=SjYnA6g@?#2yN@pIxb?0l8#1>eLa9yE*NsIdj0b)t{-^OhEhKa zSoL?ia8aB@q5%wPSblp)S_YV!na~Y|NP#6xDU>o&RYZBiOj$)VUPL!)(&d~$BR|zI z7L2N};sVD-wda*$8HUR)Zz@-Ln_eY#jleCXt}Xw#)j(OePii5=0VX8K2`7(Abg%d! z$66q>#bbbn>A8h29k7Lt@=l~DnOd@LWHf5wPhuJ~nBDd%i zvlg3b(RLhh%a{4u{n3P~&Zg&#ab}IDx#ogDuUip^T@S#-_`fd>!@9Z-nja4X?zNQGPCK+xeJLJ2-Z zq1DRRZ)_mwR9YzaoR-ck7AA`;xX-<|wLyZ3(+|0ydwSK)lN{i|ID0i2qJNN(W8!%J zcyclpH?tEM?4_78=63O`@%Ob-$<(+;?0}AoVnG#S30;qhrny$?Li+cN_XD&^5b3pM zTnnHdo1Vkuj~U;#Zn)lSXx}LsUwlf^QLUMm5E(!ok=ilY%xr@g4q}1W9RA6OM9;v< zH06aZ-MoLEQ!bN}<*{iT$6B2z6T2C99DIT-69>C90~*D_`OtWuj1!lGljs*tjUE^G zGlR+S!VZ=RkBevHMeMz#NYPiWR|DM8VN;y*NaL>%_Ti$A~O*iqIN(St$E8A->YwW2njnk{!l4hkL zsWzK7o35+r$_||v^cc-3T7#xSR!7OZ_EFmv+ZXuo9z8?b&_>EDjgvLBjSb<4@F>6# z>6}DB6Q8o4HhwidBNbY}0?z$<{__6*er;W$dex?7*SL?%HR_QM>dJ|(mV@4P7HUX1 ze2~rOCE85XwimUpa-IrRwn8=*^vY2gn0wW6UCX`t_D`kKhR0+@-EvQu3#refetMqU zs%a@zrLl5q4U=D`!+ki+>Y>H1Inw~bdsqu+F6~<&ODJCxO zX+69nt2}E_{3R0$?!oO|cdE2<-_Yo@?XwcW=m)sG+R8-Z2LpcbGHWL*6P5rzrj+&7 z$^GYjFKaiHZ>zhT@Fa!0C9(-$#uzhuvALfPny_Bw>1)ayD-%OSF8&okp>~6ospDK} znfjlXBnhFM3Q`Pzp997MNQ};j;@7C&3MYq9?v&o2SK%V+W*Z;OXsM))pf`%7V|6uu}K;rjc5h{K~S!%tOG zU9Zk=7a_`$@95>66HC_g!ob|#x0O|M=gn;4*7NI-FkSqKOA<=xryRE0oJIS!7QM-# zV)^#%?_9k7m`ow^)f)-q#G7rX);U)r7nTY^xmVmAOB~1 z8_`8J%y=OBygV?XmObIn60>qmXq)&sBJ|dK8;_E45sWGRmh&0%3A3KQVCnEYbmzzPMm zg_P<=*$6VSjIZRdD`X|+vnw=2$AtQ`2}GvEG`#%+m=JLU#09Ik`c z+dj$PH^ksHCT$!Luvy^ScC$Z?f)9e>VS|4Y8mwAihy($ELh$hPw8I&=oc0ZJzg_ob zvmBV2PBQl1X4WPD2tRcEQ5^_yhv>>rhM*knPj>vcynLuXa4n!u=~UFE^U&tBJ$7(l ze~>aQ7&3nf|DjJllPmrp0aeQwbBJl+$dnRxgk7wCM>qJc(J;A`Y?*J_3{B=2sCk@3 zgtv0PY5D@-dl76&w14S%X^-iUZ-+f}Z4J1_YPo!VtHI)N3Obv`x}{4EaOEu)`r+u!!bub)jzX@!Nxky<)nBIy}yCC4w2b{2LiR2so% zL!AInn5djlEQ7vj5@BGd*M2JgvMTwZtc8c9b`{%d8KPXn0sPu*W?ZmjiU_gEuufG% z_sSI_QlyTul%>MLYe>bOvbGO&!AA|RuCYE|2}$pw`n}Jkn(|w3Q+og-;}@Rhk%67Z zsv)nvG~P~R*$RZJ7(YnI5H?9NWfp=jn;!)L#%#bvFYp1mde{>5da^CGlFqp6E=)!+ z@T%_Qc3xApzB9{d+-;^)do1e&$^fyzQ(vevdyS!iu)!!T zKQy%p!a2~nnGe_NX*DhmjSOKN$5 z%BSqtGY0ZEOywJ91Eq8whPZlMdJRvnsfh+nN<(O!NM+RO1=Cw)igq01az1iGYkQwa z!ep4VD>2<#Yhha+$MFh6cibD*yDA%iMz6QVtXt2zr;NO{N`J~xaq#819R@9(fIipVC@x_4oxc5{kJn|-qsj!#7aFP)Sdo+i+ERm@Wx^Bz+ zKAo&4F1iti5k`y3gEjx&XMX6k&O?VQpp8`z%lyF>I()yk_d1u&)nEmXq9sfeH8KZL z+u6v=I2)a37l_C`7;}r&L$_U2cfnw+Fex%g#oRL)GhY;D2gd5;uIql#+k&M2_uH|U zbTu65r>_eOuiRJjZf||Hy@b^?{D@i$+Rru%{jsG1KGxF@OKFv(*nu?0ONC}1Dx)SJ z&W11RjO>LW(Q2bHjR__|-Y<7T93hx$SfPY!hGQ0Kk_)rB-gOOx0rKhJ^UDUJY!=6F zVL&6+zvyq%^A-`|;JjsSf8P#5=v8U(suU!0@sY)$`Z_Kz&H9qn3>j*Q2H7JXcvn(7 zXI7+KR8ZAm>@g4UMiXF6if7ecoxfgMrt+g8!E~C56tPm6f16|;5jFqmem z=s|xOtd>BbZ(>uxp!qwJIf;*AuJbdPwUo4t>t)3eryeChl>6d0sLv7Pje4`Ae(!x! zvnyz>pWm~6tq%cXttZJ=4;hYgAcWZ4!Jj+7?B>BVFkDIg9&$aa9>!=%YvYE!%FJWf zptUD-xU$RlqSh#mQ2&(!g@ec$`N@cpiZ6Vxley)8N@d9Yk6M|33Sa+wtqcJt2Q$OJ zN@b$FpuClrmR{Rly4XMNO_|bo(J#*3l+_apfjAmR3;&HCs}v2oAM3BA&yS<&SxvY^MpFo_v3Q93O3ZU3Z;q z9c4PraK20%f40NY<_0+^fK5?rqR|Hoo&ZK5kyZ6Y>;ISq?w&VEhfJU&*00cPFD6qU z@soWOVXu57n1Bhe*4XGs*Xw)||M;{^uIWA+7U4936VO}v@9y%c*;U-9V!t&cr~$36glFHq9x$J8zHDjb!1D6885jyFl=H=@JPhyCM- zpa4g7<4WI^J;voA<~K@nFWVmd>oOaI5Lu!yZe@_ySmZ0XdEml6t^z3YE}1!T*4_K7 z&)vs?eBaD&>}|*^!D%0?^c^SOxAwc%A6$$8JDwjgy%vr=&p#+Qq{tP&K$ii11wsiD z`x6H#_TZTI%+rE_Y4(|q3y{y-!1&1IYc$IG^zy?6cPX*sFtWA4ctHb&u`)%Nibme1 ze>hehB4t-Eh-X0{?!x*a?87k%AMQ%_Px`<(?P;q^;J+l^gYx>SgiU1NW2Fo7FtnEY z?RH=6<3UI5&NRmejwHh$yIuj<6h(Io_+}qJyWimN@jbcuv;x%z&tFj(2=#n>TVd%3 zJ_cnSC22JT#21<#OZvHHcS8^EG{C9jcHoU}ZsH(0C14)|Zwp^DU!$mOPGvR&k)Q^Q zN+Ehk#k9+EoMUeiT1G<}{ps>!=HLVI!}!s^D|I6)x`CE^yKZs*gs20?M3^$PU(R*M_ z3;3*u*7$$$4<+IHBj2*|mF$5KA@VZr9gt%h^R=PoY>agsLfzB6ah?tQl8c8QEjfg_ zCw&XfKUYM3n*Es`CjT2yUBJG8KZn17{F+(#ov#N2N2nAE86;FTOHetaVT0~^;3VL# z=Nv&k$Z^jp@2Rj(%uCHn{wc>*!&KT+12lE9!^fG*`?`(r^9sY$zSOLg{)_LL7X3UC z-MiJwbimMC^Vc?f{_s^u4+6;jv~2yDF%t-9Lug~XYdsXF2e}6z@lWK;r`jRd;k@1$ zXJ$j3=Ue8;ybxE=^1c&@P2QC=dsoi$T+1n`AnhqwLoAgsj8YI;o~SzgCJ_&XNYF}< zTM&F;M1RZcplH@9GV@R^24YEg#31ax{7}qLOp(|b>lty9T$7kf3RUrYi>JS5eY+g5 z)>h$m5nrjV@IoYD-2(-#w?;f2-cf9oN*M}bmoJ^Z{S^NU|5pxrACm!@sBF^g0*o#Sx%5*k;U5fN(#C|1 z-5*uJvpxlDVV*n@m%<$kF^6$AfgF&|XqLa?ZV5MmDo%Iwf4tnmxrfpqrwsVS%CilO z+_GGU&&|u0TdPocBvqqym_wg|P<`R3smb_@#9G^vTOi@7WEI#AfO8O@@AB@Ub9^8_ zs`bm(_DlGeyVqA9&gYp>dIS>zZR~hIy|BX)DBPVb6YT z`am87pb9((G z4kd=%9biGvLo-0^rwinTq>>x4Tqd#>1aSD1y5Y4>F^8RGCJ>$pa0EI0?BN7OXLg;ia$Zio^owtV z=!7u)iT=hRjxbGFde)IT{)mg_cbm9p0-4wyP$u&oJ@L+6Z0ZSJr}T&-cSX-DI^+?; zfixyd)UANz;2s~kl#Twk;Q7rHz+$P4K^lei)ENtvK&Ur*2^IA+1ZpOKCZ!kYyLu7x z`kWk7U?+g$w=c8Z8@Ka=UkF!-SqR%7x~>Jm3x*5PmH1LLlqfgZfQtC_j4^NpaogVk z5?Tn?KRElc&%p3eMBY8TW-}W6=>=o+_0-`VBD+UUZ|}|O(ImSk+=J^FFmg)Gd&Wnx zV#K}6&2-Fk+_p+LxAP2M)$QIEgeSQD#xNyHAzUh|9#{(_C>k?6nQg*H?Rw9HGw?bu z?zO9w%?~SYE@1IQxd*K*z%SiTx%=kx1aMgVU~RCG}^oe_t~Ztm$c*wY$3A{S63l(mNQl1 zg9j6$#++ngcSZP6c!Dz?aaIpW7H%Q443}^rvjo>?p+wkRBp}=B!9#VZVcW6!jG@Pr zNfV!WN+8-aciJphmc#!HDv*PoQsQt%2BBkKBYQ1FCz&FKc1%l)FA8r6JtiL`y59m6 z5zHB7>6F6N5rvz#Im?9N#e)RnvaM}??S7~(G#UXNJrq@s>uW-o7UKhDHRyP_uCEFOHkH|L`amPlh5=tW&r$BVmkFaxXKFT{^@ENq69}IckCJy)4-aAK{Jmf}8cNfh%v+%y zmdl#?j=>UV{Tv7%lWlD8BB*kq=V`$mj!YpEKhpzaaj&;rKj-beKlvCMd55L=$}1IC zjGp64fRj?@MLw7G^@3)LN-Pbdc?=e=D6xY1P^|E~d|EXA0+H3U$>M!+=A==@xC15= zN?DwJia7=6x;d1*;N{6IAufz`S^`QdV{d(1pPfir9+ zg5*qBPKcrWghY4$m;|Oe_K-~Sx?9GP9bb|_&Abx|>mbbl>j1Mb?2jHltr8L9Ja?Ia zg^`MMZaJsIlK(7Dm)QZdS))_f&Q4b&z>oE{K&aJYY#xyg zpe>G79$MK|*l4Nq{aUQ3sNO7|sf?<&wLZ_yQb%x3#c0dNqtd_q=Cm@}MYVT0UGoul zO0?303cDgSN@Z|3y7y0_#Wlix}7nONp;IlXWlKP?oHcjXWc>MSlDihirx{!?>~;v{=CSGdLP{ zw^CCh?e(G;UbSkc7b9DO827sdzF9>y>F-wX0?0Cd&B-d?qyc@|tzt=iV=@_q7PETc zqQda*OXNtslExs(-##sNDXE?dP{k(s+H=%Hn5I^%Gb@>MLn_6k0gnqS#L{JoC#u9? zM_AqDVDM=WS(NH^YTgUxx=UDU)EaZxNQ@|v=vU=|v1}({D5+a1e;mesmlSP~to_Ac zDoW>M&Fd=h)`ZG;YQ6XaGx?`9Byyv}ZI#~Xw`$xj_HD%HG^m~j*{F7hBmau~-RT5?Pld@?S=5iLUJYx9Bc&vP+{6}o&k#suLI+Tr4*gtLl zRy!{QgRBR%gMbHY*{AFY1E^A&(9>4ZTT;Q8!{&U(X%BwOmSdJQVL$g{a=>dcWIWlM zMC)dH&K5FasXWN0;c4qB;NKY}%0m8n^jz}JA7v_D_z5nv6&b00Aq$(WfSl7VBfCT< zR=CVa)Rcr+HsS@X>nL^@(b`@743sUNzPvOEyO>*@`xgBoZ;heu1|Vdrjjq|!%(SAM zPRf|x-+6VIU6e~-#|{-Pf;RRq>g6KjpSe~D8Ss-)DWA&DVHc#hlH-1andVJK?h;kN z6?3gIDH`6%zhfAR0YuL9QzO{bm$*_4$}Q7P{mKO!w3`$QC}X87NuPzxlC+Jg>eM)~ zi^Q&Q+Q!z6a}KnLtLEquR@7HkQ#kF&$~2RVJ9SgZrxtYV)M*-vj(WkX?44{ z(H<#e5P6{0{M7$24;iu%A0M6*l3UHZIAk*gPK6MgIw9@R0EV}U3qgg!4i-Td>(^`0 z77ygTLPM7HGVmxcLRu*y!I53zRF^sJw?OEzfmET5|PM_$Y*fqukI@ z7%|y&wplRE z{TnJ8gN7;}z~yBEK>n=t?J;wf=O@g&adUpQ)!Y?Q99#Lp2G_;!{wHUL{A}>`-KysM z>}VxAPt|s$>J2_IH*2%FP{N-oh>v>Y5I!hIX&MGyNtvi#jB*JZ#x`zj;9l|s(`=LS zos}(N%JxahGQS?%MJ%BY#W|mA;Z2V2$0tSbg^hIx;N0jAK#4X?*)_^hG;@ej_X6wL<-d488B^4|vy*BZF}33-m92=uBwleBmmX_ANO@ zG50O!75TAq-k`34Q1@EI#|9Bh0mlZvTM!UEA$jO%XoJRvr9ej-HxUJEFvK|1&^iH6 z?%)&wV3_bDvg#qqpYNN8DPmgIR+%tXqe>Cdu+UihU6iUGF%nbJDq2@TqnRaEvJ6GV zY5gmWXRxxt6H-KiB)$vPjda&{y5+E>p4P%`Rf!fzTF}r)vzfncZ|ivE!dOS|x!eMG zCUh_4S*>Ymvcsk}VB2LO{r*D_-`j6(nyE1huthQC;4j>!GTCU7Y+hnT9RS0m#l&uP zv#^cn&2`k4xdSCuHE`t|Suy4@LFjSnEp^0`@5E+_)fCk(=2MV2w_0wo)O@b_R28gh zq}NJ6k%&)ni7-}rHQYOfBExE;l*g+vjA`0Lw{4LJqJ5`($iAe?)9h^ z;DZvLWLGt5K*A6S8iFV{@S4RL2^t)?%Mgzi(Nm(n5P@!4F9e=-`W&Jp`nk%&&fJw& zjYB4!Q!%M>p?SD|!?mZFro*?QxnhxW>U8D!YbAnWUztVzBUM(}I#QbqI**A{k^&)3 z!C&#ZX$?lbdX!8Wh|maoLA7L##jacvV7#DDPk0~0SDgLxmttyp=ysiFPDX109qEup z?1=7+N%HWKOrgx2j%%?c9lzJYk(phaaWhp))UNu?afM$wo?CZ$|f$?0lt0XI)2DOGm7}+akdJ+{J!C~ty znrMKzQBB_uq(DpVBFRRoHG^!#Q_uE;|{y6&N7C_ z0X4ib+MLwSfVP&d)>$N0=gQ4Z%a#+*Yo7aA|D>8t*L@quD7U5drH7Z?YXK4AD4FaFK^bx`~fgyK%=U+K864_CeRta@w`yjkQ&QRkT~3 zOO#igTY{z1xv1wHWdSEU02?=&%d4Slif*;*cU?tIanqmH>X=x;?|F{Mlf zoV*z~3C;8F7cPj?|7>$;}6#$cE9N3T=8-xva;_Z97kmoe`Je@Hp z()}s2(qMBo-KXT-Y_RX&!Ty~|W6!fD?kz$9&SZJol~-f({WU;63gV_x>7FiXe@AcL z{0PahYZoFALJc1XfNAtT3UzL0XlBN65&6+k8I|fn)uA?Zhx#s%Nd)yPiR*4F}cKrV9O)l4G3{-RKWfeI}kdE7cO^y5*#~8A3BotLKvp z#194W;Vrd)^o)qf*Tq!4n`M^^_IywamBP3p2bBY$O~J`3d!0O_T-dImn6}$U7slr> z%wqOIshMDaykTp1q#7h3PfLR~eg8h6K?CZ_BArWEvp|AVew3rxX5~4a4Y{NgO28*g zz5he$<32(F-d8Iy5eJ`)cTSdkCsfe{Lq(G^t33R&g5Efao59k44t1Q z65`JkEnw02Rp|wd2RjA(`wi}!Rs9^>L`RFKCf5b}pP)rkU&NqbANNqCTcV-GQ`FqP zrS*DGLc&GP?!^rX?4>ALeNA4!tRi2|?({wvGA(dr0-2$JM7zjDD68B2G*Om1oX?rb znTj&#w-b<%A;ir3m=^zewpO$8GayQ)II{VS!`jl(wzXs~c1<9&Kj?viIt4q&+MkP! z!$tt$Hb-75Z~|q<>J-nF@g+U`h&r`~RM@|PL`-XC>l(^vvjDKW^tok!>caRC=Uz$1s)Myk5Z&r?{EU22M>*9U;5 z(p0fz{>INfD{Ew<80rdaM{=Ao!L%asLsPFBVVs4cehoaL?F&LQjf2`r0nT2@wJhzk zEueNEqx7??srW@IkB4QbNEDpA1XcgYY13k1x+3b9#2^_ zC^Ev`rRG~=y157Es}3(d3iud*YKB+FC3J()KHk1-|C1s&DYTZG=Wz6$s|89ah%>8& zNwAzGSCz4P?c2hB-Y=xA`N|#S_a1Y4*8Au0Z(>c@xtYYZpDxceDdD?t1EPw8fO9c+qZ8?~6Pcm(^ z9O1Cn+*M^d`B_`7HYWlyJrzm&+R((~IyOwC9x{ClHN9JI(l@k)smMbB?xJ@4QGP7= z_@$piO5gN=i^#)RlOtmiM8RiUlh>n^-_+D^Y^-c**3MI1Io!>(H1Y4Swg~GhnSNx; zVolZtBH!x5UtkX>kB*UjP`Og{MYMd#?pRLXwC$X#*F1}7=Gv6KIfP166Dr;MmIZfsk{}e$XFh%$d%Q-Ia|q* zr!4o@eTM*Df0RaF!k(s1u9nYGgrgun7c<~C!KD_oU3bdyT{en|R_7Tej%Dp-bck~^ z6pH^2YH9^)83j%IG8?YF(8lQ{-GdCvLDGtLun42s^;yqjnl@oCF057 zj0SbHrL~rK=ujr0DYpi2TAQ)#5WkA$5)Ehi;%a`rfjY)X>UKcP*z~nwdkpJ~>+;mC znafGu!%H%O`y-g4pm^ZCIwbJd79}lsjI*>vd3NUdx^}hG{or>xlxQ*NxQ@`e-=#l} zP0)_1hJVHZ;&=r(6fOqB?cqLOUNuNKXEkZJGwFNElg%Ia?^Q+^6b0)tEUQT|l*?u- zQ6xz%(;X8PT6B5K>->)1mBT176^mrhBySqq*hZ-xylNQ4=`DzLQ&xG07x&^!2szP} zh+;EGNZBLR&+@gnJdRyPqTVtyuNhBo+vOW2^T3J^EHDx+VQ z`KcDq3{2RHk{N~rp83T1#~RT^Ti=)EUf&jC1vPw~`P#r@?|i=!s2W+=%e%s}GM(4@ zb8^q_qQOCzRCm~syi)A$zH8y9XXoL)^0odF?1J~cWt9^5Oee{JIc;zjQTieOHt{~! zUZ(D+#rM;6pq;0Ez6dRu&09;sD!agO&htQ4a$Xtnp%C5L&yYX(G<42D>ADZa2R+-z z?BxY%p{4Urf_veSVwy+gGe}U#5xlnl(!)Ye#8hD%@E( z$l?waSONz$ItR6xbV&n>SJoBwPSi|}?q=?DF`rk2{XAv}K#Vb*Bjs~?wmPvn={&9% zO22=^6c=WBhOtQn>Cnd=qz+d)>DU86Er+lK>523(Cr^Qj84!PXZ74C?2; zV{H{G)vs;U$8fd}rPX#7(Gn@53Sm?GxE_RnxVv~bCagO)ezWe(?fvFv%`X8jS13CB zm!WSUbANIbfoY$R)}JcU3Hz9oJh|Mt0s~_O6Gk>w{FpX@{XFe|r$myezI1#p-3rPy zq^@U6s+agknm_m%Pk!(Fhg(xGyVD~%;+$M-rkr|qE^D$%ip)idqm+eo9_`AU$M=mjKBK4NI0@?5sq1@vuo{~ z##V{3Nr{=TN`-8BzN5nH9~#D#(8a?0yFWO=TUV?yVD&7~)K|N0uM|60GSAcZ*5EaI zk>A^Lm8VnZPnS9MAs|_=@XIi}L16uTmUKHuFRi=eC|4D`*vw|&LO;~e{MmRniBtYE z*{>xhOq_G}=k3tbp&>tvA4Mde@>eJ~)>nKuA-n4<=6^ldd z@O#9@i36_(XVjpf=>GKn<3~XMf~oRls@UnwnoNW3bV-{6!2L&{eh9K2*&ptw)BmSh z+b8DDGY<{clnVOt(U-q9bGfFoPO>+wHM1+|LqolJk9e0=GZ`&AT89EZw!f`kZ29h= ze8&jsI&tn{8DN=5V|j4D9r;ZYk7*ppGXKeuj43sP$Udx9txZ!T^|Bc7RumX4XVjGA zxtHPZIh`niJX1fI>SDRJ2`DvDLG||yl%Mh_9p=1j)t{(Dk46uZ|s_4Z=G|Y z%n>LDy0p4#@*lN{_fU^zEO1th`DyQ4N z2?3|uP_Xyuh;d_koac*dGT%d{t!}zsC&KrOLwa$A{LBig^Gfr1m%GeW0*j238CHz1 z@}6ik&xE0J*1}B$w(C{TC8;4hPsWS0`HBf`iqfX1=)KmSlq~=!N>2`YvCCu^;=D~v~jyomM+UL&(4kC7*^?w=IT|YsCJfswG_{K z3%&>6em&cCABY=@w2G%pCBlA+l0h11>YF*g=_P#j&Put~z^Z=8Zt}3yFiZB@0o}*Rw9>RMcYkjHJPP zEoO@3&1#H4g#&CT_ayp4?UZMJY$#awdrt;z-TegzVk2y=)-dl`ccYYm6uz6vf>|;> z@LPU83&@JEv0Pq(oFF4wo2Rer?+7`HpeC0MsEUv@#ajkog^*AFlgU}1(@mkD z?R>n`n3=r{fN~jh@RWjPqnlcaPOwYP&OM16H1~SvjX#9uchQ0MhWcR8STTHUeA9r^fiuJ_4FKp zesX~&nK?BNeUg3ApZI2ETOiKOJ#2QgbyWYm4Pgs05kPxjdN7bNgZ5sPK>r5_uPseY z7Ie#p>(AEqf}svn@x|^qN%@_)pCoJO)`zY`WhpH57q^E8nSDdA-oClVCmFujs;e-+ z;iin0Er7vPG68ZU5~i^gp^vU@nvOhVia*8Pn z50YaXk(XQQ*tc(u#}t3gjkZ^$*yVb+azx5$vmQLfQMH(%JDuyT)(KOr(Qu83qPf*} zD>fn4cGO_=!8+4Z*xs>^YB*s98;}MY0aoVaR{DPA^j;uBN@9 zKYMn|4JS|5jt#4-tQ1+RK6W@ZAdiM{KL4{VHBWObdfi^tRIy{;7ukoix*4f^;v{AA zg_?(%Pq)g9Xbj!^bVc*xfpEOg>G{q-{Jr{3&z=D2mJKrWXn zL@y^|KmV@58Zx^dHGQ=c8{p2u98U)m3mbVaHi(?19sj}vHqyP)y;x{{~vf( zIM*>9?+!*l>JMU9(gv>41yfx0gUi+MyWU)?UI>oxmV~X^)-bPk-o=aA$>BYI<-MhF zN}Hp`m(Rr~#lfLM2061K=Z9L3ZCV$kL`RfgEwuoX)}=k-9hE}tSc!$m9Gb?3YrZMr zuuRaL64)P~>6Hvd^u^3~tDT|?H^Yu1wuufZzRvD_Tq#t>D12!~VC)dV zkY4nWaUt?Jzc(d{q5*`$n}LZC$X1|0@ev54XfTl85ay)d>sA9}o-ip<|%T4$J#=Q%yg`7=Ky%_>AuAwyd@Jf_zq(-Beyu?4ztaSY!n$58e>-)eb$o-`D27 zL7-9#)1geE*fZ&Ia?4yMK9Y^cHPr8s*y@yap?k|)iR~3n$ugrh4ojr`%IH=S*I>q! z3H#yaD&QTqFt6gf6uk1$wg`QjKRhx=7~m!@_l!iS&@=gx1Ygb3iOZ;tk(@#kok~fD zwJ9gc>3{M_W1kAnHqe1Odi4?`RTfu8ax6TjFt5^&$|h!!-+3a}pF)eI&JmTNKO_bz zX4f)k24^ZU=iy#(ThLI&4l5SpW>c^iZu<~i(5jHEP2n+l23^ZI0HC=mlEHl zxEA@&&FI%AYB2k9?6#=fA~6Jp%q3{0%UOp8z0=`EMW#e1Crx#s7xmNnLi1as zRE_Fs=o7a+!h#KSjo|LGEOy*e0Jnjpzs$Oo-9h*>&*;%hmG6dcc;k-b^I|{1GDeiH zkwVR$<|rMxPuAz;cK9u5|4&%?M!`p!;N^e4H>DA_zW~QWrVL9!pC$EWa6VUqcYpO zOr`$d)0`Ib5R{=_qj^_m--@cb`O?>*f}a#aNzB)v|NS_pW|3uVC6pbZF=578z?pbE zqGB#F96Km@U{&tB!RKS*vJCWqeHOA{6bz){#cSxeDPwPH)+`_KQOLB)dVP60C%Gq_ zVjW?8Lgbl7?e%5!A~-PbF>#5E&3x&7Me8h-oM}(X))8TkNl1tssP00vn^-%ZydKqW zf$5FTJe7EEv-_I!f-73zB}_^xvUYvgDJA2$4h51g5aot&3`%OZxCmd$kJ)p8o`lqGvP~jg;mhi}G;u z>Racxf1q2&%6%m-d2|`G&u$-uyp^R=OFl+Uy$C`~GD)k@qe_3|S=kD;eb4<|!N$>X zN|pYWzkDiuh!&}MTE@oEmkSF`8q*o1PoIZNU5^zFmw%EK3*j77p+~|&67YlD3|n{0 zI-F(6Cn8n}gn0Gt=g^q6$42>Q{6Wd_{(4D5$B{0#0Oz;dw?RiGL$%W!q@=CK z?a*L&8Lt*y2n%(YK(*5@MEX=5bPS$2wbW(O~R8W%!OlS#_nQmtf4SA)1`3KmgWOYpoj3<)~Mw}^p(B%Q?# z@59S$q&ETtAKf&s!QRA=KxuMn(J6BVCEM)Ct5og`C1O1XJ$}Cqfmw&ReXH$AWKy5d z8>&ygMnh7@KHktq{@opNl%~I;_lOze%W8s@U8|KJ667f4 zy8M%ov103KD}>udI2p|H!>~p=esN?PgAnohRKz+**$c6W9QiPqwg$QeY-6c1Y?Crn z8R0=|kpr&N?r-O<>6U5o{pE3p2!m^z(+8Xo3$;EFOqN6B{})$MW_2S4UKE*wBUXuz zS*q#kM@xQvHj7K$n{Nd1z^V&aylSMxT;}fPz=rlYGiupUM4)AhC)g{+LVz8i(Gz~$ z#-VJWzXf6X(PN48@%ZtFiUxOH{IP#@NlOJ!~2hJ61 zytpiLxXqO`!je`zs8u5_1Q~r&2+MZM)FkM)$j2*n73UG^3closq0G;^hPh6+$&!i?H3EJ zQ+MPb0Hh)nY%GUh)_8$r?S`Wg2n|oVfLKj?7pi*g!YQbE=07V{b#7(M8VbIzwnJRw zAjSD@`BQXjB8y!OkG*L_WY^&x*s_ET)gF8HC)j|_qep0^AJ&TIWX(@qEMOo`d_hjS zcFJ~jaMN7oKgVwZEQ-rRKV;XXMeU!3esi1t)pGf{dH}^-egYs&@zzNL9cCVNPU^oh zeT2;ivGOLz0i66qB^oTRfui4y1B|MTnkptovjr3$N!@43b>4&<zXw43OIo}`5epe1ZHjh*k2qih%>8GZNNET>>8_o ziM=N>hWyZNmlkqalu~`SoTR+AD1;@`1@i#N8OU}x(Ho5wS71^TCr?}Cko}R&6xG)(_ zoY?@d<9c;Kk2%$!<#Zmmw}--!I_I0Fwdydsq4H zuS@ss&^4cxX~5}(t&yR+U)Ltu-5uCiW`!!?WXb%=?|s^vBt?9hbKu6|6 zmHObwjo_=U0P1Sjo36WSGU-lSYrnuG%Dsy}m29uYs|p%d`W2!K0NgH^esQ#8X{Ys|Ocp6AvBp?D-G3j;rSv z^AEh$0GE&QVOQz_;yKLUh9$i79Y}SH%D*=0LzO>K-KMTTAU(k+dHUbQo4wtwZ7>jJ z{DmdHJbrsQ= 0; i--) { + if (glyphIndices[i] === undefined) { + glyphIndices[i] = lastValue; + } else { + lastValue = glyphIndices[i]; + } + } + + this._glyphIndices = glyphIndices; + return glyphIndices; + } + + get stringStart() { + return Math.min(...this.stringIndices); + } + + get stringEnd() { + return Math.max(...this.stringIndices); } get advanceWidth() { @@ -60,18 +88,32 @@ class GlyphRun extends Run { slice(start, end) { const glyphStart = this.glyphIndices[start]; - const glyphEnd = this.glyphIndices[end] || this.end; + let glyphEnd = this.glyphIndices[end]; + + if (glyphEnd === undefined) { + glyphEnd = this.glyphIndices[this.glyphIndices.length - 1] + 1; + } + + let glyphs; + let positions; + let stringIndices; + + if (glyphEnd === 0) { + glyphs = [this.glyphs[glyphStart]]; + positions = [this.positions[glyphStart]]; + stringIndices = [this.stringIndices[glyphStart]]; + } else { + glyphs = this.glyphs.slice(glyphStart, glyphEnd); + positions = this.positions.slice(glyphStart, glyphEnd); + stringIndices = this.stringIndices.slice(glyphStart, glyphEnd); + } + + stringIndices = stringIndices.map(index => index - this.stringIndices[glyphStart]); start += this.start; end += this.start; end = Math.min(end, this.end); - const glyphs = this.glyphs.slice(glyphStart - this.start, glyphEnd - this.start); - const positions = this.positions.slice(glyphStart - this.start, glyphEnd - this.start); - const stringIndices = this.stringIndices - .slice(glyphStart - this.start, glyphEnd - this.start) - .map(index => index - this.stringIndices[glyphStart - this.start]); - return new GlyphRun(start, end, this.attributes, glyphs, positions, stringIndices, true); } diff --git a/src/models/GlyphString.js b/src/models/GlyphString.js index a535cdf..da7d4a9 100644 --- a/src/models/GlyphString.js +++ b/src/models/GlyphString.js @@ -93,20 +93,19 @@ class GlyphString { } runIndexAtGlyphIndex(index) { - const idx = index + this.start; let count = 0; - for (let i = 0; i < this._glyphRuns.length; i++) { - const run = this._glyphRuns[i]; + for (let i = 0; i < this.glyphRuns.length; i++) { + const run = this.glyphRuns[i]; - if (count <= idx && idx < count + run.glyphs.length) { + if (count <= index && index < count + run.glyphs.length) { return i; } count += run.glyphs.length; } - return this._glyphRuns.length - 1; + return this.glyphRuns.length - 1; } runAtGlyphIndex(index) { @@ -130,13 +129,41 @@ class GlyphString { } glyphAtIndex(index) { - const run = this.runAtGlyphIndex(index); - return run.glyphs[this.start + index - run.start]; + let run; + let count = 0; + + for (let i = 0; i < this.glyphRuns.length; i++) { + run = this.glyphRuns[i]; + + if (count <= index && index < count + run.glyphs.length) { + return run.glyphs[index - count]; + } + + count += run.glyphs.length; + } + + return run.glyphs[run.glyphs.length - 1]; + } + + positionAtIndex(index) { + let run; + let count = 0; + + for (let i = 0; i < this.glyphRuns.length; i++) { + run = this.glyphRuns[i]; + + if (count <= index && index < count + run.positions.length) { + return run.positions[index - count]; + } + + count += run.positions.length; + } + + return run.positions[run.positions.length - 1]; } getGlyphWidth(index) { - const run = this.runAtGlyphIndex(index); - return run.positions[this.start + index - run.start].xAdvance; + return this.positionAtIndex(index).xAdvance; } glyphIndexAtOffset(width) { @@ -163,19 +190,42 @@ class GlyphString { return index; } - stringIndexForGlyphIndex(glyphIndex) { - const run = this.runAtGlyphIndex(glyphIndex); + stringIndexForGlyphIndex(index) { + let run; + let count = 0; + let offset = 0; - if (glyphIndex >= run.end) { - return -1; + for (let i = 0; i < this.glyphRuns.length; i++) { + run = this.glyphRuns[i]; + + if (count <= index && index < count + run.glyphs.length) { + return offset + run.stringIndices[index - count]; + } + + offset += run.stringEnd + 1; + count += run.glyphs.length; } - return run.start - this.start + run.stringIndices[this.start + glyphIndex - run.start]; + return run.stringIndices[run.stringIndices.length - 1]; } - glyphIndexForStringIndex(stringIndex) { - const run = this.runAtStringIndex(stringIndex); - return run.start - this.start + run.glyphIndices[this.start + stringIndex - run.start]; + glyphIndexForStringIndex(index) { + let run; + let count = 0; + let offset = 0; + + for (let i = 0; i < this.glyphRuns.length; i++) { + run = this.glyphRuns[i]; + + if (count <= index && index < count + run.length) { + return offset + run.glyphIndices[index - count]; + } + + count += run.length; + offset += run.glyphs.length; + } + + return run.glyphIndices[run.glyphIndices.length - 1]; } codePointAtGlyphIndex(glyphIndex) { diff --git a/test/models/GlyphString.test.js b/test/models/GlyphString.test.js index d63c21c..0d76c90 100644 --- a/test/models/GlyphString.test.js +++ b/test/models/GlyphString.test.js @@ -51,7 +51,7 @@ describe('GlyphString', () => { expect(string.descent).toBeCloseTo(-3.5, 1); }); - test('should get glyphs run', () => { + test('should get glyph runs', () => { const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] @@ -64,21 +64,6 @@ describe('GlyphString', () => { expect(string.glyphRuns[1].end).toBe(11); }); - test('should get glyphs run for sliced string', () => { - const string = createLatinString({ - value: 'Lorem ipsum', - runs: [[0, 6], [6, 11]] - }); - - const sliced = string.slice(2, 8); - - expect(sliced.glyphRuns).toHaveLength(2); - expect(sliced.glyphRuns[0].start).toBe(2); - expect(sliced.glyphRuns[0].end).toBe(6); - expect(sliced.glyphRuns[1].start).toBe(6); - expect(sliced.glyphRuns[1].end).toBe(8); - }); - test('should get glyphs run (non latin)', () => { const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', @@ -92,10 +77,10 @@ describe('GlyphString', () => { expect(string.glyphRuns[1].end).toBe(21); }); - test('should get glyphs run for sliced string (non latin)', () => { - const string = createKhmerString({ - value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', - runs: [[0, 8], [8, 21]] + test('should get glyph runs for sliced string', () => { + const string = createLatinString({ + value: 'Lorem ipsum', + runs: [[0, 6], [6, 11]] }); const sliced = string.slice(2, 8); @@ -107,6 +92,21 @@ describe('GlyphString', () => { expect(sliced.glyphRuns[1].end).toBe(8); }); + test('should get glyphs run for sliced string (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(1, 15); + + expect(sliced.glyphRuns).toHaveLength(2); + expect(sliced.glyphRuns[0].start).toBe(1); + expect(sliced.glyphRuns[0].end).toBe(8); + expect(sliced.glyphRuns[1].start).toBe(8); + expect(sliced.glyphRuns[1].end).toBe(15); + }); + test('should isWhiteSpace return true if white space', () => { const string = createLatinString({ value: 'Lorem ipsum' }); @@ -126,7 +126,7 @@ describe('GlyphString', () => { expect(sliced.string).toBe('rem '); expect(sliced.glyphRuns[0].start).toBe(2); expect(sliced.glyphRuns[0].end).toBe(6); - expect(sliced.glyphRuns[0].glyphs).toHaveLength(4); + expect(sliced.glyphRuns[0].glyphs.length).toBe(4); }); test('should slice exceeding range', () => { @@ -136,7 +136,7 @@ describe('GlyphString', () => { expect(sliced.string).toBe('rem ipsum'); expect(sliced.glyphRuns[0].start).toBe(2); expect(sliced.glyphRuns[0].end).toBe(11); - expect(sliced.glyphRuns[0].glyphs).toHaveLength(9); + expect(sliced.glyphRuns[0].glyphs.length).toBe(9); }); test('should ignore unnecesary trailing runs when slice', () => { @@ -175,20 +175,6 @@ describe('GlyphString', () => { expect(string.runIndexAtGlyphIndex(9)).toBe(1); }); - test('should return correct run index at glyph index for sliced strings', () => { - const string = createLatinString({ - value: 'Lorem ipsum', - runs: [[0, 6], [6, 11]] - }); - - const sliced = string.slice(4, 11); - - expect(sliced.runIndexAtGlyphIndex(0)).toBe(0); - expect(sliced.runIndexAtGlyphIndex(1)).toBe(0); - expect(sliced.runIndexAtGlyphIndex(2)).toBe(1); - expect(sliced.runIndexAtGlyphIndex(9)).toBe(1); - }); - test('should return correct run index at glyph index (non latin)', () => { const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', @@ -203,7 +189,21 @@ describe('GlyphString', () => { expect(string.runIndexAtGlyphIndex(15)).toBe(1); }); - test.skip('should return correct run index at glyph index for sliced strings (non latin)', () => { + test('should return correct run index at glyph index for sliced strings', () => { + const string = createLatinString({ + value: 'Lorem ipsum', + runs: [[0, 6], [6, 11]] + }); + + const sliced = string.slice(4, 11); + + expect(sliced.runIndexAtGlyphIndex(0)).toBe(0); + expect(sliced.runIndexAtGlyphIndex(1)).toBe(0); + expect(sliced.runIndexAtGlyphIndex(2)).toBe(1); + expect(sliced.runIndexAtGlyphIndex(9)).toBe(1); + }); + + test('should return correct run index at glyph index for sliced strings (non latin)', () => { const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', runs: [[0, 8], [8, 21]] @@ -211,14 +211,10 @@ describe('GlyphString', () => { const sliced = string.slice(4, 11); - console.log(sliced); - expect(sliced.runIndexAtGlyphIndex(0)).toBe(0); - expect(sliced.runIndexAtGlyphIndex(4)).toBe(0); - expect(sliced.runIndexAtGlyphIndex(6)).toBe(0); - expect(sliced.runIndexAtGlyphIndex(7)).toBe(1); - expect(sliced.runIndexAtGlyphIndex(11)).toBe(1); - expect(sliced.runIndexAtGlyphIndex(15)).toBe(1); + expect(sliced.runIndexAtGlyphIndex(3)).toBe(0); + expect(sliced.runIndexAtGlyphIndex(4)).toBe(1); + expect(sliced.runIndexAtGlyphIndex(6)).toBe(1); }); test('should return correct run at glyph index', () => { @@ -255,20 +251,6 @@ describe('GlyphString', () => { expect(string.runIndexAtStringIndex(9)).toBe(1); }); - test('should return correct run index at string index for sliced strings', () => { - const string = createLatinString({ - value: 'Lorem ipsum', - runs: [[0, 6], [6, 11]] - }); - - const sliced = string.slice(4, 11); - - expect(sliced.runIndexAtStringIndex(0)).toBe(0); - expect(sliced.runIndexAtStringIndex(1)).toBe(0); - expect(sliced.runIndexAtStringIndex(2)).toBe(1); - expect(sliced.runIndexAtStringIndex(5)).toBe(1); - }); - test('should return correct run index at string index (non latin)', () => { const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', @@ -283,6 +265,20 @@ describe('GlyphString', () => { expect(string.runIndexAtStringIndex(20)).toBe(1); }); + test('should return correct run index at string index for sliced strings', () => { + const string = createLatinString({ + value: 'Lorem ipsum', + runs: [[0, 6], [6, 11]] + }); + + const sliced = string.slice(4, 11); + + expect(sliced.runIndexAtStringIndex(0)).toBe(0); + expect(sliced.runIndexAtStringIndex(1)).toBe(0); + expect(sliced.runIndexAtStringIndex(2)).toBe(1); + expect(sliced.runIndexAtStringIndex(5)).toBe(1); + }); + test('should return correct run index at string index for sliced strings (non latin)', () => { const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', @@ -309,6 +305,18 @@ describe('GlyphString', () => { expect(string.runAtStringIndex(9).start).toBe(6); }); + test('should return correct run at string index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.runAtStringIndex(2).start).toBe(0); + expect(string.runAtStringIndex(7).start).toBe(0); + expect(string.runAtStringIndex(8).start).toBe(8); + expect(string.runAtStringIndex(20).start).toBe(8); + }); + test('should return correct run at string index for sliced strings', () => { const string = createLatinString({ value: 'Lorem ipsum', @@ -323,16 +331,6 @@ describe('GlyphString', () => { expect(sliced.runAtStringIndex(5).start).toBe(6); }); - test('should return correct run at string index (non latin)', () => { - const string = createKhmerString({ - value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', - runs: [[0, 8], [8, 21]] - }); - - expect(string.runAtStringIndex(2).start).toBe(0); - expect(string.runAtStringIndex(9).start).toBe(8); - }); - test('should return correct run at string index for sliced strings (non latin)', () => { const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', @@ -343,6 +341,7 @@ describe('GlyphString', () => { expect(sliced.runAtStringIndex(0).start).toBe(0); expect(sliced.runAtStringIndex(1).start).toBe(0); + expect(sliced.runAtStringIndex(3).start).toBe(0); expect(sliced.runAtStringIndex(4).start).toBe(8); expect(sliced.runAtStringIndex(6).start).toBe(8); }); @@ -360,6 +359,21 @@ describe('GlyphString', () => { expect(string.glyphAtIndex(9).id).toBe(secondRunGlyphs[3].id); }); + test('should return correct glyph at index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const firstRunGlyphs = string._glyphRuns[0].glyphs; + const secondRunGlyphs = string._glyphRuns[1].glyphs; + + expect(string.glyphAtIndex(2).id).toBe(firstRunGlyphs[2].id); + expect(string.glyphAtIndex(6).id).toBe(firstRunGlyphs[6].id); + expect(string.glyphAtIndex(7).id).toBe(secondRunGlyphs[0].id); + expect(string.glyphAtIndex(15).id).toBe(secondRunGlyphs[8].id); + }); + test('should return correct glyph at index for sliced strings', () => { const string = createLatinString({ value: 'Lorem ipsum', @@ -376,22 +390,7 @@ describe('GlyphString', () => { expect(sliced.glyphAtIndex(5).id).toBe(secondRunGlyphs[3].id); }); - test.skip('should return correct glyph at index (non latin)', () => { - const string = createKhmerString({ - value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', - runs: [[0, 8], [8, 21]] - }); - - const firstRunGlyphs = string._glyphRuns[0].glyphs; - const secondRunGlyphs = string._glyphRuns[1].glyphs; - - expect(string.glyphAtIndex(2).id).toBe(firstRunGlyphs[2].id); - expect(string.glyphAtIndex(6).id).toBe(firstRunGlyphs[6].id); - expect(string.glyphAtIndex(7).id).toBe(secondRunGlyphs[0].id); - expect(string.glyphAtIndex(15).id).toBe(secondRunGlyphs[8].id); - }); - - test.skip('should return correct glyph at index for sliced strings (non latin)', () => { + test('should return correct glyph at index for sliced strings (non latin)', () => { const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', runs: [[0, 8], [8, 21]] @@ -401,10 +400,9 @@ describe('GlyphString', () => { const firstRunGlyphs = sliced._glyphRuns[0].glyphs; const secondRunGlyphs = sliced._glyphRuns[1].glyphs; - const runs = sliced.glyphRuns; - // expect(sliced.glyphAtIndex(0).id).toBe(firstRunGlyphs[3].id); - // expect(sliced.glyphAtIndex(3).id).toBe(firstRunGlyphs[6].id); - // expect(sliced.glyphAtIndex(4).id).toBe(secondRunGlyphs[0].id); + expect(sliced.glyphAtIndex(0).id).toBe(firstRunGlyphs[3].id); + expect(sliced.glyphAtIndex(3).id).toBe(firstRunGlyphs[6].id); + expect(sliced.glyphAtIndex(4).id).toBe(secondRunGlyphs[0].id); }); test('should return correct glyph width at index', () => { @@ -420,6 +418,21 @@ describe('GlyphString', () => { expect(string.getGlyphWidth(9)).toBe(secondRunPositions[3].xAdvance); }); + test('should return correct glyph width at index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const firstRunPositions = string._glyphRuns[0].positions; + const secondRunPositions = string._glyphRuns[1].positions; + + expect(string.getGlyphWidth(0)).toBe(firstRunPositions[0].xAdvance); + expect(string.getGlyphWidth(6)).toBe(firstRunPositions[6].xAdvance); + expect(string.getGlyphWidth(7)).toBe(secondRunPositions[0].xAdvance); + expect(string.getGlyphWidth(15)).toBe(secondRunPositions[8].xAdvance); + }); + test('should return correct glyph width at index for sliced strings', () => { const string = createLatinString({ value: 'Lorem ipsum', @@ -436,6 +449,21 @@ describe('GlyphString', () => { expect(sliced.getGlyphWidth(5)).toBe(secondRunPositions[3].xAdvance); }); + test('should return correct glyph width at index for sliced strings (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 11); + const firstRunPositions = sliced._glyphRuns[0].positions; + const secondRunPositions = sliced._glyphRuns[1].positions; + + expect(sliced.getGlyphWidth(0)).toBe(firstRunPositions[3].xAdvance); + expect(sliced.getGlyphWidth(1)).toBe(firstRunPositions[6].xAdvance); + expect(sliced.getGlyphWidth(4)).toBe(secondRunPositions[0].xAdvance); + }); + test('should return correct glyph index at offset', () => { // l o r e m i p s u m // 6.22 7.24 4.65 6.73 11.16 3.11 3.03 7.35 5.72 7.36 11.16 @@ -450,6 +478,20 @@ describe('GlyphString', () => { expect(string.glyphIndexAtOffset(50)).toBe(8); }); + test('should return correct glyph index at offset (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.glyphIndexAtOffset(8)).toBe(0); + expect(string.glyphIndexAtOffset(10)).toBe(4); + expect(string.glyphIndexAtOffset(24)).toBe(6); + expect(string.glyphIndexAtOffset(32)).toBe(7); + expect(string.glyphIndexAtOffset(51)).toBe(8); + expect(string.glyphIndexAtOffset(92)).toBe(14); + }); + test('should return correct glyph index at offset for sliced strings', () => { const string = createLatinString({ value: 'Lorem ipsum', @@ -468,6 +510,20 @@ describe('GlyphString', () => { expect(sliced.glyphIndexAtOffset(39)).toBe(6); }); + test('should return correct glyph index at offset for sliced strings (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 11); + + expect(sliced.glyphIndexAtOffset(0)).toBe(1); + expect(sliced.glyphIndexAtOffset(9)).toBe(1); + expect(sliced.glyphIndexAtOffset(14)).toBe(3); + expect(sliced.glyphIndexAtOffset(24)).toBe(4); + }); + test('should return correct string index for glyph index', () => { const string = createLatinString({ value: 'Lorem ipsum', @@ -480,6 +536,21 @@ describe('GlyphString', () => { expect(string.stringIndexForGlyphIndex(10)).toBe(10); }); + test('should return correct string index for glyph index (non latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.stringIndexForGlyphIndex(0)).toBe(0); + expect(string.stringIndexForGlyphIndex(2)).toBe(3); + expect(string.stringIndexForGlyphIndex(6)).toBe(7); + expect(string.stringIndexForGlyphIndex(7)).toBe(8); + expect(string.stringIndexForGlyphIndex(8)).toBe(12); + expect(string.stringIndexForGlyphIndex(11)).toBe(16); + expect(string.stringIndexForGlyphIndex(15)).toBe(20); + }); + test('should return correct string index for glyph index for sliced strings', () => { const string = createLatinString({ value: 'Lorem ipsum', @@ -493,6 +564,20 @@ describe('GlyphString', () => { expect(sliced.stringIndexForGlyphIndex(6)).toBe(6); }); + test('should return correct string index for glyph index for sliced strings (not latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 15); + + expect(sliced.stringIndexForGlyphIndex(0)).toBe(0); + expect(sliced.stringIndexForGlyphIndex(3)).toBe(3); + expect(sliced.stringIndexForGlyphIndex(4)).toBe(4); + expect(sliced.stringIndexForGlyphIndex(5)).toBe(8); + }); + test('should return correct glyph index for string index', () => { const string = createLatinString({ value: 'Lorem ipsum', @@ -505,17 +590,44 @@ describe('GlyphString', () => { expect(string.glyphIndexForStringIndex(10)).toBe(10); }); + test('should return correct glyph index for string index (not latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + expect(string.glyphIndexForStringIndex(0)).toBe(0); + expect(string.glyphIndexForStringIndex(4)).toBe(3); + expect(string.glyphIndexForStringIndex(7)).toBe(6); + expect(string.glyphIndexForStringIndex(8)).toBe(7); + expect(string.glyphIndexForStringIndex(12)).toBe(8); + }); + test('should return correct glyph index for string index for sliced strings', () => { const string = createLatinString({ value: 'Lorem ipsum', runs: [[0, 6], [6, 11]] }); - const sliced = string.slice(4, 11); + const sliced = string.slice(2, 6); expect(sliced.glyphIndexForStringIndex(0)).toBe(0); + expect(sliced.glyphIndexForStringIndex(1)).toBe(1); expect(sliced.glyphIndexForStringIndex(2)).toBe(2); - expect(sliced.glyphIndexForStringIndex(6)).toBe(6); + expect(sliced.glyphIndexForStringIndex(3)).toBe(3); + }); + + test('should return correct glyph index for string index for sliced strings (not latin)', () => { + const string = createKhmerString({ + value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', + runs: [[0, 8], [8, 21]] + }); + + const sliced = string.slice(4, 15); + + // expect(sliced.glyphIndexForStringIndex(0)).toBe(0); + // expect(sliced.glyphIndexForStringIndex(3)).toBe(3); + // expect(sliced.glyphIndexForStringIndex(4)).toBe(4); }); test('should return correct glyph code at glyph index', () => { From fd8b93ebbef061d642a6bfcfd0aa144dee8ed84b Mon Sep 17 00:00:00 2001 From: Diego Muracciole Date: Mon, 26 Mar 2018 18:38:09 -0300 Subject: [PATCH 4/5] Fix models tests --- out.pdf | Bin 10520 -> 5787 bytes src/generators/GlyphGenerator.js | 2 +- src/models/GlyphRun.js | 25 ++------ src/models/GlyphString.js | 98 ++++++++++++++--------------- temp.js | 6 ++ test/models/GlyphRun.test.js | 66 +++++++++++++------- test/models/GlyphString.test.js | 102 ++++++++++++++++++------------- test/utils/glyphRuns.js | 15 +---- test/utils/glyphStrings.js | 30 ++++++--- 9 files changed, 185 insertions(+), 159 deletions(-) diff --git a/out.pdf b/out.pdf index 2c4104c8963dbd68852149d78122ec363f9d3179..992f9188fa56f5cf76ddca8a05c62f4366ea29a1 100644 GIT binary patch delta 4443 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 P;V>yAP(VOcPYw8g#Huso delta 9215 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 DUi`-0 diff --git a/src/generators/GlyphGenerator.js b/src/generators/GlyphGenerator.js index 6bc33d0..96011d2 100644 --- a/src/generators/GlyphGenerator.js +++ b/src/generators/GlyphGenerator.js @@ -53,7 +53,7 @@ export default class GlyphGenerator { return res; }); - return new GlyphString(attributedString.string, glyphRuns, 0, attributedString.string.length); + return new GlyphString(attributedString.string, glyphRuns); } resolveRuns(attributedString) { diff --git a/src/models/GlyphRun.js b/src/models/GlyphRun.js index 75f7313..d897ee2 100644 --- a/src/models/GlyphRun.js +++ b/src/models/GlyphRun.js @@ -87,28 +87,11 @@ class GlyphRun extends Run { } slice(start, end) { - const glyphStart = this.glyphIndices[start]; - let glyphEnd = this.glyphIndices[end]; + const glyphs = this.glyphs.slice(start, end); + const positions = this.positions.slice(start, end); + let stringIndices = this.stringIndices.slice(start, end); - if (glyphEnd === undefined) { - glyphEnd = this.glyphIndices[this.glyphIndices.length - 1] + 1; - } - - let glyphs; - let positions; - let stringIndices; - - if (glyphEnd === 0) { - glyphs = [this.glyphs[glyphStart]]; - positions = [this.positions[glyphStart]]; - stringIndices = [this.stringIndices[glyphStart]]; - } else { - glyphs = this.glyphs.slice(glyphStart, glyphEnd); - positions = this.positions.slice(glyphStart, glyphEnd); - stringIndices = this.stringIndices.slice(glyphStart, glyphEnd); - } - - stringIndices = stringIndices.map(index => index - this.stringIndices[glyphStart]); + stringIndices = stringIndices.map(index => index - this.stringIndices[start]); start += this.start; end += this.start; diff --git a/src/models/GlyphString.js b/src/models/GlyphString.js index da7d4a9..ec06cee 100644 --- a/src/models/GlyphString.js +++ b/src/models/GlyphString.js @@ -22,7 +22,7 @@ const HANGING_PUNCTUATION_END_CODEPOINTS = new Set([ class GlyphString { constructor(string, glyphRuns, start, end) { - this._string = string; + this.string = string; this._glyphRuns = glyphRuns; this.start = start || 0; this._end = end; @@ -30,16 +30,14 @@ class GlyphString { this._glyphRunsCacheEnd = null; } - get string() { - return this._string.slice(this.start, this.end); - } - get end() { - if (this._end != null) { - return this._end; + const glyphEnd = this._glyphRuns[this._glyphRuns.length - 1].end; + + if (this._end) { + return Math.min(this._end, glyphEnd); } - return this._glyphRuns.length > 0 ? this._glyphRuns[this._glyphRuns.length - 1].end : 0; + return this._glyphRuns.length > 0 ? glyphEnd : 0; } get length() { @@ -67,8 +65,8 @@ class GlyphString { return this._glyphRunsCache; } - const startRunIndex = this.runIndexAtStringIndex(0); - const endRunIndex = this.runIndexAtStringIndex(this.length); + const startRunIndex = this.runIndexAtGlyphIndex(0); + const endRunIndex = this.runIndexAtGlyphIndex(this.length); const startRun = this._glyphRuns[startRunIndex]; const endRun = this._glyphRuns[endRunIndex]; const runs = []; @@ -89,14 +87,23 @@ class GlyphString { } slice(start, end) { - return new GlyphString(this._string, this._glyphRuns, start + this.start, end + this.start); + const stringStart = this.stringIndexForGlyphIndex(start); + const stringEnd = this.stringIndexForGlyphIndex(end - 1); + + return new GlyphString( + this.string.slice(stringStart, stringEnd + 1), + this._glyphRuns, + start + this.start, + end + this.start + ); } runIndexAtGlyphIndex(index) { + index += this.start; let count = 0; - for (let i = 0; i < this.glyphRuns.length; i++) { - const run = this.glyphRuns[i]; + for (let i = 0; i < this._glyphRuns.length; i++) { + const run = this._glyphRuns[i]; if (count <= index && index < count + run.glyphs.length) { return i; @@ -105,44 +112,36 @@ class GlyphString { count += run.glyphs.length; } - return this.glyphRuns.length - 1; + return this._glyphRuns.length - 1; } runAtGlyphIndex(index) { - return this._glyphRuns[this.runIndexAtGlyphIndex(index)]; + return this.glyphRuns[this.runIndexAtGlyphIndex(index)]; } runIndexAtStringIndex(index) { - const idx = index + this._glyphRuns[0].start + this.start; + let offset = 0; - for (let i = 0; i < this._glyphRuns.length; i++) { - if (this._glyphRuns[i].start <= idx && idx < this._glyphRuns[i].end) { + for (let i = 0; i < this.glyphRuns.length; i++) { + const run = this.glyphRuns[i]; + + if (offset + run.stringStart <= index && offset + run.stringEnd >= index) { return i; } + + offset += run.stringEnd; } return this._glyphRuns.length - 1; } runAtStringIndex(index) { - return this._glyphRuns[this.runIndexAtStringIndex(index)]; + return this.glyphRuns[this.runIndexAtStringIndex(index)]; } glyphAtIndex(index) { - let run; - let count = 0; - - for (let i = 0; i < this.glyphRuns.length; i++) { - run = this.glyphRuns[i]; - - if (count <= index && index < count + run.glyphs.length) { - return run.glyphs[index - count]; - } - - count += run.glyphs.length; - } - - return run.glyphs[run.glyphs.length - 1]; + const run = this.runAtGlyphIndex(index); + return run.glyphs[this.start + index - run.start]; } positionAtIndex(index) { @@ -198,15 +197,15 @@ class GlyphString { for (let i = 0; i < this.glyphRuns.length; i++) { run = this.glyphRuns[i]; - if (count <= index && index < count + run.glyphs.length) { - return offset + run.stringIndices[index - count]; + if (offset <= index && offset + run.length > index) { + return count + run.stringIndices[index + this.start - run.start]; } - offset += run.stringEnd + 1; - count += run.glyphs.length; + offset += run.length; + count += run.stringEnd + 1; } - return run.stringIndices[run.stringIndices.length - 1]; + return count + run.stringIndices[run.stringIndices.length - 1]; } glyphIndexForStringIndex(index) { @@ -217,15 +216,15 @@ class GlyphString { for (let i = 0; i < this.glyphRuns.length; i++) { run = this.glyphRuns[i]; - if (count <= index && index < count + run.length) { - return offset + run.glyphIndices[index - count]; + if (offset <= index && index < offset + run.stringEnd + 1) { + return count + run.glyphIndices[index - offset]; } - count += run.length; - offset += run.glyphs.length; + count += run.glyphs.length; + offset += run.stringEnd + 1; } - return run.glyphIndices[run.glyphIndices.length - 1]; + return offset; } codePointAtGlyphIndex(glyphIndex) { @@ -291,8 +290,11 @@ class GlyphString { const glyph = run.attributes.font.glyphForCodePoint(codePoint); const idx = this.start + index - run.start; + if (this._end) { + this._end += 1; + } + run.glyphs.splice(idx, 0, glyph); - run.glyphIndices.splice(idx, 0, run.glyphIndices[idx]); run.stringIndices.splice(idx, 0, run.stringIndices[idx]); run.positions.splice(idx, 0, { xAdvance: glyph.advanceWidth, @@ -308,10 +310,6 @@ class GlyphString { this._glyphRuns[i].end++; } - if (this._end != null) { - this._end++; - } - this._glyphRunsCache = null; } @@ -336,10 +334,6 @@ class GlyphString { this._glyphRuns[i].end--; } - if (this._end != null) { - this._end--; - } - this._glyphRunsCache = null; } } diff --git a/temp.js b/temp.js index 2eba6da..b389956 100644 --- a/temp.js +++ b/temp.js @@ -2,6 +2,7 @@ import fs from 'fs'; import PDFDocument from 'pdfkit'; import Path from './src/geom/Path'; import LayoutEngine from './src/layout/LayoutEngine'; +import Run from './src/models/Run'; import AttributedString from './src/models/AttributedString'; import Container from './src/models/Container'; import TextRenderer from './src/renderers/TextRenderer'; @@ -58,6 +59,11 @@ const string = AttributedString.fromFragments([ } ]); +// const string = new AttributedString('ខ្ញុំអាចញ៉ាំកញ្ចក់បាន',[ +// new Run(0, 8, { font: 'Khmer', color: 'red' }), +// new Run(8, 21, { font: 'Khmer', color: 'red' }), +// ]) + const l = new LayoutEngine(); const container = new Container(path, { exclusionPaths: [exclusion] diff --git a/test/models/GlyphRun.test.js b/test/models/GlyphRun.test.js index 3d52965..42eabb2 100644 --- a/test/models/GlyphRun.test.js +++ b/test/models/GlyphRun.test.js @@ -18,6 +18,30 @@ describe('GlyphRun', () => { expect(glyphRun.length).toBe(glyphs.length); }); + test('should get correct start', () => { + const { glyphRun } = createLatinRun(); + + expect(glyphRun.start).toBe(0); + }); + + test('should get correct start (non latin)', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); + + expect(glyphRun.start).toBe(0); + }); + + test('should get correct end', () => { + const { glyphRun } = createLatinRun(); + + expect(glyphRun.end).toBe(11); + }); + + test('should get correct end', () => { + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); + + expect(glyphRun.end).toBe(16); + }); + test('should get ascent correctly when no attachments', () => { const { glyphRun, attrs } = createLatinRun(); const scale = attrs.fontSize / openSans.unitsPerEm; @@ -74,7 +98,7 @@ describe('GlyphRun', () => { }); test('should exact slice range return same run', () => { - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const sliced = glyphRun.slice(0, 11); expect(sliced.start).toBe(0); @@ -82,7 +106,7 @@ describe('GlyphRun', () => { }); test('should slice containing range', () => { - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const sliced = glyphRun.slice(2, 5); expect(sliced.start).toBe(2); @@ -90,7 +114,7 @@ describe('GlyphRun', () => { }); test('should slice exceeding range', () => { - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const sliced = glyphRun.slice(2, 20); expect(sliced.start).toBe(2); @@ -98,7 +122,7 @@ describe('GlyphRun', () => { }); test('should slice containing range when start not zero', () => { - const { glyphRun } = createLatinRun({ start: 5, end: 11 }); + const { glyphRun } = createLatinRun({ start: 5 }); const sliced = glyphRun.slice(2, 5); expect(sliced.start).toBe(7); @@ -106,7 +130,7 @@ describe('GlyphRun', () => { }); test('should slice exceeding range when start not zero', () => { - const { glyphRun } = createLatinRun({ start: 5, end: 11 }); + const { glyphRun } = createLatinRun({ start: 5 }); const sliced = glyphRun.slice(2, 20); expect(sliced.start).toBe(7); @@ -116,30 +140,30 @@ describe('GlyphRun', () => { test('should correctly slice glyphs', () => { // 47 82 85 72 80 3 918 83 86 88 80 // l o r e m i p s u m - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const { glyphs } = glyphRun.slice(2, 8); expect(glyphs.map(g => g.id)).toEqual([85, 72, 80, 3, 918, 83]); }); test('should correctly slice glyphs (non latin)', () => { - const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); const { glyphs } = glyphRun.slice(1, 8); - expect(glyphs.map(g => g.id)).toEqual([153, 177, 112, 248, 188, 49]); + expect(glyphs.map(g => g.id)).toEqual([153, 177, 112, 248, 188, 49, 296]); }); test('should exact slice return same glyphs', () => { // 47 82 85 72 80 3 918 83 86 88 80 // l o r e m i p s u m - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const { glyphs } = glyphRun.slice(0, 11); expect(glyphs.map(g => g.id)).toEqual([47, 82, 85, 72, 80, 3, 918, 83, 86, 88, 80]); }); test('should exact slice return same glyphs (non latin)', () => { - const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); const { glyphs } = glyphRun.slice(0, 21); expect(glyphs.map(g => g.id)).toEqual([ @@ -165,7 +189,7 @@ describe('GlyphRun', () => { test('should correctly slice positions', () => { // 6.23 7.25 4.66 6.73 11.16 3.12 3.35 7.35 5.72 7.37 11.16 // l o r e m i p s u m - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const sliced = glyphRun.slice(2, 8); const positions = sliced.positions.map(p => round(p.xAdvance)); @@ -173,17 +197,17 @@ describe('GlyphRun', () => { }); test('should correctly slice positions (non latin)', () => { - const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); const sliced = glyphRun.slice(1, 8); const positions = sliced.positions.map(p => round(p.xAdvance)); - expect(positions).toEqual([0, 0, 0, 9.08, 4.54, 9.08]); + expect(positions).toEqual([0, 0, 0, 9.08, 4.54, 9.08, 18.16]); }); test('should exact slice return same positions', () => { // 6.23 7.25 4.66 6.73 11.16 3.12 3.35 7.35 5.72 7.37 11.16 // l o r e m i p s u m - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const sliced = glyphRun.slice(0, 11); const positions = sliced.positions.map(p => round(p.xAdvance)); @@ -191,7 +215,7 @@ describe('GlyphRun', () => { }); test('should exact slice return same positions (non latin)', () => { - const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); const sliced = glyphRun.slice(0, 21); const positions = sliced.positions.map(p => round(p.xAdvance)); @@ -216,35 +240,35 @@ describe('GlyphRun', () => { }); test('should correctly slice string indices', () => { - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const { stringIndices } = glyphRun.slice(2, 8); expect(stringIndices).toEqual([0, 1, 2, 3, 4, 5]); }); test('should correctly slice string indices (non latin)', () => { - const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); const { stringIndices } = glyphRun.slice(1, 8); - expect(stringIndices).toEqual([0, 2, 3, 4, 5, 6]); + expect(stringIndices).toEqual([0, 2, 3, 4, 5, 6, 7]); }); test('should exact slice return same string indices', () => { - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const { stringIndices } = glyphRun.slice(0, 11); expect(stringIndices).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); test('should exact slice return same string indices (non latin)', () => { - const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0, end: 21 }); + const { glyphRun } = createKhmerRun({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន', start: 0 }); const { stringIndices } = glyphRun.slice(0, 21); expect(stringIndices).toEqual([0, 1, 3, 4, 5, 6, 7, 8, 12, 13, 14, 16, 17, 18, 19, 20]); }); test('should correctly slice glyph indices', () => { - const { glyphRun } = createLatinRun({ start: 0, end: 11 }); + const { glyphRun } = createLatinRun({ start: 0 }); const { glyphIndices } = glyphRun.slice(2, 8); expect(glyphIndices).toEqual([0, 1, 2, 3, 4, 5]); diff --git a/test/models/GlyphString.test.js b/test/models/GlyphString.test.js index 0d76c90..71bbac8 100644 --- a/test/models/GlyphString.test.js +++ b/test/models/GlyphString.test.js @@ -21,12 +21,24 @@ describe('GlyphString', () => { expect(string.end).toBe(11); }); + test('should get string end (non latin)', () => { + const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន' }); + + expect(string.end).toBe(16); + }); + test('should get string length', () => { const string = createLatinString({ value: 'Lorem ipsum' }); expect(string.length).toBe(11); }); + test('should get string length (non latin)', () => { + const string = createKhmerString({ value: 'ខ្ញុំអាចញ៉ាំកញ្ចក់បាន' }); + + expect(string.length).toBe(16); + }); + test('should get string advance width', () => { const string = createLatinString({ value: 'Lorem ipsum' }); @@ -72,9 +84,9 @@ describe('GlyphString', () => { expect(string.glyphRuns).toHaveLength(2); expect(string.glyphRuns[0].start).toBe(0); - expect(string.glyphRuns[0].end).toBe(8); - expect(string.glyphRuns[1].start).toBe(8); - expect(string.glyphRuns[1].end).toBe(21); + expect(string.glyphRuns[0].end).toBe(7); + expect(string.glyphRuns[1].start).toBe(7); + expect(string.glyphRuns[1].end).toBe(16); }); test('should get glyph runs for sliced string', () => { @@ -102,8 +114,8 @@ describe('GlyphString', () => { expect(sliced.glyphRuns).toHaveLength(2); expect(sliced.glyphRuns[0].start).toBe(1); - expect(sliced.glyphRuns[0].end).toBe(8); - expect(sliced.glyphRuns[1].start).toBe(8); + expect(sliced.glyphRuns[0].end).toBe(7); + expect(sliced.glyphRuns[1].start).toBe(7); expect(sliced.glyphRuns[1].end).toBe(15); }); @@ -212,8 +224,8 @@ describe('GlyphString', () => { const sliced = string.slice(4, 11); expect(sliced.runIndexAtGlyphIndex(0)).toBe(0); - expect(sliced.runIndexAtGlyphIndex(3)).toBe(0); - expect(sliced.runIndexAtGlyphIndex(4)).toBe(1); + expect(sliced.runIndexAtGlyphIndex(2)).toBe(0); + expect(sliced.runIndexAtGlyphIndex(3)).toBe(1); expect(sliced.runIndexAtGlyphIndex(6)).toBe(1); }); @@ -235,8 +247,8 @@ describe('GlyphString', () => { const sliced = string.slice(4, 11); - expect(sliced.runAtGlyphIndex(0).start).toBe(0); - expect(sliced.runAtGlyphIndex(1).start).toBe(0); + expect(sliced.runAtGlyphIndex(0).start).toBe(4); + expect(sliced.runAtGlyphIndex(1).start).toBe(4); expect(sliced.runAtGlyphIndex(2).start).toBe(6); expect(sliced.runAtGlyphIndex(5).start).toBe(6); }); @@ -248,6 +260,8 @@ describe('GlyphString', () => { }); expect(string.runIndexAtStringIndex(2)).toBe(0); + expect(string.runIndexAtStringIndex(5)).toBe(0); + expect(string.runIndexAtStringIndex(6)).toBe(1); expect(string.runIndexAtStringIndex(9)).toBe(1); }); @@ -289,8 +303,7 @@ describe('GlyphString', () => { expect(sliced.runIndexAtStringIndex(0)).toBe(0); expect(sliced.runIndexAtStringIndex(2)).toBe(0); - expect(sliced.runIndexAtStringIndex(3)).toBe(0); - expect(sliced.runIndexAtStringIndex(4)).toBe(1); + expect(sliced.runIndexAtStringIndex(3)).toBe(1); expect(sliced.runIndexAtStringIndex(5)).toBe(1); expect(sliced.runIndexAtStringIndex(6)).toBe(1); }); @@ -313,8 +326,8 @@ describe('GlyphString', () => { expect(string.runAtStringIndex(2).start).toBe(0); expect(string.runAtStringIndex(7).start).toBe(0); - expect(string.runAtStringIndex(8).start).toBe(8); - expect(string.runAtStringIndex(20).start).toBe(8); + expect(string.runAtStringIndex(8).start).toBe(7); + expect(string.runAtStringIndex(20).start).toBe(7); }); test('should return correct run at string index for sliced strings', () => { @@ -325,8 +338,8 @@ describe('GlyphString', () => { const sliced = string.slice(4, 11); - expect(sliced.runAtStringIndex(0).start).toBe(0); - expect(sliced.runAtStringIndex(1).start).toBe(0); + expect(sliced.runAtStringIndex(0).start).toBe(4); + expect(sliced.runAtStringIndex(1).start).toBe(4); expect(sliced.runAtStringIndex(2).start).toBe(6); expect(sliced.runAtStringIndex(5).start).toBe(6); }); @@ -339,11 +352,11 @@ describe('GlyphString', () => { const sliced = string.slice(4, 11); - expect(sliced.runAtStringIndex(0).start).toBe(0); - expect(sliced.runAtStringIndex(1).start).toBe(0); - expect(sliced.runAtStringIndex(3).start).toBe(0); - expect(sliced.runAtStringIndex(4).start).toBe(8); - expect(sliced.runAtStringIndex(6).start).toBe(8); + expect(sliced.runAtStringIndex(0).start).toBe(4); + expect(sliced.runAtStringIndex(1).start).toBe(4); + expect(sliced.runAtStringIndex(2).start).toBe(4); + expect(sliced.runAtStringIndex(3).start).toBe(7); + expect(sliced.runAtStringIndex(6).start).toBe(7); }); test('should return correct glyph at index', () => { @@ -400,9 +413,9 @@ describe('GlyphString', () => { const firstRunGlyphs = sliced._glyphRuns[0].glyphs; const secondRunGlyphs = sliced._glyphRuns[1].glyphs; - expect(sliced.glyphAtIndex(0).id).toBe(firstRunGlyphs[3].id); - expect(sliced.glyphAtIndex(3).id).toBe(firstRunGlyphs[6].id); - expect(sliced.glyphAtIndex(4).id).toBe(secondRunGlyphs[0].id); + expect(sliced.glyphAtIndex(0).id).toBe(firstRunGlyphs[4].id); + expect(sliced.glyphAtIndex(2).id).toBe(firstRunGlyphs[6].id); + expect(sliced.glyphAtIndex(3).id).toBe(secondRunGlyphs[0].id); }); test('should return correct glyph width at index', () => { @@ -459,9 +472,10 @@ describe('GlyphString', () => { const firstRunPositions = sliced._glyphRuns[0].positions; const secondRunPositions = sliced._glyphRuns[1].positions; - expect(sliced.getGlyphWidth(0)).toBe(firstRunPositions[3].xAdvance); - expect(sliced.getGlyphWidth(1)).toBe(firstRunPositions[6].xAdvance); - expect(sliced.getGlyphWidth(4)).toBe(secondRunPositions[0].xAdvance); + expect(sliced.getGlyphWidth(0)).toBe(firstRunPositions[4].xAdvance); + expect(sliced.getGlyphWidth(1)).toBe(firstRunPositions[5].xAdvance); + expect(sliced.getGlyphWidth(3)).toBe(secondRunPositions[0].xAdvance); + expect(sliced.getGlyphWidth(4)).toBe(secondRunPositions[1].xAdvance); }); test('should return correct glyph index at offset', () => { @@ -518,10 +532,12 @@ describe('GlyphString', () => { const sliced = string.slice(4, 11); - expect(sliced.glyphIndexAtOffset(0)).toBe(1); - expect(sliced.glyphIndexAtOffset(9)).toBe(1); - expect(sliced.glyphIndexAtOffset(14)).toBe(3); - expect(sliced.glyphIndexAtOffset(24)).toBe(4); + expect(sliced.glyphIndexAtOffset(0)).toBe(0); + expect(sliced.glyphIndexAtOffset(10)).toBe(1); + expect(sliced.glyphIndexAtOffset(14)).toBe(2); + expect(sliced.glyphIndexAtOffset(24)).toBe(3); + expect(sliced.glyphIndexAtOffset(43)).toBe(4); + expect(sliced.glyphIndexAtOffset(52)).toBe(5); }); test('should return correct string index for glyph index', () => { @@ -570,12 +586,14 @@ describe('GlyphString', () => { runs: [[0, 8], [8, 21]] }); - const sliced = string.slice(4, 15); + const sliced = string.slice(4, 11); expect(sliced.stringIndexForGlyphIndex(0)).toBe(0); + expect(sliced.stringIndexForGlyphIndex(1)).toBe(1); expect(sliced.stringIndexForGlyphIndex(3)).toBe(3); - expect(sliced.stringIndexForGlyphIndex(4)).toBe(4); + expect(sliced.stringIndexForGlyphIndex(4)).toBe(7); expect(sliced.stringIndexForGlyphIndex(5)).toBe(8); + expect(sliced.stringIndexForGlyphIndex(6)).toBe(9); }); test('should return correct glyph index for string index', () => { @@ -625,9 +643,9 @@ describe('GlyphString', () => { const sliced = string.slice(4, 15); - // expect(sliced.glyphIndexForStringIndex(0)).toBe(0); - // expect(sliced.glyphIndexForStringIndex(3)).toBe(3); - // expect(sliced.glyphIndexForStringIndex(4)).toBe(4); + expect(sliced.glyphIndexForStringIndex(0)).toBe(0); + expect(sliced.glyphIndexForStringIndex(3)).toBe(3); + expect(sliced.glyphIndexForStringIndex(4)).toBe(4); }); test('should return correct glyph code at glyph index', () => { @@ -775,16 +793,16 @@ describe('GlyphString', () => { expect(string.start).toBe(0); expect(string.end).toBe(12); - expect(string._glyphRuns[0].start).toBe(0); - expect(string._glyphRuns[0].end).toBe(7); - expect(string._glyphRuns[1].start).toBe(7); - expect(string._glyphRuns[1].end).toBe(12); - expect(string._glyphRuns[0].glyphs[2].id).toBe(16); + expect(string.glyphRuns[0].start).toBe(0); + expect(string.glyphRuns[0].end).toBe(7); + expect(string.glyphRuns[1].start).toBe(7); + expect(string.glyphRuns[1].end).toBe(12); + expect(string.glyphRuns[0].glyphs[2].id).toBe(16); // Test string indices. // The new glyph shouldn't interfer with current indices - expect(string._glyphRuns[0].stringIndices[2]).toBe(2); - expect(string._glyphRuns[0].stringIndices[3]).toBe(2); + expect(string.glyphRuns[0].stringIndices[2]).toBe(2); + expect(string.glyphRuns[0].stringIndices[3]).toBe(2); }); test('should successfully insert glyph for sliced strings', () => { diff --git a/test/utils/glyphRuns.js b/test/utils/glyphRuns.js index 989e08d..2095b39 100644 --- a/test/utils/glyphRuns.js +++ b/test/utils/glyphRuns.js @@ -5,21 +5,12 @@ import GlyphRun from '../../src/models/GlyphRun'; export const glyphRunFactory = font => ({ attributes = {}, value = 'Lorem Ipsum', - start = 0, - end = value.length + start = 0 } = {}) => { - const string = value.slice(start, end); - const run = font.layout(string); + const run = font.layout(value); const attrs = new RunStyle(Object.assign({}, { font }, attributes)); const { glyphs, positions, stringIndices } = run; - const glyphRun = new GlyphRun( - start, - end || glyphs.length, - attrs, - glyphs, - positions, - stringIndices - ); + const glyphRun = new GlyphRun(start, glyphs.length, attrs, glyphs, positions, stringIndices); return { glyphRun, glyphs, positions, stringIndices, attrs }; }; diff --git a/test/utils/glyphStrings.js b/test/utils/glyphStrings.js index f24be4b..3e2d0a9 100644 --- a/test/utils/glyphStrings.js +++ b/test/utils/glyphStrings.js @@ -1,21 +1,31 @@ +import RunStyle from '../../src/models/RunStyle'; +import GlyphRun from '../../src/models/GlyphRun'; import GlyphString from '../../src/models/GlyphString'; -import { glyphRunFactory } from './glyphRuns'; /* eslint-disable-next-line */ export const glyphStringFactory = font => ({ value = 'Lorem ipsum', runs = [[0, value.length]] } = {}) => { - const createRun = glyphRunFactory(font); + let glyphIndex = 0; - const glyphRuns = runs.map( - run => - createRun({ - value, - start: run[0], - end: run[1] - }).glyphRun - ); + const glyphRuns = runs.map(run => { + const string = value.slice(run[0], run[1]); + const attrs = new RunStyle(Object.assign({}, { font })); + const { glyphs, positions, stringIndices } = font.layout(string); + const glyphRun = new GlyphRun( + glyphIndex, + glyphIndex + glyphs.length, + attrs, + glyphs, + positions, + stringIndices + ); + + glyphIndex += glyphs.length; + + return glyphRun; + }); return new GlyphString(value, glyphRuns, 0, value.length); }; From 346d07fc57fb07f1a49b60e82090ca3b6abed48c Mon Sep 17 00:00:00 2001 From: Diego Muracciole Date: Tue, 27 Mar 2018 03:41:43 -0300 Subject: [PATCH 5/5] Fix LineFragment --- out.pdf | Bin 5787 -> 32011 bytes src/models/GlyphString.js | 11 ++++++----- src/models/LineFragment.js | 7 +------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/out.pdf b/out.pdf index 992f9188fa56f5cf76ddca8a05c62f4366ea29a1..abb1fdad77a0af50f3d76d091314e73b26430b93 100644 GIT binary patch literal 32011 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%0D(bBqm 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 VfZIQo<6z_BVn?8+mQs;M_#aBYwU7V+ delta 4648 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