@chr33s/pdf-restructure 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +323 -0
  3. package/dist/array.d.ts +15 -0
  4. package/dist/array.js +95 -0
  5. package/dist/array.js.map +1 -0
  6. package/dist/base.d.ts +4 -0
  7. package/dist/base.js +16 -0
  8. package/dist/base.js.map +1 -0
  9. package/dist/bitfield.d.ts +11 -0
  10. package/dist/bitfield.js +37 -0
  11. package/dist/bitfield.js.map +1 -0
  12. package/dist/boolean.d.ts +10 -0
  13. package/dist/boolean.js +18 -0
  14. package/dist/boolean.js.map +1 -0
  15. package/dist/buffer.d.ts +11 -0
  16. package/dist/buffer.js +31 -0
  17. package/dist/buffer.js.map +1 -0
  18. package/dist/decode-stream.d.ts +26 -0
  19. package/dist/decode-stream.js +84 -0
  20. package/dist/decode-stream.js.map +1 -0
  21. package/dist/encode-stream.d.ts +19 -0
  22. package/dist/encode-stream.js +137 -0
  23. package/dist/encode-stream.js.map +1 -0
  24. package/dist/enum.d.ts +11 -0
  25. package/dist/enum.js +25 -0
  26. package/dist/enum.js.map +1 -0
  27. package/dist/index.d.ts +17 -0
  28. package/dist/index.js +18 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/lazy-array.d.ts +22 -0
  31. package/dist/lazy-array.js +75 -0
  32. package/dist/lazy-array.js.map +1 -0
  33. package/dist/number.d.ts +51 -0
  34. package/dist/number.js +76 -0
  35. package/dist/number.js.map +1 -0
  36. package/dist/optional.d.ts +14 -0
  37. package/dist/optional.js +34 -0
  38. package/dist/optional.js.map +1 -0
  39. package/dist/pointer.d.ts +28 -0
  40. package/dist/pointer.js +160 -0
  41. package/dist/pointer.js.map +1 -0
  42. package/dist/reserved.d.ts +12 -0
  43. package/dist/reserved.js +23 -0
  44. package/dist/reserved.js.map +1 -0
  45. package/dist/string.d.ts +14 -0
  46. package/dist/string.js +123 -0
  47. package/dist/string.js.map +1 -0
  48. package/dist/struct.d.ts +15 -0
  49. package/dist/struct.js +93 -0
  50. package/dist/struct.js.map +1 -0
  51. package/dist/utils.d.ts +13 -0
  52. package/dist/utils.js +45 -0
  53. package/dist/utils.js.map +1 -0
  54. package/dist/versioned-struct.d.ts +18 -0
  55. package/dist/versioned-struct.js +129 -0
  56. package/dist/versioned-struct.js.map +1 -0
  57. package/package.json +47 -0
  58. package/src/array.ts +113 -0
  59. package/src/base.ts +17 -0
  60. package/src/bitfield.ts +46 -0
  61. package/src/boolean.ts +24 -0
  62. package/src/buffer.ts +40 -0
  63. package/src/decode-stream.ts +97 -0
  64. package/src/encode-stream.ts +161 -0
  65. package/src/enum.ts +32 -0
  66. package/src/index.ts +17 -0
  67. package/src/lazy-array.ts +91 -0
  68. package/src/number.ts +88 -0
  69. package/src/optional.ts +46 -0
  70. package/src/pointer.ts +204 -0
  71. package/src/reserved.ts +29 -0
  72. package/src/string.ts +154 -0
  73. package/src/struct.ts +127 -0
  74. package/src/utils.ts +55 -0
  75. package/src/versioned-struct.ts +174 -0
  76. package/test/array.test.ts +95 -0
  77. package/test/bitfield.test.ts +52 -0
  78. package/test/boolean.test.ts +35 -0
  79. package/test/buffer.test.ts +49 -0
  80. package/test/decode-stream.test.ts +104 -0
  81. package/test/encode-stream.test.ts +111 -0
  82. package/test/enum.test.ts +30 -0
  83. package/test/lazy-array.test.ts +70 -0
  84. package/test/number.test.ts +222 -0
  85. package/test/optional.test.ts +105 -0
  86. package/test/pointer.test.ts +248 -0
  87. package/test/reserved.test.ts +28 -0
  88. package/test/string.test.ts +114 -0
  89. package/test/struct.test.ts +164 -0
  90. package/test/versioned-struct.test.ts +462 -0
  91. package/tsconfig.json +9 -0
  92. package/tsconfig.typecheck.json +14 -0
  93. package/vitest.config.ts +12 -0
