UNPKG

@pdfme/pdf-lib

Version:

Create and modify PDF files with JavaScript

576 lines (567 loc) 27.6 kB
import PDFPageLeaf from '../../../src/core/structures/PDFPageLeaf'; import { mergeIntoTypedArray, PDFArray, PDFBool, PDFCatalog, PDFContext, PDFDict, PDFHexString, PDFName, PDFNull, PDFNumber, PDFObjectParser, PDFPageTree, PDFRawStream, PDFRef, PDFString, typedArrayFor, numberToString, } from '../../../src/index'; const parse = (value, options = {}) => { const context = PDFContext.create(); const parser = PDFObjectParser.forBytes(typedArrayFor(value), context, options.capNumbers); return parser.parseObject(); }; const expectParse = (value, options) => expect(parse(value, options)); const expectParseStr = (value, options) => expect(String(parse(value, options))); describe(`PDFObjectParser`, () => { const origConsoleWarn = console.warn; beforeAll(() => { const ignoredWarnings = [ 'Parsed number that is too large for some PDF readers:', ]; console.warn = jest.fn((...args) => { const isIgnored = ignoredWarnings.find((iw) => args[0].includes(iw)); if (!isIgnored) origConsoleWarn(...args); }); }); beforeEach(() => { jest.clearAllMocks(); }); afterAll(() => { console.warn = origConsoleWarn; }); it(`throws an error when given empty input`, () => { expect(() => parse('')).toThrow(); }); it(`throws an error for invalid input`, () => { expect(() => parse('I_AM_INVAL')).toThrow(); }); describe(`when parsing true booleans`, () => { it(`handles just the 'true' keyword`, () => { expectParse('true').toBe(PDFBool.True); }); it(`handles whitespace before and after the 'true' keyword`, () => { expectParse('\0\t\n\f\r true\0\t\n\f\r ').toBe(PDFBool.True); }); it(`handles comments before and after the 'true' keyword`, () => { expectParse('% Lulz wut?\ntrue% Lulz wut?\n').toBe(PDFBool.True); }); }); describe(`when parsing false booleans`, () => { it(`handles just the 'false' keyword`, () => { expectParse('false').toBe(PDFBool.False); }); it(`handles whitespace before and after the 'false' keyword`, () => { expectParse('\0\t\n\f\r false\0\t\n\f\r ').toBe(PDFBool.False); }); it(`handles comments before and after the 'false' keyword`, () => { expectParse('% Lulz wut?\nfalse% Lulz wut?\n').toBe(PDFBool.False); }); }); describe(`when parsing numbers`, () => { [ ['123', '123'], ['43445', '43445'], ['+17', '17'], ['-98', '-98'], ['0', '0'], ['34.5', '34.5'], ['-3.62', '-3.62'], ['+123.6', '123.6'], ['4.', '4'], ['-.002', '-0.002'], ['0.', '0'], ].forEach(([input, output]) => { it(`handles ${input}`, () => { expectParse(input).toBeInstanceOf(PDFNumber); expectParseStr(input).toBe(output); }); }); it(`handles whitespace before and after the number`, () => { expectParse('\0\t\n\f\r -.5\0\t\n\f\r ').toBeInstanceOf(PDFNumber); expectParseStr('\0\t\n\f\r -.5\0\t\n\f\r ').toBe('-0.5'); }); it(`handles comments before and after the number`, () => { expectParse('% Lulz wut?\n-.5% Lulz wut?\n').toBeInstanceOf(PDFNumber); expectParseStr('% Lulz wut?\n-.5% Lulz wut?\n').toBe('-0.5'); }); it(`stops parsing the number when whitespace is encountered`, () => { expectParseStr('12\0' + '3').toBe('12'); expectParseStr('12\t3').toBe('12'); expectParseStr('12\n3').toBe('12'); expectParseStr('12\f3').toBe('12'); expectParseStr('12\r3').toBe('12'); expectParseStr('12 3').toBe('12'); }); it(`stops parsing the number when a delimiter is encountered`, () => { expectParseStr('12(3').toBe('12'); expectParseStr('12)3').toBe('12'); expectParseStr('12<3').toBe('12'); expectParseStr('12>3').toBe('12'); expectParseStr('12[3').toBe('12'); expectParseStr('12]3').toBe('12'); expectParseStr('12{3').toBe('12'); expectParseStr('12}3').toBe('12'); expectParseStr('12/3').toBe('12'); expectParseStr('12%3').toBe('12'); }); it(`can parse several numbers mashed together`, () => { const input = typedArrayFor('0.01.123+2.1-3..1-2.-.1'); const context = PDFContext.create(); const parser = PDFObjectParser.forBytes(input, context); expect(parser.parseObject().toString()).toBe('0.01'); expect(parser.parseObject().toString()).toBe('0.123'); expect(parser.parseObject().toString()).toBe('2.1'); expect(parser.parseObject().toString()).toBe('-3'); expect(parser.parseObject().toString()).toBe('0.1'); expect(parser.parseObject().toString()).toBe('-2'); expect(parser.parseObject().toString()).toBe('-0.1'); }); it(`caps numbers at Number.MAX_SAFE_INTEGER when capNumbers=true`, () => { expectParseStr(numberToString(Number.MAX_SAFE_INTEGER - 1), { capNumbers: true, }).toBe('9007199254740990'); expectParseStr(numberToString(Number.MAX_SAFE_INTEGER), { capNumbers: true, }).toBe('9007199254740991'); expectParseStr(numberToString(Number.MAX_SAFE_INTEGER + 1), { capNumbers: true, }).toBe('9007199254740991'); expectParseStr('340282346638528900000000000000000000000', { capNumbers: true, }).toBe('9007199254740991'); expectParseStr('340282346638528859811704183484516925440', { capNumbers: true, }).toBe('9007199254740991'); }); it(`does not cap numbers at Number.MAX_SAFE_INTEGER when capNumbers=false`, () => { expectParseStr(numberToString(Number.MAX_SAFE_INTEGER - 1)).toBe('9007199254740990'); expectParseStr(numberToString(Number.MAX_SAFE_INTEGER)).toBe('9007199254740991'); expectParseStr(numberToString(Number.MAX_SAFE_INTEGER + 1)).toBe('9007199254740992'); expectParseStr('340282346638528900000000000000000000000').toBe('340282346638528900000000000000000000000'); expectParseStr('340282346638528859811704183484516925440').toBe('340282346638528900000000000000000000000'); }); }); describe(`when parsing literal strings`, () => { [ ['(This is a string)'], ['(Strings may contain newlines\nand such.)'], [ '(Strings may contain balanced parentheses ( ) and special characters (*!&}^% and so on).)', ], ['(The following is an empty string.)'], ['()'], ['(It has zero (0) length.)'], ].forEach(([input]) => { it(`handles ${input}`, () => { expectParse(input).toBeInstanceOf(PDFString); expectParseStr(input).toBe(input); }); }); it(`handles whitespace before and after the string`, () => { expectParse('\0\t\n\f\r (testing)\0\t\n\f\r ').toBeInstanceOf(PDFString); expectParseStr('\0\t\n\f\r (testing)\0\t\n\f\r ').toBe('(testing)'); }); it(`handles comments before and after the string`, () => { expectParse('% Lulz wut?\n(testing)% Lulz wut?\n').toBeInstanceOf(PDFString); expectParseStr('% Lulz wut?\n(testing)% Lulz wut?\n').toBe('(testing)'); }); it(`does not stop parsing the string when whitespace is encountered`, () => { expectParseStr('(foo\0bar)').toBe('(foo\0bar)'); expectParseStr('(foo\tbar)').toBe('(foo\tbar)'); expectParseStr('(foo\nbar)').toBe('(foo\nbar)'); expectParseStr('(foo\fbar)').toBe('(foo\fbar)'); expectParseStr('(foo\rbar)').toBe('(foo\rbar)'); expectParseStr('(foo bar)').toBe('(foo bar)'); }); it(`does not stop parsing the string when a delimiter is encountered`, () => { expectParseStr('(foo<bar)').toBe('(foo<bar)'); expectParseStr('(foo>bar)').toBe('(foo>bar)'); expectParseStr('(foo[bar)').toBe('(foo[bar)'); expectParseStr('(foo]bar)').toBe('(foo]bar)'); expectParseStr('(foo{bar)').toBe('(foo{bar)'); expectParseStr('(foo}bar)').toBe('(foo}bar)'); expectParseStr('(foo/bar)').toBe('(foo/bar)'); expectParseStr('(foo%bar)').toBe('(foo%bar)'); }); it(`handles comments embedded within the string`, () => { expectParse('(stuff% and things\n)').toBeInstanceOf(PDFString); expectParseStr('(stuff% and things\n)').toBe('(stuff% and things\n)'); }); it(`handles nested parenthesis`, () => { expectParse('(FOO(BAR(QUX)(BAZ)))').toBeInstanceOf(PDFString); expectParseStr('(FOO(BAR(QUX)(BAZ)))').toBe('(FOO(BAR(QUX)(BAZ)))'); }); it(`respects escaped parenthesis`, () => { expectParse('(FOO\\(BAR)').toBeInstanceOf(PDFString); expectParseStr('(FOO\\(BAR)').toBe('(FOO\\(BAR)'); }); it(`respects escaped backslashes`, () => { expect(() => parse('(FOO\\\\(BAR)')).toThrow(); }); }); describe(`when parsing hex strings`, () => { [ ['<4E6F762073686D6F7A206B6120706F702E>'], ['<901FA3>'], ['<901FA>'], ['<01\n23\r45\f67\t89\0ab cdefABCDEF>'], ].forEach(([input]) => { it(`handles ${input}`, () => { expectParse(input).toBeInstanceOf(PDFHexString); expectParseStr(input).toBe(input); }); }); it(`handles whitespace before and after the hex string`, () => { expectParse('\0\t\n\f\r <ABC123>\0\t\n\f\r ').toBeInstanceOf(PDFHexString); expectParseStr('\0\t\n\f\r <ABC123>\0\t\n\f\r ').toBe('<ABC123>'); }); it(`handles comments before and after the hex string`, () => { expectParse('% Lulz wut?\n<ABC123>% Lulz wut?\n').toBeInstanceOf(PDFHexString); expectParseStr('% Lulz wut?\n<ABC123>% Lulz wut?\n').toBe('<ABC123>'); }); it(`does not stop parsing the hex string when whitespace is encountered`, () => { expectParseStr('<ABC\0D>').toBe('<ABC\0D>'); expectParseStr('<ABC\tD>').toBe('<ABC\tD>'); expectParseStr('<ABC\nD>').toBe('<ABC\nD>'); expectParseStr('<ABC\fD>').toBe('<ABC\fD>'); expectParseStr('<ABC\rD>').toBe('<ABC\rD>'); expectParseStr('<ABC D>').toBe('<ABC D>'); }); }); describe(`when parsing names`, () => { [ ['/Name1', 'Name1'], ['/ASomewhatLongerName', 'ASomewhatLongerName'], [ '/A;Name_With-Various***Characters?', 'A;Name_With-Various***Characters?', ], ['/1.2', '1.2'], ['/$$', '$$'], ['/@pattern', '@pattern'], ['/.notdef', '.notdef'], ['/lime#20Green', 'lime Green'], ['/paired#28#29parentheses', 'paired()parentheses'], ['/The_Key_of_F#23_Minor', 'The_Key_of_F#_Minor'], ['/A#42', 'AB'], ].forEach(([input, output]) => { it(`handles ${input}`, () => { expectParse(input).toBe(PDFName.of(output)); }); }); it(`handles names consisting of a single '/'`, () => { expectParse('/').toBe(PDFName.of('')); }); it(`handles whitespace before and after the name`, () => { expectParse('\0\t\n\f\r /Foo\0\t\n\f\r ').toBe(PDFName.of('Foo')); }); it(`handles comments before and after the name`, () => { expectParse('% Lulz wut?\n/Foo% Lulz wut?\n').toBe(PDFName.of('Foo')); }); it(`stops parsing the name when whitespace is encountered`, () => { expectParse('/Foo\0Bar').toBe(PDFName.of('Foo')); expectParse('/Foo\tBar').toBe(PDFName.of('Foo')); expectParse('/Foo\nBar').toBe(PDFName.of('Foo')); expectParse('/Foo\fBar').toBe(PDFName.of('Foo')); expectParse('/Foo\rBar').toBe(PDFName.of('Foo')); expectParse('/Foo Bar').toBe(PDFName.of('Foo')); }); it(`stops parsing the name when a delimiter is encountered`, () => { expectParse('/Foo(Bar').toBe(PDFName.of('Foo')); expectParse('/Foo)Bar').toBe(PDFName.of('Foo')); expectParse('/Foo<Bar').toBe(PDFName.of('Foo')); expectParse('/Foo>Bar').toBe(PDFName.of('Foo')); expectParse('/Foo[Bar').toBe(PDFName.of('Foo')); expectParse('/Foo]Bar').toBe(PDFName.of('Foo')); expectParse('/Foo{Bar').toBe(PDFName.of('Foo')); expectParse('/Foo}Bar').toBe(PDFName.of('Foo')); expectParse('/Foo/Bar').toBe(PDFName.of('Foo')); expectParse('/Foo%Bar').toBe(PDFName.of('Foo')); }); it(`can parse several names mashed together`, () => { const input = typedArrayFor('/Foo/Bar/Qux//Baz/Bing/Bang'); const context = PDFContext.create(); const parser = PDFObjectParser.forBytes(input, context); expect(parser.parseObject()).toBe(PDFName.of('Foo')); expect(parser.parseObject()).toBe(PDFName.of('Bar')); expect(parser.parseObject()).toBe(PDFName.of('Qux')); expect(parser.parseObject()).toBe(PDFName.of('')); expect(parser.parseObject()).toBe(PDFName.of('Baz')); expect(parser.parseObject()).toBe(PDFName.of('Bing')); expect(parser.parseObject()).toBe(PDFName.of('Bang')); }); it(`handles names containing non-ASCII characters`, () => { expectParse('/ABCDEE+»ªÎÄÖÐËÎ').toBe(PDFName.of('ABCDEE+»ªÎÄÖÐËÎ')); }); }); describe(`when parsing arrays`, () => { it(`handles empty arrays`, () => { expectParse('[]').toBeInstanceOf(PDFArray); expectParseStr('[]').toBe('[ ]'); }); it(`handles empty arrays with whitespace between braces`, () => { expectParse('[\0\t\n\f\r ]').toBeInstanceOf(PDFArray); expectParseStr('[\0\t\n\f\r ]').toBe('[ ]'); }); it(`handles arrays of all value types seperated by whitespace and (multiple) comments`, () => { const input = `% Comment \0\t\n\f\r % Comment [ \0\t\n\f\r % Comment /Foo % Comment \0\t\n\f\r % Comment << /Key /Val >> % Comment \0\t\n\f\r % Comment [] % Comment \0\t\n\f\r % Comment (Bar) % Comment \0\t\n\f\r % Comment 21 0 R % Comment \0\t\n\f\r % Comment 0.56 % Comment \0\t\n\f\r % Comment <ABC123> % Comment \0\t\n\f\r % Comment true % Comment \0\t\n\f\r % Comment null % Comment \0\t\n\f\r % Comment ]% Comment \0\t\n\f\r % Comment`; const object = parse(input); expect(object).toBeInstanceOf(PDFArray); const array = object; expect(array.size()).toBe(9); expect(array.get(0)).toBe(PDFName.of('Foo')); expect(array.get(1)).toBeInstanceOf(PDFDict); expect(array.get(2)).toBeInstanceOf(PDFArray); expect(array.get(3)).toBeInstanceOf(PDFString); expect(array.get(4)).toBe(PDFRef.of(21)); expect(array.get(5)).toBeInstanceOf(PDFNumber); expect(array.get(6)).toBeInstanceOf(PDFHexString); expect(array.get(7)).toBe(PDFBool.True); expect(array.get(8)).toBe(PDFNull); }); it(`handles arrays with no whitespace or comments`, () => { expectParse('[true/FooBar[]<</Foo/Bar>>21.null]').toBeInstanceOf(PDFArray); expectParseStr('[true/FooBar[]<</Foo/Bar>>21.null]').toBe('[ true /FooBar [ ] <<\n/Foo /Bar\n>> 21 null ]'); }); it(`throws an error when closing delimiter is missing`, () => { expect(() => parse('[/Foo')).toThrow(); }); it(`throws an error for mismatches delimiters`, () => { expect(() => parse('[[]')).toThrow(); }); it(`throws an error when an invalid element is detected`, () => { expect(() => parse('[/Foo I_AM_INVALID]')).toThrow(); }); }); describe(`when parsing dictionaries`, () => { it(`handles empty dictionaries`, () => { expectParse('<<>>').toBeInstanceOf(PDFDict); expectParseStr('<<>>').toBe('<<\n>>'); }); it(`handles empty dictionaries with whitespace between brackets`, () => { expectParse('<<\0\t\n\f\r >>').toBeInstanceOf(PDFDict); expectParseStr('<<\0\t\n\f\r >>').toBe('<<\n>>'); }); it(`handles dictionaries of all value types seperated by whitespace and (multiplecomments`, () => { const input = `% Comment \0\t\n\f\r % Comment << % Entry 1 /PDFName % Key /Foo % Value % Entry 2 /PDFDictionary % Key << /Key /Val >> % Value % Entry 3 /PDFArray % Key [1 (2)] % Value % Entry 4 /PDFString % Key (Look, a string!) % Value % Entry 5 /PDFRef % Key 21 0 R % Value % Entry 6 /PDFNumber % Key -.123 % Value % Entry 7 /PDFHexString % Key <ABC123> % Value % Entry 8 /PDFBool % Key true % Value % Entry 9 /PDFNull % Key null % Value % End >>% Comment \0\t\n\f\r % Comment `; const object = parse(input); expect(object).toBeInstanceOf(PDFDict); const dict = object; expect(dict.entries().length).toBe(9); expect(dict.get(PDFName.of('PDFName'))).toBe(PDFName.of('Foo')); expect(dict.get(PDFName.of('PDFDictionary'))).toBeInstanceOf(PDFDict); expect(dict.get(PDFName.of('PDFArray'))).toBeInstanceOf(PDFArray); expect(dict.get(PDFName.of('PDFString'))).toBeInstanceOf(PDFString); expect(dict.get(PDFName.of('PDFRef'))).toBe(PDFRef.of(21)); expect(dict.get(PDFName.of('PDFNumber'))).toBeInstanceOf(PDFNumber); expect(dict.get(PDFName.of('PDFHexString'))).toBeInstanceOf(PDFHexString); expect(dict.get(PDFName.of('PDFBool'))).toBe(PDFBool.True); expect(dict.get(PDFName.of('PDFNull'))).toBe(undefined); }); it(`handles dictionaries with no whitespace or comments`, () => { expectParse('<</Foo true/Bar[]/Qux<<>>/Baz 21./Bing null>>').toBeInstanceOf(PDFDict); expectParseStr('<</Foo true/Bar[]/Qux<<>>/Baz 21./Bing null>>').toBe('<<\n/Foo true\n/Bar [ ]\n/Qux <<\n>>\n/Baz 21\n/Bing null\n>>'); }); it(`returns the correct subclass based on the dictionary's 'Type'`, () => { expectParse('<< >>').toBeInstanceOf(PDFDict); expectParse('<< /Type /Catalog >>').toBeInstanceOf(PDFCatalog); expectParse('<< /Type /Pages >>').toBeInstanceOf(PDFPageTree); expectParse('<< /Type /Page >>').toBeInstanceOf(PDFPageLeaf); }); it(`throws an error when closing delimiter is missing`, () => { expect(() => parse('<</Foo/Bar')).toThrow(); }); it(`throws an error for mismatched delimiters`, () => { expect(() => parse('<<>')).toThrow(); }); it(`throws an error when an invalid key is detected`, () => { expect(() => parse('<</Foo/Bar I_AM_INVALID>>')).toThrow(); }); it(`throws an error when an invalid value is detected`, () => { expect(() => parse('<</Foo I_AM_INVALID>>')).toThrow(); }); }); describe(`when parsing streams`, () => { [ [ '<< >>\nstream\nstream foobar endstream\nendstream', '<<\n/Length 23\n>>\nstream\nstream foobar endstream\nendstream', ], [ '<</Length 2>>\r\nstream\r\nquxbaz\r\nendstream', '<<\n/Length 6\n>>\nstream\nquxbaz\nendstream', ], [ '<<>>streamfoobarendstream', '<<\n/Length 6\n>>\nstream\nfoobar\nendstream', ], [ '<<>>\rstream\rstuff\rendstream', '<<\n/Length 5\n>>\nstream\nstuff\nendstream', ], [ '<<>>\n\rstream\n\rthingz\n\rendstream', '<<\n/Length 8\n>>\nstream\n\rthingz\n\nendstream', ], ].forEach(([input, output]) => { it(`can parse ${JSON.stringify(input)}`, () => { const object = parse(typedArrayFor(input)); expect(object).toBeInstanceOf(PDFRawStream); const buffer = new Uint8Array(object.sizeInBytes()); object.copyBytesInto(buffer, 0); expect(buffer).toEqual(typedArrayFor(output)); }); }); // Note that the ' \r\n' sequence following the 'stream' keyword is // technically invalid (per the specification). But some PDFs have it, so // we will support it anyways. it(`handles streams with a space, carriage return, and a newline following the 'stream' keyword`, () => { expectParse(`<<>>\r\nstream \r\n Stuff and Things \nendstream`); expectParseStr(`<<>>\r\nstream \r\n Stuff and Things \nendstream`).toBe('<<\n/Length 18\n>>\nstream\n Stuff and Things \nendstream'); }); it(`handles streams with a carriage return and a newline following the 'stream' keyword`, () => { expectParse(`<<>>\r\nstream\r\n Stuff and Things \nendstream`); expectParseStr(`<<>>\r\nstream\r\n Stuff and Things \nendstream`).toBe('<<\n/Length 18\n>>\nstream\n Stuff and Things \nendstream'); }); it(`handles streams with only a carriage return following the 'stream' keyword`, () => { expectParse(`<<>>\rstream\r Stuff and Things \nendstream`); expectParseStr(`<<>>\nstream\n Stuff and Things \nendstream`).toBe('<<\n/Length 18\n>>\nstream\n Stuff and Things \nendstream'); }); it(`handles streams with a carriage return preceding the 'endstream' keyword`, () => { expectParse(`<<>>\r\nstream\r\n Stuff and Things \rendstream`); expectParseStr(`<<>>\r\nstream\r\n Stuff and Things \rendstream`).toBe('<<\n/Length 18\n>>\nstream\n Stuff and Things \nendstream'); }); it(`handles comments and whitespace preceding the 'stream' keyword`, () => { expectParse(`<<>>\0\t\n\f\r % I am a comment\0\t\n\f\r stream\r\n Stuff and Things \nendstream`); expectParseStr(`<<>>\0\t\n\f\r % I am a comment\0\t\n\f\r stream\r\n Stuff and Things \nendstream`).toBe('<<\n/Length 18\n>>\nstream\n Stuff and Things \nendstream'); }); it(`handles binary stream content`, () => { const input = mergeIntoTypedArray('<<>>stream', new Uint8Array([12, 492, 0, 129]), 'endstream'); const output = mergeIntoTypedArray('<<\n/Length 4\n>>\nstream\n', new Uint8Array([12, 492, 0, 129]), '\nendstream'); const object = parse(typedArrayFor(input)); expect(object).toBeInstanceOf(PDFRawStream); const buffer = new Uint8Array(object.sizeInBytes()); object.copyBytesInto(buffer, 0); expect(buffer).toEqual(typedArrayFor(output)); }); }); describe(`when parsing null`, () => { it(`handles just the 'null' keyword`, () => { expectParse('null').toBe(PDFNull); }); it(`handles whitespace before and after the 'null' keyword`, () => { expectParse('\0\t\n\f\r null\0\t\n\f\r ').toBe(PDFNull); }); it(`handles comments before and after the 'null' keyword`, () => { expectParse('% Lulz wut?\nnull% Lulz wut?\n').toBe(PDFNull); }); }); describe(`when parsing indirect object references`, () => { it(`handles whitespace before and after the ref`, () => { expectParse('\0\t\n\f\r 1 2 R\0\t\n\f\r ').toBe(PDFRef.of(1, 2)); }); it(`handles whitespace within the ref`, () => { expectParse('1\0\t\n\f\r2\0\t\n\f\rR').toBe(PDFRef.of(1, 2)); }); it(`handles comments before and after the ref`, () => { expectParse('% Lulz wut?\n1 2 R% Lulz wut?\n').toBe(PDFRef.of(1, 2)); }); it(`handles comments within the ref`, () => { expectParse('1% Lulz wut?\r2% Lulz wut?\rR').toBe(PDFRef.of(1, 2)); }); it(`does not stop parsing the ref when whitespace is encountered`, () => { expectParse('1\0' + '2\0R').toBe(PDFRef.of(1, 2)); expectParse('1\t2\tR').toBe(PDFRef.of(1, 2)); expectParse('1\n2\nR').toBe(PDFRef.of(1, 2)); expectParse('1\f2\fR').toBe(PDFRef.of(1, 2)); expectParse('1\r2\rR').toBe(PDFRef.of(1, 2)); expectParse('1 2 R').toBe(PDFRef.of(1, 2)); }); it(`stops parsing the ref when a delimiter is encountered`, () => { expectParseStr('1 2(R').toBe('1'); expectParseStr('1 2)R').toBe('1'); expectParseStr('1 2<R').toBe('1'); expectParseStr('1 2>R').toBe('1'); expectParseStr('1 2[R').toBe('1'); expectParseStr('1 2]R').toBe('1'); expectParseStr('1 2{R').toBe('1'); expectParseStr('1 2}R').toBe('1'); expectParseStr('1 2/R').toBe('1'); expectParseStr('1 2%R').toBe('1'); }); it(`can parse several refs mashed together`, () => { const input = typedArrayFor('0 0R1 1R 2 2R'); const context = PDFContext.create(); const parser = PDFObjectParser.forBytes(input, context); expect(parser.parseObject()).toBe(PDFRef.of(0, 0)); expect(parser.parseObject()).toBe(PDFRef.of(1, 1)); expect(parser.parseObject()).toBe(PDFRef.of(2, 2)); }); it(`can parse a number, then a ref, then a number`, () => { const input = typedArrayFor('0 21 0 R 42'); const context = PDFContext.create(); const parser = PDFObjectParser.forBytes(input, context); const object1 = parser.parseObject(); expect(object1).toBeInstanceOf(PDFNumber); expect(object1.toString()).toBe('0'); const object2 = parser.parseObject(); expect(object2).toBe(PDFRef.of(21)); const object3 = parser.parseObject(); expect(object3).toBeInstanceOf(PDFNumber); expect(object3.toString()).toBe('42'); }); }); }); //# sourceMappingURL=PDFObjectParser.spec.js.map