mirror of
https://github.com/zoriya/libpgs-js.git
synced 2026-08-27 00:00:51 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
/dist/
|
||||
/node_modules/
|
||||
/.idea/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 David Schulte
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1 @@
|
||||
This library renders the graphical subtitles format PGS _(.sub / .sup)_ in the browser.
|
||||
Generated
+1867
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "libpgs",
|
||||
"version": "0.1.0",
|
||||
"author": "David Schulte",
|
||||
"license": "MIT",
|
||||
"description": "Renderer for graphical subtitles (PGS) in the browser. ",
|
||||
"keywords": [
|
||||
"libpgs",
|
||||
"pgs",
|
||||
"pgssub",
|
||||
"subtitle"
|
||||
],
|
||||
"homepage": "https://github.com/Arcus92/libpgs-js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Arcus92/libpgs-js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Arcus92/libpgs-js/issues"
|
||||
},
|
||||
"main": "dist/libpgs.js",
|
||||
"types": "dist/libpgs.d.ts",
|
||||
"scripts": {
|
||||
"build": "webpack -c webpack.configuration.js",
|
||||
"clear": "rm -rf ./dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/recommended": "^1.0.6",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import {SubtitleRenderer} from "./subtitleRenderer";
|
||||
export {SubtitleRenderer}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader";
|
||||
import {PresentationCompositionSegment} from "./presentationCompositionSegment";
|
||||
import {PaletteDefinitionSegment} from "./paletteDefinitionSegment";
|
||||
import {ObjectDefinitionSegment} from "./objectDefinitionSegment";
|
||||
import {WindowDefinitionSegment} from "./windowDefinitionSegment";
|
||||
import {SegmentType} from "./segmentType";
|
||||
|
||||
export class DisplaySet {
|
||||
public presentationTimestamp: number = 0;
|
||||
public decodingTimestamp: number = 0;
|
||||
public presentationComposition?: PresentationCompositionSegment;
|
||||
public paletteDefinitions: PaletteDefinitionSegment[] = [];
|
||||
public objectDefinitions: ObjectDefinitionSegment[] = [];
|
||||
public windowDefinitions: WindowDefinitionSegment[] = [];
|
||||
|
||||
public read(reader: BigEndianBinaryReader, includeHeader: boolean) {
|
||||
|
||||
// Clear
|
||||
this.presentationTimestamp = 0;
|
||||
this.decodingTimestamp = 0;
|
||||
this.presentationComposition = undefined;
|
||||
this.paletteDefinitions = [];
|
||||
this.objectDefinitions = [];
|
||||
this.windowDefinitions = [];
|
||||
|
||||
while (true)
|
||||
{
|
||||
// The header is included before every segment. Even for the end segment.
|
||||
if (includeHeader)
|
||||
{
|
||||
const magicNumber = reader.readUInt16();
|
||||
if (magicNumber != 0x5047) {
|
||||
throw new Error("Invalid magic number!");
|
||||
}
|
||||
|
||||
this.presentationTimestamp = reader.readUInt32();
|
||||
this.decodingTimestamp = reader.readUInt32();
|
||||
}
|
||||
|
||||
const type = reader.readUInt8();
|
||||
const size = reader.readUInt16()
|
||||
switch (type) {
|
||||
case SegmentType.paletteDefinition:
|
||||
const pds = new PaletteDefinitionSegment();
|
||||
pds.read(reader, size);
|
||||
this.paletteDefinitions.push(pds);
|
||||
break;
|
||||
case SegmentType.objectDefinition:
|
||||
const ods = new ObjectDefinitionSegment();
|
||||
ods.read(reader, size);
|
||||
this.objectDefinitions.push(ods);
|
||||
break;
|
||||
case SegmentType.presentationComposition:
|
||||
const pcs = new PresentationCompositionSegment();
|
||||
pcs.read(reader, size);
|
||||
this.presentationComposition = pcs;
|
||||
break;
|
||||
case SegmentType.windowDefinition:
|
||||
const wds = new WindowDefinitionSegment();
|
||||
wds.read(reader, size);
|
||||
this.windowDefinitions.push(wds);
|
||||
break;
|
||||
case SegmentType.end:
|
||||
return;
|
||||
default:
|
||||
throw new Error(`Unsupported segment type ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader";
|
||||
import {Segment} from "./segment";
|
||||
import {SegmentType} from "./segmentType";
|
||||
|
||||
export class EndSegment implements Segment {
|
||||
|
||||
public get segmentType(): number {
|
||||
return SegmentType.end;
|
||||
}
|
||||
|
||||
public read(reader: BigEndianBinaryReader, length: number): void {
|
||||
// No data to read
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import {Segment} from "./segment";
|
||||
import {SegmentType} from "./segmentType";
|
||||
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader";
|
||||
|
||||
export class ObjectDefinitionSegment implements Segment {
|
||||
public id: number = 0;
|
||||
public versionNumber: number = 0;
|
||||
public lastInSequenceFlag: number = 0;
|
||||
public width: number = 0;
|
||||
public height: number = 0;
|
||||
public dataLength: number = 0;
|
||||
public data?: Uint8Array;
|
||||
|
||||
public get isFirstInSequence(): boolean {
|
||||
return (this.lastInSequenceFlag & 0x80) != 0;
|
||||
}
|
||||
|
||||
public get isLastInSequence(): boolean {
|
||||
return (this.lastInSequenceFlag & 0x40) != 0;
|
||||
}
|
||||
|
||||
public get segmentType(): number {
|
||||
return SegmentType.objectDefinition;
|
||||
}
|
||||
|
||||
public read(reader: BigEndianBinaryReader, length: number): void {
|
||||
this.id = reader.readUInt16();
|
||||
this.versionNumber = reader.readUInt8();
|
||||
this.lastInSequenceFlag = reader.readUInt8();
|
||||
|
||||
// This is the total data length of ALL segments.
|
||||
// We need to add all data segments to encode the image.
|
||||
if (this.isFirstInSequence) {
|
||||
this.dataLength = reader.readUInt24();
|
||||
this.width = reader.readUInt16();
|
||||
this.height = reader.readUInt16();
|
||||
this.data = reader.readBytes(length - 11);
|
||||
} else {
|
||||
this.data = reader.readBytes(length - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {Segment} from "./segment";
|
||||
import {SegmentType} from "./segmentType";
|
||||
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader";
|
||||
|
||||
export class PaletteEntry {
|
||||
public y: number = 0;
|
||||
public cr: number = 0;
|
||||
public cb: number = 0;
|
||||
|
||||
public r: number = 0;
|
||||
public g: number = 0;
|
||||
public b: number = 0;
|
||||
|
||||
public a: number = 0;
|
||||
}
|
||||
|
||||
export class PaletteDefinitionSegment implements Segment {
|
||||
public id: number = 0;
|
||||
public versionNumber: number = 0;
|
||||
public entries: { [key: number]: PaletteEntry } = {};
|
||||
|
||||
public get segmentType(): number {
|
||||
return SegmentType.paletteDefinition;
|
||||
}
|
||||
|
||||
public read(reader: BigEndianBinaryReader, length: number): void {
|
||||
this.id = reader.readUInt8();
|
||||
this.versionNumber = reader.readUInt8();
|
||||
|
||||
const count = (length - 2) / 5;
|
||||
this.entries = {};
|
||||
for (let i = 0; i < count; i++) {
|
||||
const id = reader.readUInt8();
|
||||
const entry = new PaletteEntry();
|
||||
|
||||
// Load the YCrCbA value
|
||||
entry.y = reader.readUInt8();
|
||||
entry.cr = reader.readUInt8();
|
||||
entry.cb = reader.readUInt8();
|
||||
entry.a = reader.readUInt8();
|
||||
|
||||
// Also store the RGBA value
|
||||
const y = entry.y;
|
||||
const cb = entry.cb - 128;
|
||||
const cr = entry.cr - 128;
|
||||
entry.r = PaletteDefinitionSegment.clamp(Math.round(y + 1.40200 * cr), 0, 255);
|
||||
entry.g = PaletteDefinitionSegment.clamp(Math.round(y - 0.34414 * cb - 0.71414 * cr), 0, 255);
|
||||
entry.b = PaletteDefinitionSegment.clamp(Math.round(y + 1.77200 * cb), 0, 255);
|
||||
|
||||
this.entries[id] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
private static clamp(value: number, min: number, max: number): number {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import {Segment} from "./segment";
|
||||
import {SegmentType} from "./segmentType";
|
||||
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader";
|
||||
|
||||
export class CompositionObject {
|
||||
public id: number = 0;
|
||||
public windowId: number = 0;
|
||||
public croppedFlag: number = 0;
|
||||
public horizontalPosition: number = 0;
|
||||
public verticalPosition: number = 0;
|
||||
public croppingHorizontalPosition: number = 0;
|
||||
public croppingVerticalPosition: number = 0;
|
||||
public croppingWidth: number = 0;
|
||||
public croppingHeightPosition: number = 0;
|
||||
public get hasCropping(): boolean {
|
||||
return (this.croppedFlag & 0x80) != 0
|
||||
}
|
||||
}
|
||||
|
||||
export class PresentationCompositionSegment implements Segment {
|
||||
public width: number = 0;
|
||||
public height: number = 0;
|
||||
public frameRate: number = 0;
|
||||
public compositionNumber: number = 0;
|
||||
public compositionState: number = 0;
|
||||
public paletteUpdateFlag: number = 0;
|
||||
public paletteId: number = 0;
|
||||
public compositionObjects: CompositionObject[] = [];
|
||||
|
||||
public get segmentType(): number {
|
||||
return SegmentType.presentationComposition;
|
||||
}
|
||||
|
||||
public read(reader: BigEndianBinaryReader, length: number): void {
|
||||
this.width = reader.readUInt16();
|
||||
this.height = reader.readUInt16();
|
||||
this.frameRate = reader.readUInt8();
|
||||
this.compositionNumber = reader.readUInt16();
|
||||
this.compositionState = reader.readUInt8();
|
||||
this.paletteUpdateFlag = reader.readUInt8();
|
||||
this.paletteId = reader.readUInt8();
|
||||
|
||||
const count = reader.readUInt8();
|
||||
this.compositionObjects = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const compositionObject = new CompositionObject();
|
||||
compositionObject.id = reader.readUInt16();
|
||||
compositionObject.windowId = reader.readUInt8();
|
||||
compositionObject.croppedFlag = reader.readUInt8();
|
||||
compositionObject.horizontalPosition = reader.readUInt16();
|
||||
compositionObject.verticalPosition = reader.readUInt16();
|
||||
if (compositionObject.hasCropping) {
|
||||
compositionObject.croppingHorizontalPosition = reader.readUInt16();
|
||||
compositionObject.croppingVerticalPosition = reader.readUInt16();
|
||||
compositionObject.croppingWidth = reader.readUInt16();
|
||||
compositionObject.croppingHeightPosition = reader.readUInt16();
|
||||
}
|
||||
|
||||
this.compositionObjects.push(compositionObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader";
|
||||
|
||||
export interface Segment {
|
||||
/// Gets the segment type
|
||||
get segmentType() : number;
|
||||
|
||||
/// Reads the segment from the data stream
|
||||
read(reader: BigEndianBinaryReader, length: number): void;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum SegmentType {
|
||||
paletteDefinition = 0x14,
|
||||
objectDefinition = 0x15,
|
||||
presentationComposition = 0x16,
|
||||
windowDefinition = 0x17,
|
||||
end = 0x80,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {Segment} from "./segment";
|
||||
import {SegmentType} from "./segmentType";
|
||||
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader";
|
||||
|
||||
export class WindowDefinition {
|
||||
public id: number = 0;
|
||||
public horizontalPosition: number = 0;
|
||||
public verticalPosition: number = 0;
|
||||
public width: number = 0;
|
||||
public height: number = 0;
|
||||
}
|
||||
|
||||
export class WindowDefinitionSegment implements Segment {
|
||||
public windows: WindowDefinition[] = [];
|
||||
|
||||
public get segmentType(): number {
|
||||
return SegmentType.windowDefinition;
|
||||
}
|
||||
|
||||
public read(reader: BigEndianBinaryReader, length: number): void {
|
||||
const count = reader.readUInt8();
|
||||
this.windows = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const window = new WindowDefinition();
|
||||
window.id = reader.readUInt8();
|
||||
window.horizontalPosition = reader.readUInt16();
|
||||
window.verticalPosition = reader.readUInt16();
|
||||
window.width = reader.readUInt16();
|
||||
window.height = reader.readUInt16();
|
||||
|
||||
this.windows.push(window);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import {DisplaySet} from "./pgs/displaySet";
|
||||
import {BigEndianBinaryReader} from "./utils/bigEndianBinaryReader";
|
||||
import {runLengthEncoding} from "./utils/runLengthEncoding";
|
||||
import {CompositionObject} from "./pgs/presentationCompositionSegment";
|
||||
|
||||
export class SubtitleRenderer {
|
||||
private readonly canvas: HTMLCanvasElement;
|
||||
private readonly context: CanvasRenderingContext2D;
|
||||
private readonly video: HTMLVideoElement;
|
||||
private displaySets: DisplaySet[] = [];
|
||||
private currentIndex: number = -1;
|
||||
|
||||
public constructor(canvas: HTMLCanvasElement, video: HTMLVideoElement) {
|
||||
this.canvas = canvas;
|
||||
this.video = video;
|
||||
this.context = canvas.getContext('2d')!;
|
||||
|
||||
this.video.addEventListener('timeupdate', (e) => this.onTimeUpdate(e));
|
||||
}
|
||||
|
||||
private onTimeUpdate(e: Event) {
|
||||
this.renderDisplaySetAt(this.video.currentTime);
|
||||
}
|
||||
|
||||
public async loadFromUrlAsync(url: string) {
|
||||
const result = await fetch(url);
|
||||
const buffer = await result.arrayBuffer();
|
||||
this.loadFromBuffer(buffer);
|
||||
}
|
||||
|
||||
public loadFromBuffer(buffer: ArrayBuffer) {
|
||||
this.displaySets = [];
|
||||
const reader = new BigEndianBinaryReader(new Uint8Array(buffer));
|
||||
while (reader.position < reader.length) {
|
||||
const displaySet = new DisplaySet();
|
||||
displaySet.read(reader, true);
|
||||
this.displaySets.push(displaySet);
|
||||
}
|
||||
}
|
||||
|
||||
public renderDisplaySetAt(time: number): void {
|
||||
let index = -1;
|
||||
time = time * 1000 * 90;
|
||||
for (const displaySet of this.displaySets) {
|
||||
|
||||
if (displaySet.presentationTimestamp > time) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (this.currentIndex == index) return;
|
||||
this.currentIndex = index;
|
||||
|
||||
console.log('Subtitle update!');
|
||||
|
||||
if (index < 0) return;
|
||||
const displaySet= this.displaySets[index];
|
||||
this.renderDisplaySet(displaySet);
|
||||
}
|
||||
|
||||
private renderDisplaySet(displaySet: DisplaySet) {
|
||||
if (!displaySet.presentationComposition) return;
|
||||
|
||||
this.canvas.width = displaySet.presentationComposition.width;
|
||||
this.canvas.height = displaySet.presentationComposition.height;
|
||||
|
||||
for (const composition of displaySet.presentationComposition.compositionObjects) {
|
||||
this.renderComposition(displaySet, composition);
|
||||
}
|
||||
}
|
||||
|
||||
private renderComposition(displaySet: DisplaySet, composition: CompositionObject): void {
|
||||
if (!displaySet.presentationComposition) return;
|
||||
let window = displaySet.windowDefinitions
|
||||
.flatMap(w => w.windows).find(w => w.id === composition.windowId);
|
||||
if (!window) return;
|
||||
|
||||
const pixelData = this.getPixelDataFromComposition(displaySet, composition);
|
||||
if (pixelData) {
|
||||
this.context.drawImage(pixelData, window.horizontalPosition, window.verticalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private getPixelDataFromComposition(displaySet: DisplaySet, composition: CompositionObject): HTMLCanvasElement | undefined {
|
||||
if (!displaySet.presentationComposition) return undefined;
|
||||
let palette = displaySet.paletteDefinitions
|
||||
.find(p => p.id === displaySet.presentationComposition?.paletteId);
|
||||
if (!palette) return undefined;
|
||||
|
||||
// Collect meta data
|
||||
let width: number = 0;
|
||||
let height: number = 0;
|
||||
let dataLength: number = 0;
|
||||
const dataChunks: Uint8Array[] = [];
|
||||
for (const ods of displaySet.objectDefinitions) {
|
||||
if (ods.id != composition.id) continue;
|
||||
if (ods.isFirstInSequence) {
|
||||
width = ods.width;
|
||||
height = ods.height;
|
||||
}
|
||||
|
||||
if (ods.data) {
|
||||
dataLength += ods.data.length;
|
||||
dataChunks.push(ods.data);
|
||||
}
|
||||
}
|
||||
if (dataLength == 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Merge the data into one binary blob.
|
||||
// TODO: Make this more memory efficient...
|
||||
const runLengthData = new Uint8Array(dataLength);
|
||||
let offset: number = 0;
|
||||
for (const dataChunk of dataChunks) {
|
||||
runLengthData.set(dataChunk, offset);
|
||||
offset += dataChunk.length;
|
||||
}
|
||||
|
||||
// Building the subtitle image.
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d')!;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const imageData = context.createImageData(width, height);
|
||||
const buffer = imageData.data;
|
||||
|
||||
// The pixel data is run-length encoded. The value is the palette entry index.
|
||||
runLengthEncoding(runLengthData, (idx, x, y, value) => {
|
||||
const col = palette?.entries[value];
|
||||
if (!col) return;
|
||||
buffer[idx * 4] = col.r;
|
||||
buffer[idx * 4 + 1] = col.g;
|
||||
buffer[idx * 4 + 2] = col.b;
|
||||
buffer[idx * 4 + 3] = col.a;
|
||||
|
||||
});
|
||||
context.putImageData(imageData, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export class BigEndianBinaryReader {
|
||||
private readonly buffer: Uint8Array;
|
||||
|
||||
private current: number = 0;
|
||||
|
||||
public constructor(buffer: Uint8Array) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public get position(): number {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.buffer.length;
|
||||
}
|
||||
|
||||
public readUInt8(): number {
|
||||
return this.buffer[this.current++];
|
||||
}
|
||||
|
||||
public readUInt16(): number {
|
||||
const b1 = this.readUInt8();
|
||||
const b2 = this.readUInt8();
|
||||
return (b1 << 8) + b2;
|
||||
}
|
||||
|
||||
public readUInt24(): number {
|
||||
const b1 = this.readUInt8();
|
||||
const b2 = this.readUInt8();
|
||||
const b3 = this.readUInt8();
|
||||
return (b1 << 16) + (b2 << 8) + b3;
|
||||
}
|
||||
|
||||
public readUInt32(): number {
|
||||
const b1 = this.readUInt8();
|
||||
const b2 = this.readUInt8();
|
||||
const b3 = this.readUInt8();
|
||||
const b4 = this.readUInt8();
|
||||
return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;
|
||||
}
|
||||
|
||||
public readBytes(count: number): Uint8Array {
|
||||
const data = this.buffer.slice(this.current, this.current + count);
|
||||
this.current += count;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export function runLengthEncoding(data: Uint8Array, setter: (idx: number, x: number, y: number, value: number) => void): void {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let p = 0;
|
||||
let idx = 0;
|
||||
while (p < data.length) {
|
||||
const byte1 = data[p++];
|
||||
// Raw byte
|
||||
if (byte1 != 0x00) {
|
||||
setter(idx++, x++, y, byte1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const byte2 = data[p++];
|
||||
// End of line
|
||||
if (byte2 == 0x00) {
|
||||
x = 0;
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const bit8 = (byte2 & 0b10000000) != 0;
|
||||
const bit7 = (byte2 & 0b01000000) != 0;
|
||||
let num = byte2 & 0b00111111;
|
||||
if (bit7) {
|
||||
num = (num << 8) + data[p++];
|
||||
}
|
||||
const value = bit8 ? data[p++] : 0x00;
|
||||
for (let i = 0; i < num; i++) {
|
||||
setter(idx++, x++, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@tsconfig/recommended/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist",
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: './src/libpgs.ts',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'libpgs.js',
|
||||
library: 'libpgs',
|
||||
libraryTarget: 'umd'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user