package/src/enum.ts ADDED
@@ -0,0 +1,32 @@
1
+ import Base from "./base.js";
2
+ import type DecodeStream from "./decode-stream.js";
3
+ import type EncodeStream from "./encode-stream.js";
4
+
5
+ export default class Enum<T = unknown> extends Base<T> {
6
+ public type: any;
7
+ public options: T[];
8
+
9
+ constructor(type: any, options: T[] = []) {
10
+ super();
11
+ this.type = type;
12
+ this.options = options;
13
+ }
14
+
15
+ decode(stream: DecodeStream): T {
16
+ const index = this.type.decode(stream);
17
+ return this.options[index] ?? index;
18
+ }
19
+
20
+ size(): number {
21
+ return this.type.size();
22
+ }
23
+
24
+ encode(stream: EncodeStream, value: T): void {
25
+ const index = this.options.indexOf(value);
26
+ if (index === -1) {
27
+ throw new Error(`Unknown option in enum: ${String(value)}`);
28
+ }
29
+
30
+ this.type.encode(stream, index);
31
+ }
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export { default as Array } from "./array.js";
2
+ export { default as Base } from "./base.js";
3
+ export { default as Bitfield } from "./bitfield.js";
4
+ export { default as Boolean } from "./boolean.js";
5
+ export { default as Buffer } from "./buffer.js";
6
+ export { default as DecodeStream } from "./decode-stream.js";
7
+ export { default as EncodeStream } from "./encode-stream.js";
8
+ export { default as Enum } from "./enum.js";
9
+ export { default as LazyArray } from "./lazy-array.js";
10
+ export * from "./number.js";
11
+ export { default as Optional } from "./optional.js";
12
+ export * from "./pointer.js";
13
+ export { default as Reserved } from "./reserved.js";
14
+ export { default as String } from "./string.js";
15
+ export { default as Struct } from "./struct.js";
16
+ export * from "./utils.js";
17
+ export { default as VersionedStruct } from "./versioned-struct.js";
@@ -0,0 +1,91 @@
1
+ import ArrayT from "./array.js";
2
+ import type DecodeStream from "./decode-stream.js";
3
+ import type EncodeStream from "./encode-stream.js";
4
+ import { Number as NumberT } from "./number.js";
5
+ import { resolveLength } from "./utils.js";
6
+
7
+ // Node.js inspect symbol for custom formatting
8
+ const inspectSymbol = Symbol.for("nodejs.util.inspect.custom");
9
+
10
+ export class LazyArray<T = unknown> {
11
+ #base: number;
12
+ #items: Array<T | undefined> = [];
13
+
14
+ public type: any;
15
+ public length: number;
16
+ public stream: DecodeStream;
17
+ public ctx: any;
18
+
19
+ constructor(type: any, length: number, stream: DecodeStream, ctx: any) {
20
+ this.type = type;
21
+ this.length = length;
22
+ this.stream = stream;
23
+ this.ctx = ctx;
24
+ this.#base = stream.pos;
25
+ }
26
+
27
+ get(index: number): T | undefined {
28
+ if (index < 0 || index >= this.length) {
29
+ return undefined;
30
+ }
31
+
32
+ if (typeof this.#items[index] === "undefined") {
33
+ const pos = this.stream.pos;
34
+ this.stream.pos = this.#base + this.type.size(null, this.ctx) * index;
35
+ this.#items[index] = this.type.decode(this.stream, this.ctx);
36
+ this.stream.pos = pos;
37
+ }
38
+
39
+ return this.#items[index];
40
+ }
41
+
42
+ toArray(): T[] {
43
+ const values: T[] = [];
44
+ for (let i = 0; i < this.length; i += 1) {
45
+ const value = this.get(i);
46
+ if (typeof value !== "undefined") {
47
+ values.push(value);
48
+ }
49
+ }
50
+ return values;
51
+ }
52
+
53
+ inspect(): string {
54
+ return JSON.stringify(this.toArray());
55
+ }
56
+
57
+ [inspectSymbol](): string {
58
+ return JSON.stringify(this.toArray());
59
+ }
60
+ }
61
+
62
+ export default class LazyArrayT<T = unknown> extends ArrayT<T, LazyArray<T>> {
63
+ decode(stream: DecodeStream, parent?: any): LazyArray<T> {
64
+ const pos = stream.pos;
65
+ const length = resolveLength(this.length, stream, parent);
66
+ let ctx = parent;
67
+
68
+ if (this.length instanceof NumberT) {
69
+ ctx = {
70
+ parent,
71
+ _startOffset: pos,
72
+ _currentOffset: 0,
73
+ _length: length,
74
+ };
75
+ }
76
+
77
+ const result = new LazyArray<T>(this.type, length, stream, ctx);
78
+ stream.pos += length * this.type.size(null, ctx);
79
+ return result;
80
+ }
81
+
82
+ size(value?: LazyArray<T> | T[], ctx?: any): number {
83
+ const resolved = value instanceof LazyArray ? value.toArray() : value;
84
+ return super.size(resolved as T[] | undefined, ctx);
85
+ }
86
+
87
+ encode(stream: EncodeStream, value: LazyArray<T> | T[], ctx?: any): void {
88
+ const resolved = value instanceof LazyArray ? value.toArray() : value;
89
+ super.encode(stream, resolved as T[], ctx);
90
+ }
91
+ }
package/src/number.ts ADDED
@@ -0,0 +1,88 @@
1
+ import Base from "./base.js";
2
+ import DecodeStream from "./decode-stream.js";
3
+ import type EncodeStream from "./encode-stream.js";
4
+
5
+ export class NumberT extends Base<number> {
6
+ #fn: string;
7
+ public type: string;
8
+ public endian: "BE" | "LE";
9
+
10
+ constructor(type: string, endian: "BE" | "LE" = "BE") {
11
+ super();
12
+ this.type = type;
13
+ this.endian = endian;
14
+ this.#fn = type;
15
+ if (this.type[this.type.length - 1] !== "8") {
16
+ this.#fn += this.endian;
17
+ }
18
+ }
19
+
20
+ size(): number {
21
+ const size = DecodeStream.TYPES[this.type as keyof typeof DecodeStream.TYPES];
22
+ if (!size) {
23
+ throw new Error(`Unknown number type: ${this.type}`);
24
+ }
25
+ return size;
26
+ }
27
+
28
+ decode(stream: DecodeStream): number {
29
+ return (stream as any)[`read${this.#fn}`]();
30
+ }
31
+
32
+ encode(stream: EncodeStream, value: number): void {
33
+ (stream as any)[`write${this.#fn}`](value);
34
+ }
35
+ }
36
+
37
+ export const Number = NumberT;
38
+
39
+ export const uint8 = new NumberT("UInt8");
40
+ export const uint16be = new NumberT("UInt16", "BE");
41
+ export const uint16le = new NumberT("UInt16", "LE");
42
+ export const uint16 = uint16be;
43
+ export const uint24be = new NumberT("UInt24", "BE");
44
+ export const uint24le = new NumberT("UInt24", "LE");
45
+ export const uint24 = uint24be;
46
+ export const uint32be = new NumberT("UInt32", "BE");
47
+ export const uint32le = new NumberT("UInt32", "LE");
48
+ export const uint32 = uint32be;
49
+ export const int8 = new NumberT("Int8");
50
+ export const int16be = new NumberT("Int16", "BE");
51
+ export const int16le = new NumberT("Int16", "LE");
52
+ export const int16 = int16be;
53
+ export const int24be = new NumberT("Int24", "BE");
54
+ export const int24le = new NumberT("Int24", "LE");
55
+ export const int24 = int24be;
56
+ export const int32be = new NumberT("Int32", "BE");
57
+ export const int32le = new NumberT("Int32", "LE");
58
+ export const int32 = int32be;
59
+ export const floatbe = new NumberT("Float", "BE");
60
+ export const floatle = new NumberT("Float", "LE");
61
+ export const float = floatbe;
62
+ export const doublebe = new NumberT("Double", "BE");
63
+ export const doublele = new NumberT("Double", "LE");
64
+ export const double = doublebe;
65
+
66
+ export class Fixed extends NumberT {
67
+ readonly #point: number;
68
+
69
+ constructor(size: number, endian: "BE" | "LE", fracBits = size >> 1) {
70
+ super(`Int${size}`, endian);
71
+ this.#point = 1 << fracBits;
72
+ }
73
+
74
+ decode(stream: DecodeStream): number {
75
+ return super.decode(stream) / this.#point;
76
+ }
77
+
78
+ encode(stream: EncodeStream, value: number): void {
79
+ super.encode(stream, (value * this.#point) | 0);
80
+ }
81
+ }
82
+
83
+ export const fixed16be = new Fixed(16, "BE");
84
+ export const fixed16le = new Fixed(16, "LE");
85
+ export const fixed16 = fixed16be;
86
+ export const fixed32be = new Fixed(32, "BE");
87
+ export const fixed32le = new Fixed(32, "LE");
88
+ export const fixed32 = fixed32be;
@@ -0,0 +1,46 @@
1
+ import Base from "./base.js";
2
+ import type DecodeStream from "./decode-stream.js";
3
+ import type EncodeStream from "./encode-stream.js";
4
+
5
+ type Condition = boolean | ((this: any, parent?: any) => boolean);
6
+
7
+ export default class Optional<T = unknown> extends Base<T | undefined> {
8
+ public type: any;
9
+ public condition: Condition;
10
+
11
+ constructor(type: any, condition: Condition = true) {
12
+ super();
13
+ this.type = type;
14
+ this.condition = condition;
15
+ }
16
+
17
+ #evaluateCondition(parent?: any): boolean {
18
+ if (typeof this.condition === "function") {
19
+ return this.condition.call(parent, parent);
20
+ }
21
+
22
+ return this.condition;
23
+ }
24
+
25
+ decode(stream: DecodeStream, parent?: any): T | undefined {
26
+ if (this.#evaluateCondition(parent)) {
27
+ return this.type.decode(stream, parent);
28
+ }
29
+
30
+ return undefined;
31
+ }
32
+
33
+ size(value?: T, parent?: any): number {
34
+ if (this.#evaluateCondition(parent)) {
35
+ return this.type.size(value, parent);
36
+ }
37
+
38
+ return 0;
39
+ }
40
+
41
+ encode(stream: EncodeStream, value: T, parent?: any): void {
42
+ if (this.#evaluateCondition(parent)) {
43
+ this.type.encode(stream, value, parent);
44
+ }
45
+ }
46
+ }
package/src/pointer.ts ADDED
@@ -0,0 +1,204 @@
1
+ import Base from "./base.js";
2
+ import type DecodeStream from "./decode-stream.js";
3
+ import type EncodeStream from "./encode-stream.js";
4
+ import { PropertyDescriptor } from "./utils.js";
5
+
6
+ export type PointerType = "local" | "immediate" | "parent" | "global";
7
+
8
+ export interface PointerOptions {
9
+ type?: PointerType;
10
+ allowNull?: boolean;
11
+ nullValue?: number;
12
+ relativeTo?: string | ((ctx: any) => number);
13
+ lazy?: boolean;
14
+ }
15
+
16
+ export class Pointer extends Base<any> {
17
+ public options: Required<Omit<PointerOptions, "relativeTo">> & {
18
+ relativeTo?: string | ((ctx: any) => number);
19
+ };
20
+ public type: any;
21
+ public offsetType: any;
22
+
23
+ constructor(offsetType: any, type: any, options: PointerOptions = {}) {
24
+ super();
25
+ this.offsetType = offsetType;
26
+ this.type = type === "void" ? null : type;
27
+ this.options = {
28
+ type: "local",
29
+ allowNull: true,
30
+ nullValue: 0,
31
+ lazy: false,
32
+ ...options,
33
+ };
34
+ }
35
+
36
+ #relativeToGetter(ctx: any): number {
37
+ const { relativeTo } = this.options;
38
+ if (!relativeTo) {
39
+ return 0;
40
+ }
41
+
42
+ if (typeof relativeTo === "function") {
43
+ return relativeTo(ctx) ?? 0;
44
+ }
45
+
46
+ return relativeTo.split(".").reduce((obj: any, prop: string) => obj?.[prop], ctx) ?? 0;
47
+ }
48
+
49
+ decode(stream: DecodeStream, ctx: any): any {
50
+ const offset = this.offsetType.decode(stream, ctx);
51
+
52
+ if (offset === this.options.nullValue && this.options.allowNull) {
53
+ return null;
54
+ }
55
+
56
+ let relative = 0;
57
+
58
+ switch (this.options.type) {
59
+ case "local":
60
+ relative = ctx?._startOffset ?? 0;
61
+ break;
62
+ case "immediate":
63
+ relative = stream.pos - this.offsetType.size();
64
+ break;
65
+ case "parent":
66
+ relative = ctx?.parent?._startOffset ?? 0;
67
+ break;
68
+ default: {
69
+ let current = ctx;
70
+ while (current?.parent) {
71
+ current = current.parent;
72
+ }
73
+ relative = current?._startOffset ?? 0;
74
+ }
75
+ }
76
+
77
+ if (this.options.relativeTo) {
78
+ relative += this.#relativeToGetter(ctx);
79
+ }
80
+
81
+ const ptr = offset + relative;
82
+
83
+ if (this.type) {
84
+ let value: any;
85
+ let decoded = false;
86
+ const decodeValue = () => {
87
+ if (decoded) {
88
+ return value;
89
+ }
90
+ const pos = stream.pos;
91
+ stream.pos = ptr;
92
+ value = this.type.decode(stream, ctx);
93
+ stream.pos = pos;
94
+ decoded = true;
95
+ return value;
96
+ };
97
+
98
+ if (this.options.lazy) {
99
+ return new PropertyDescriptor({ get: decodeValue });
100
+ }
101
+
102
+ return decodeValue();
103
+ }
104
+
105
+ return ptr;
106
+ }
107
+
108
+ size(val?: any, ctx?: any): number {
109
+ const parent = ctx;
110
+
111
+ if (ctx) {
112
+ switch (this.options.type) {
113
+ case "local":
114
+ case "immediate":
115
+ break;
116
+ case "parent":
117
+ ctx = ctx.parent;
118
+ break;
119
+ default:
120
+ while (ctx.parent) {
121
+ ctx = ctx.parent;
122
+ }
123
+ }
124
+ }
125
+
126
+ let type = this.type;
127
+ if (!type) {
128
+ if (!(val instanceof VoidPointer)) {
129
+ throw new Error("Must be a VoidPointer");
130
+ }
131
+ type = val.type;
132
+ val = val.value;
133
+ }
134
+
135
+ if (val != null && ctx) {
136
+ ctx.pointerSize += type.size(val, parent);
137
+ }
138
+
139
+ return this.offsetType.size();
140
+ }
141
+
142
+ encode(stream: EncodeStream, val: any, ctx: any): void {
143
+ const parent = ctx;
144
+
145
+ if (val == null) {
146
+ this.offsetType.encode(stream, this.options.nullValue);
147
+ return;
148
+ }
149
+
150
+ let relative: number;
151
+
152
+ switch (this.options.type) {
153
+ case "local":
154
+ relative = ctx.startOffset;
155
+ break;
156
+ case "immediate":
157
+ relative = stream.pos + this.offsetType.size(val, parent);
158
+ break;
159
+ case "parent":
160
+ ctx = ctx.parent;
161
+ relative = ctx.startOffset;
162
+ break;
163
+ default:
164
+ relative = 0;
165
+ while (ctx.parent) {
166
+ ctx = ctx.parent;
167
+ }
168
+ }
169
+
170
+ if (this.options.relativeTo) {
171
+ const target = parent?.val ?? parent;
172
+ relative += this.#relativeToGetter(target);
173
+ }
174
+
175
+ this.offsetType.encode(stream, ctx.pointerOffset - relative);
176
+
177
+ let type = this.type;
178
+ if (!type) {
179
+ if (!(val instanceof VoidPointer)) {
180
+ throw new Error("Must be a VoidPointer");
181
+ }
182
+ type = val.type;
183
+ val = val.value;
184
+ }
185
+
186
+ ctx.pointers.push({
187
+ type,
188
+ val,
189
+ parent,
190
+ });
191
+
192
+ ctx.pointerOffset += type.size(val, parent);
193
+ }
194
+ }
195
+
196
+ export class VoidPointer {
197
+ public type: any;
198
+ public value: any;
199
+
200
+ constructor(type: any, value: any) {
201
+ this.type = type;
202
+ this.value = value;
203
+ }
204
+ }
@@ -0,0 +1,29 @@
1
+ import Base from "./base.js";
2
+ import type DecodeStream from "./decode-stream.js";
3
+ import type EncodeStream from "./encode-stream.js";
4
+ import { resolveLength, type LengthLike } from "./utils.js";
5
+
6
+ export default class Reserved extends Base<void> {
7
+ public type: any;
8
+ public count: LengthLike | number;
9
+
10
+ constructor(type: any, count: LengthLike | number = 1) {
11
+ super();
12
+ this.type = type;
13
+ this.count = count;
14
+ }
15
+
16
+ decode(stream: DecodeStream, parent?: any): undefined {
17
+ stream.pos += this.size(undefined, parent);
18
+ return undefined;
19
+ }
20
+
21
+ size(_data?: any, parent?: any): number {
22
+ const count = resolveLength(this.count as LengthLike, undefined, parent);
23
+ return this.type.size() * count;
24
+ }
25
+
26
+ encode(stream: EncodeStream, _val?: any, parent?: any): void {
27
+ stream.fill(0, this.size(undefined, parent));
28
+ }
29
+ }
package/src/string.ts ADDED
@@ -0,0 +1,154 @@
1
+ import Base from "./base.js";
2
+ import type DecodeStream from "./decode-stream.js";
3
+ import type EncodeStream from "./encode-stream.js";
4
+ import { Number as NumberT } from "./number.js";
5
+ import { resolveLength, type LengthLike } from "./utils.js";
6
+
7
+ type EncodingResolver = string | ((this: any, parent?: any) => string | undefined);
8
+
9
+ const ENCODING_ALIASES: Record<string, string> = {
10
+ "utf-8": "utf8",
11
+ utf8: "utf8",
12
+ ascii: "ascii",
13
+ utf16: "utf16le",
14
+ "utf-16": "utf16le",
15
+ utf16le: "utf16le",
16
+ "utf-16le": "utf16le",
17
+ "utf16-le": "utf16le",
18
+ "utf-16-le": "utf16le",
19
+ utf16be: "utf16be",
20
+ "utf-16be": "utf16be",
21
+ "utf16-be": "utf16be",
22
+ "utf-16-be": "utf16be",
23
+ ucs2: "ucs2",
24
+ };
25
+
26
+ function normalizeEncoding(encoding: string | undefined): string {
27
+ if (!encoding) {
28
+ return "ascii";
29
+ }
30
+ const key = encoding.toLowerCase();
31
+ return ENCODING_ALIASES[key] ?? key;
32
+ }
33
+
34
+ function resolveEncodingValue(
35
+ resolver: EncodingResolver,
36
+ parent?: any,
37
+ useParentVal = false,
38
+ ): string {
39
+ if (typeof resolver === "function") {
40
+ const ctx = useParentVal ? (parent?.val ?? parent) : parent;
41
+ return normalizeEncoding(resolver.call(ctx, ctx));
42
+ }
43
+
44
+ return normalizeEncoding(resolver);
45
+ }
46
+
47
+ function encodingWidth(encoding: string): number {
48
+ switch (encoding) {
49
+ case "ascii":
50
+ case "utf8":
51
+ return 1;
52
+ case "utf16le":
53
+ case "utf16be":
54
+ case "ucs2":
55
+ return 2;
56
+ default:
57
+ return 1;
58
+ }
59
+ }
60
+
61
+ const textEncoder = new TextEncoder();
62
+
63
+ function byteLength(string: string, encoding: string): number {
64
+ switch (encoding) {
65
+ case "utf8":
66
+ return textEncoder.encode(string).length;
67
+ case "utf16le":
68
+ case "utf16be":
69
+ case "ucs2":
70
+ return string.length * 2;
71
+ case "ascii":
72
+ default:
73
+ return string.length;
74
+ }
75
+ }
76
+
77
+ export default class StringT extends Base<string | Uint8Array> {
78
+ public length?: LengthLike;
79
+ public encoding: EncodingResolver;
80
+
81
+ constructor(length?: LengthLike, encoding: EncodingResolver = "ascii") {
82
+ super();
83
+ this.length = length;
84
+ this.encoding = encoding;
85
+ }
86
+
87
+ decode(stream: DecodeStream, parent?: any): string | Uint8Array {
88
+ const encoding = resolveEncodingValue(this.encoding, parent);
89
+ const width = encodingWidth(encoding);
90
+
91
+ let length: number;
92
+
93
+ if (typeof this.length !== "undefined") {
94
+ length = resolveLength(this.length, stream, parent);
95
+ } else {
96
+ const { buffer } = stream;
97
+ let pos = stream.pos;
98
+ const limit = stream.length;
99
+ const maxPos = Math.max(limit - width + 1, stream.pos);
100
+
101
+ while (pos < maxPos && (buffer[pos] !== 0x00 || (width === 2 && buffer[pos + 1] !== 0x00))) {
102
+ pos += width;
103
+ }
104
+
105
+ length = Math.max(pos - stream.pos, 0);
106
+ }
107
+
108
+ const value = stream.readString(length, encoding);
109
+
110
+ if (typeof this.length === "undefined" && stream.pos < stream.length) {
111
+ stream.pos += width;
112
+ }
113
+
114
+ return value;
115
+ }
116
+
117
+ size(value?: string, parent?: any): number {
118
+ if (value == null) {
119
+ return resolveLength(this.length, undefined, parent);
120
+ }
121
+
122
+ const encoding = resolveEncodingValue(this.encoding, parent, true);
123
+ let size = byteLength(value, encoding);
124
+
125
+ if (this.length instanceof NumberT) {
126
+ size += this.length.size();
127
+ }
128
+
129
+ if (typeof this.length === "undefined") {
130
+ size += encodingWidth(encoding);
131
+ }
132
+
133
+ return size;
134
+ }
135
+
136
+ encode(stream: EncodeStream, value: string, parent?: any): void {
137
+ const encoding = resolveEncodingValue(this.encoding, parent, true);
138
+ const width = encodingWidth(encoding);
139
+
140
+ if (this.length instanceof NumberT) {
141
+ this.length.encode(stream, byteLength(value, encoding));
142
+ }
143
+
144
+ stream.writeString(value, encoding);
145
+
146
+ if (typeof this.length === "undefined") {
147
+ if (width === 2) {
148
+ stream.writeUInt16LE(0x0000);
149
+ } else {
150
+ stream.writeUInt8(0x00);
151
+ }
152
+ }
153
+ }
154
+ }