UNPKG

ply-js

Version:

A TypeScript port based on python-plyfile for reading and writing .ply files

157 lines (156 loc) 5.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PlyHeaderLines = exports.PlyHeaderParser = void 0; /* * This file is part of python-plyfile (original work Copyright © 2014-2025 Darsh Ranjan * and plyfile authors). TypeScript port © 2025 Gustavo Diogo Silva (GitHub: GustavoDiogo). * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this * program. If not, see <http://www.gnu.org/licenses/>. */ const utils_1 = require("./utils"); const errors_1 = require("./errors"); const property_1 = require("./property"); class PlyHeaderParser { constructor(lines) { this.format = null; // 'ascii' | 'binary_little_endian' | 'binary_big_endian' this.elements = []; this.comments = []; this.objInfo = []; this.lines = 1; this._allowed = ['format', 'comment', 'obj_info']; for (const line of lines) this.consume(line); if (this._allowed.length) this._error('early end-of-file'); } consume(rawLine) { this.lines += 1; if (!rawLine) this._error('early end-of-file'); const line = rawLine.trim(); const parts = line.split(/\s+/, 1); const keyword = parts[0] || ''; if (!this._allowed.includes(keyword)) this._error(`expected one of {${this._allowed.join(', ')}}`); const rest = line.slice(keyword.length).trimStart(); this[`parse_${keyword}`](rest); return this._allowed; } _error(message = 'parse error') { throw new errors_1.PlyHeaderParseError(message, this.lines); } parse_format(data) { const fields = data.trim().split(/\s+/); if (fields.length !== 2) this._error('expected "format {format} 1.0"'); const fmt = fields[0]; if (!(fmt in utils_1.byteOrderMap)) this._error(`don't understand format '${fmt}'`); if (fields[1] !== '1.0') this._error("expected version '1.0'"); this.format = fmt; this._allowed = ['element', 'comment', 'obj_info', 'end_header']; } parse_comment(data) { if (!this.elements.length) this.comments.push(data); else this.elements[this.elements.length - 1].comments.push(data); } parse_obj_info(data) { this.objInfo.push(data); } parse_element(data) { const fields = data.trim().split(/\s+/); if (fields.length !== 2) this._error('expected "element {name} {count}"'); const name = fields[0]; const count = Number(fields[1]); if (!Number.isInteger(count)) this._error('expected integer count'); this.elements.push({ name, comments: [], count, properties: [] }); this._allowed = ['element', 'comment', 'property', 'end_header']; } parse_property(data) { const tgt = this.elements[this.elements.length - 1]; const fields = data.trim().split(/\s+/); if (fields.length < 2) this._error("bad 'property' line"); if (fields[0] === 'list') { if (fields.length !== 4) this._error("expected \"property list {len_type} {val_type} {name}\""); tgt.properties.push(new property_1.PlyListProperty(fields[3], fields[1], fields[2])); } else { if (fields.length !== 2) this._error("expected \"property {type} {name}\""); tgt.properties.push(new property_1.PlyProperty(fields[1], fields[0])); } } parse_end_header(data) { if (data) this._error("unexpected data after 'end_header'"); this._allowed = []; } } exports.PlyHeaderParser = PlyHeaderParser; class PlyHeaderLines { constructor(stream) { this.stream = stream; this.chars = []; this.nl = ''; this.lenNl = 0; this.done = false; this.lineNo = 1; const s = this.decode(this.stream.read(4)); if (s.slice(0, 3) !== 'ply') throw new errors_1.PlyHeaderParseError("expected 'ply'", 1); this.nl = s.slice(3); if (this.nl === '\r') { const c = this.decode(this.stream.read(1)); if (c === '\n') this.nl += c; else this.chars.push(c); } else if (this.nl !== '\n') { throw new errors_1.PlyHeaderParseError("unexpected characters after 'ply'", 1); } this.lenNl = this.nl.length; } decode(x) { if (x === null || x === undefined) return ''; return typeof x === 'string' ? x : x.toString('ascii'); } *[Symbol.iterator]() { while (!this.done) { this.lineNo += 1; while (this.chars.slice(-this.lenNl).join('') !== this.nl) { const next = this.decode(this.stream.read(1)); if (!next) throw new errors_1.PlyHeaderParseError('early end-of-file', this.lineNo); this.chars.push(next); } const line = this.chars.slice(0, -this.lenNl).join(''); this.chars = []; if (line === 'end_header') this.done = true; yield line; } } } exports.PlyHeaderLines = PlyHeaderLines;