HP-GL: a .plt file opens when its first instruction is SI, DI or another known one

A file read as HP-GL by its extension no longer has to start with an instruction plot files usually start with. Any instruction the reader knows, or one of another set that a semicolon ends, may come first; the rest of the check still refuses files that are not HP-GL. Telling HP-GL apart by content alone keeps the stricter check.
This commit is contained in:
dotCooCoo
2026-11-11 17:27:49 -08:00
parent 74ea25cd6c
commit 64988aaa49
3 changed files with 34 additions and 3 deletions
+11 -3
View File
@@ -66,6 +66,13 @@ const FIRST: &[[u8; 2]] = &[
/// WD, DT, SM and PE. An instruction the reader doesn't know counts when its numbers end with a
/// semicolon.
pub fn is_hpgl(bytes: &[u8]) -> bool {
holds_instructions(bytes, true)
}
/// Does `bytes` hold HP-GL instructions as [`is_hpgl`] checks? When `strict` is false, any of them
/// may come first: a file picked by its extension may start with one a plot file seldom starts
/// with (`SI`, `DI`).
fn holds_instructions(bytes: &[u8], strict: bool) -> bool {
let mut lex = Lexer::new(bytes);
let mut terminator = lex::ETX;
let mut first = 0;
@@ -77,7 +84,7 @@ pub fn is_hpgl(bytes: &[u8]) -> bool {
if n == 0 {
first = lex.pos();
}
if n == 0 && !FIRST.contains(&op) {
if strict && n == 0 && !FIRST.contains(&op) {
return false;
}
let known = PLAYED.contains(&op) || IGNORED.contains(&op);
@@ -110,9 +117,10 @@ pub fn is_hpgl(bytes: &[u8]) -> bool {
true
}
/// Read an HP-GL file.
/// Read an HP-GL file. It may start with any instruction the reader knows, or one of another set
/// that a semicolon ends; the rest of [`is_hpgl`]'s check applies.
pub fn import(bytes: &[u8]) -> Result<Imported, String> {
if !is_hpgl(bytes) {
if !holds_instructions(bytes, false) {
return Err("not an HP-GL file".into());
}
let mut lex = Lexer::new(bytes);
+13
View File
@@ -440,6 +440,19 @@ fn only_plot_files_are_taken_for_hpgl() {
assert!(is_hpgl(b"IN;SP1;PU;PD;PU;PD;PU;PD;hello world"));
}
/// A file read as HP-GL (picked by its extension) may start with any instruction the reader knows,
/// or one of another set that a semicolon ends; its content alone doesn't make it HP-GL.
#[test]
fn a_plot_file_may_start_with_any_instruction() {
for f in [&b"SI0.2,0.3;SP1;PA0,0;PD1016,0;"[..], b"DI1,0;SP1;PA0,0;PD1016,0;", b"GC5;SP1;PA0,0;PD1016,0;"] {
assert!(!is_hpgl(f), "{:?}", String::from_utf8_lossy(f));
assert_eq!(leaves(&plot_bytes(f).document).len(), 1, "{:?}", String::from_utf8_lossy(f));
}
for f in [&b"GC5"[..], b"SIhello world", b"set terminal png\n"] {
assert_eq!(import(f).err().as_deref(), Some("not an HP-GL file"), "{:?}", String::from_utf8_lossy(f));
}
}
#[test]
fn escapes_that_carry_values_or_data_are_skipped_whole() {
// A reset leaves HP-GL until a language switch; symbol-set escapes carry a value and a letter.
@@ -79,6 +79,16 @@ fn files_that_are_not_hpgl_are_refused() {
assert_eq!(text["format"], "txt", "{text}");
}
/// A plot file named as one opens when its first instruction is one a plot file seldom starts with;
/// under another name it isn't taken for HP-GL.
#[test]
fn a_plot_file_by_its_name_may_start_with_any_instruction() {
let plot = b"SI0.2,0.3;SP1;PA0,0;PD1016,0;";
let r = Session::new().execute("document.open", &json!({"name": "part.plt", "dataBase64": b64(plot)})).unwrap();
assert_eq!(r["format"].as_str(), Some("hpgl"), "{r}");
assert!(detect("drawing.prn", plot).is_none_or(|f| f.id != "hpgl"));
}
#[test]
fn the_dialogs_and_the_format_list_name_hpgl() {
let exts = ["plt", "hpgl", "hgl", "hpg"];