//! Behavior tests on small hand-built fonts.
//!
//! The golden fixtures are both non-italic, weight 400, and carry all four name
//! records. They cannot show the italic flag, a different weight, missing name
//! records, ignored records, duplicate records, or an empty name string. These
//! tests build tiny SFNT fonts that isolate each of those behaviors.
//!
//! Each expected EOT buffer is a constant byte string. The values follow from
//! the conversion rules in the crate docs: an 82-byte little-endian prefix, four
//! length-prefixed UTF-16LE name strings in the order family, subfamily,
//! version, full, a two-byte pad, then the font verbatim.

use ttf2eot::ttf2eot;

// --- SFNT builders ---------------------------------------------------------

/// One UTF-16BE name string in code units. ASCII input keeps the high byte zero.
fn utf16be(s: &str) -> Vec<u8> {
    let mut out = Vec::with_capacity(s.len() * 2);
    for c in s.chars() {
        let u = c as u16;
        out.push((u >> 8) as u8);
        out.push((u & 0xff) as u8);
    }
    out
}

/// One name record for the builder.
struct Rec {
    platform: u16,
    encoding: u16,
    language: u16,
    name_id: u16,
    string: Vec<u8>,
}

fn rec(platform: u16, encoding: u16, language: u16, name_id: u16, s: &str) -> Rec {
    Rec {
        platform,
        encoding,
        language,
        name_id,
        string: utf16be(s),
    }
}

/// A matching Microsoft/Unicode-BMP/English-US record.
fn mrec(name_id: u16, s: &str) -> Rec {
    rec(3, 1, 0x0409, name_id, s)
}

fn push_be16(buf: &mut Vec<u8>, v: u16) {
    buf.extend_from_slice(&v.to_be_bytes());
}

fn push_be32(buf: &mut Vec<u8>, v: u32) {
    buf.extend_from_slice(&v.to_be_bytes());
}

/// Build an OS/2 table. Version, weight, and fsSelection are caller supplied.
/// PANOSE, unicode ranges, and codepage ranges hold fixed marker bytes so the
/// expected output can pin them.
fn build_os2(version: u16, weight: u16, fs_selection: u16) -> Vec<u8> {
    let mut t = vec![0u8; 96];
    t[0..2].copy_from_slice(&version.to_be_bytes());
    t[4..6].copy_from_slice(&weight.to_be_bytes());
    for k in 0..10u8 {
        t[32 + k as usize] = 0x10 + k;
    }
    t[42..46].copy_from_slice(&0xAABBCCDDu32.to_be_bytes());
    t[46..50].copy_from_slice(&0x11223344u32.to_be_bytes());
    t[50..54].copy_from_slice(&0x55667788u32.to_be_bytes());
    t[54..58].copy_from_slice(&0x99AABBCCu32.to_be_bytes());
    t[62..64].copy_from_slice(&fs_selection.to_be_bytes());
    t[78..82].copy_from_slice(&0xDEADBEEFu32.to_be_bytes());
    t[82..86].copy_from_slice(&0xFEEDFACEu32.to_be_bytes());
    t
}

/// Build a head table with a marker checkSumAdjustment at offset 8.
fn build_head() -> Vec<u8> {
    let mut t = vec![0u8; 54];
    t[8..12].copy_from_slice(&0xCAFEBABEu32.to_be_bytes());
    t
}

/// Build a name table from records. The string storage follows the record
/// array and each record's offset points into it.
fn build_name(records: &[Rec]) -> Vec<u8> {
    let count = records.len() as u16;
    let string_offset = 6 + records.len() * 12;

    let mut header = Vec::new();
    push_be16(&mut header, 0); // format
    push_be16(&mut header, count);
    push_be16(&mut header, string_offset as u16);

    let mut recs = Vec::new();
    let mut store = Vec::new();
    let mut cursor: u16 = 0;
    for r in records {
        push_be16(&mut recs, r.platform);
        push_be16(&mut recs, r.encoding);
        push_be16(&mut recs, r.language);
        push_be16(&mut recs, r.name_id);
        push_be16(&mut recs, r.string.len() as u16);
        push_be16(&mut recs, cursor);
        store.extend_from_slice(&r.string);
        cursor += r.string.len() as u16;
    }

    let mut out = header;
    out.extend_from_slice(&recs);
    out.extend_from_slice(&store);
    out
}

/// Assemble a font from `(tag, data)` tables. Tables sit back to back after the
/// 12-byte header and the directory.
fn assemble(tables: &[(&[u8; 4], Vec<u8>)]) -> Vec<u8> {
    let n = tables.len();
    let mut header = Vec::new();
    push_be32(&mut header, 0x0001_0000); // sfntVersion
    push_be16(&mut header, n as u16); // numTables
    push_be16(&mut header, 0);
    push_be16(&mut header, 0);
    push_be16(&mut header, 0);

    let mut dir = Vec::new();
    let mut body = Vec::new();
    let mut offset = (12 + n * 16) as u32;
    for (tag, data) in tables {
        dir.extend_from_slice(*tag);
        push_be32(&mut dir, 0); // checksum, ignored
        push_be32(&mut dir, offset);
        push_be32(&mut dir, data.len() as u32);
        body.extend_from_slice(data);
        offset += data.len() as u32;
    }

    let mut out = header;
    out.extend_from_slice(&dir);
    out.extend_from_slice(&body);
    out
}

/// A standard three-table font with the given OS/2 and name records.
fn font(os2: Vec<u8>, name: Vec<u8>) -> Vec<u8> {
    assemble(&[(b"OS/2", os2), (b"head", build_head()), (b"name", name)])
}

// --- base64 (for the expected EOT oracle strings) --------------------------

fn b64(s: &str) -> Vec<u8> {
    fn val(c: u8) -> i32 {
        match c {
            b'A'..=b'Z' => (c - b'A') as i32,
            b'a'..=b'z' => (c - b'a' + 26) as i32,
            b'0'..=b'9' => (c - b'0' + 52) as i32,
            b'+' => 62,
            b'/' => 63,
            _ => -1,
        }
    }
    let mut out = Vec::new();
    let mut buf = 0i32;
    let mut bits = 0;
    for &c in s.as_bytes() {
        if c == b'=' {
            break;
        }
        let v = val(c);
        if v < 0 {
            continue;
        }
        buf = (buf << 6) | v;
        bits += 6;
        if bits >= 8 {
            bits -= 8;
            out.push((buf >> bits) as u8);
        }
    }
    out
}

/// Compare and report the first differing offset.
fn assert_bytes_eq(got: &[u8], want: &[u8], label: &str) {
    assert_eq!(
        got.len(),
        want.len(),
        "{label}: length got={} want={}",
        got.len(),
        want.len()
    );
    if let Some(i) = got.iter().zip(want).position(|(a, b)| a != b) {
        panic!(
            "{label}: first diff at offset {i}: got={:#04x} want={:#04x}",
            got[i], want[i]
        );
    }
}

fn le_u32(b: &[u8], o: usize) -> u32 {
    u32::from_le_bytes(b[o..o + 4].try_into().unwrap())
}

fn le_u16(b: &[u8], o: usize) -> u16 {
    u16::from_le_bytes(b[o..o + 2].try_into().unwrap())
}

// --- expected EOT oracle strings -------------------------------------------
//
// These constants were produced by running the conversion on the matching font
// built above. Each one pins the whole output, so a regression anywhere in the
// header, the name strings, the pad, or the tail shows up as a byte diff.

const EOT_A_ITALIC_W700: &str = "xAEAADQBAAABAAIAAAAAABAREhMUFRYXGBkBAbwCAAAAAExQ3cy7qkQzIhGId2ZVzLuqme++rd7O+u3+vrr+ygAAAAAAAAAAAAAAAAAAAAAAAAYARgBhAG0AAAAMAEkAdABhAGwAaQBjAAAABgAxAC4ANQAAABQARgBhAG0AIABJAHQAYQBsAGkAYwAAAAAAAAEAAAADAAAAAAAAT1MvMgAAAAAAAAA8AAAAYGhlYWQAAAAAAAAAnAAAADZuYW1lAAAAAAAAANIAAABiAAEAAAK8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQERITFBUWFxgZqrvM3REiM0RVZneImaq7zAAAAAAAAQAAAAAAAAAAAAAAAAAA3q2+7/7t+s4AAAAAAAAAAAAAAAAAAAAAAADK/rq+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAA2AAMAAQQJAAEABgAAAAMAAQQJAAIADAAGAAMAAQQJAAQAFAASAAMAAQQJAAUABgAmAEYAYQBtAEkAdABhAGwAaQBjAEYAYQBtACAASQB0AGEAbABpAGMAMQAuADU=";

const EOT_B_PARTIAL: &str = "kgEAACgBAAABAAIAAAAAABAREhMUFRYXGBkBAJABAAAAAExQ3cy7qkQzIhGId2ZVzLuqme++rd7O+u3+vrr+ygAAAAAAAAAAAAAAAAAAAAAAAAgATwBuAGwAeQAAAAAEAHYAMgAAAAAAAAABAAAAAwAAAAAAAE9TLzIAAAAAAAAAPAAAAGBoZWFkAAAAAAAAAJwAAAA2bmFtZQAAAAAAAADSAAAAVgABAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBESExQVFhcYGaq7zN0RIjNEVWZ3iJmqu8wAAAAAAAAAAAAAAAAAAAAAAAAAAN6tvu/+7frOAAAAAAAAAAAAAAAAAAAAAAAAyv66vgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQANgADAAEECQABAAgAAAADAAEECQAFAAQACAABAAAAAAABAA4ADAADAAEECQADAAYAGgBPAG4AbAB5AHYAMgBJAEcATgBPAFIARQBEAHUAaQBk";

const EOT_C_V0_W300: &str = "pAEAACQBAAABAAIAAAAAABAREhMUFRYXGBkBACwBAAAAAExQ3cy7qkQzIhGId2ZVzLuqmQAAAAAAAAAAvrr+ygAAAAAAAAAAAAAAAAAAAAAAAAgAWgBlAHIAbwAAAAYAUgBlAGcAAAAGADAALgAxAAAACABaAGUAcgBvAAAAAAAAAQAAAAMAAAAAAABPUy8yAAAAAAAAADwAAABgaGVhZAAAAAAAAACcAAAANm5hbWUAAAAAAAAA0gAAAFIAAAAAASwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAREhMUFRYXGBmqu8zdESIzRFVmd4iZqrvMAAAAAAAAAAAAAAAAAAAAAAAAAADerb7v/u36zgAAAAAAAAAAAAAAAAAAAAAAAMr+ur4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEADYAAwABBAkAAQAIAAAAAwABBAkAAgAGAAgAAwABBAkABAAIAA4AAwABBAkABQAGABYAWgBlAHIAbwBSAGUAZwBaAGUAcgBvADAALgAx";

const EOT_D_DUP_MASK: &str = "qgEAADIBAAABAAIAAAAAABAREhMUFRYXGBkBAZABAAAAAExQ3cy7qkQzIhGId2ZVzLuqme++rd7O+u3+vrr+ygAAAAAAAAAAAAAAAAAAAAAAAAgATABhAHMAdAAAAAIAUgAAAAIAOQAAAAgATABhAHMAdAAAAAAAAAEAAAADAAAAAAAAT1MvMgAAAAAAAAA8AAAAYGhlYWQAAAAAAAAAnAAAADZuYW1lAAAAAAAAANIAAABgAAEAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQERITFBUWFxgZqrvM3REiM0RVZneImaq7zAAAAAAAIQAAAAAAAAAAAAAAAAAA3q2+7/7t+s4AAAAAAAAAAAAAAAAAAAAAAADK/rq+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQBCAAMAAQQJAAEACgAAAAMAAQQJAAEACAAKAAMAAQQJAAIAAgASAAMAAQQJAAQACAAUAAMAAQQJAAUAAgAcAEYAaQByAHMAdABMAGEAcwB0AFIATABhAHMAdAA5";

const EOT_E_EMPTY: &str = "eAEAAA4BAAABAAIAAAAAABAREhMUFRYXGBkBAJABAAAAAExQ3cy7qkQzIhGId2ZVzLuqme++rd7O+u3+vrr+ygAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAFIAAAACADEAAAACAFgAAAAAAAABAAAAAwAAAAAAAE9TLzIAAAAAAAAAPAAAAGBoZWFkAAAAAAAAAJwAAAA2bmFtZQAAAAAAAADSAAAAPAABAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBESExQVFhcYGaq7zN0RIjNEVWZ3iJmqu8wAAAAAAAAAAAAAAAAAAAAAAAAAAN6tvu/+7frOAAAAAAAAAAAAAAAAAAAAAAAAyv66vgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQANgADAAEECQABAAAAAAADAAEECQACAAIAAAADAAEECQAEAAIAAgADAAEECQAFAAIABABSAFgAMQ==";

// --- tests -----------------------------------------------------------------

/// Italic font with a non-default weight. The fixtures cannot show either.
/// fsSelection bit 0 set yields ITALIC = 1. usWeightClass 700 yields WEIGHT 700
/// as a little-endian u32.
#[test]
fn italic_and_weight() {
    let os2 = build_os2(1, 700, 0x0001);
    let name = build_name(&[
        mrec(1, "Fam"),
        mrec(2, "Italic"),
        mrec(4, "Fam Italic"),
        mrec(5, "1.5"),
    ]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();

    assert_eq!(out[27], 1, "italic bit set");
    assert_eq!(le_u32(&out, 28), 700, "weight 700");
    assert_bytes_eq(&out, &b64(EOT_A_ITALIC_W700), "A_italic_w700");
}

/// fsSelection carries bits beyond bit 0. Only bit 0 reaches the ITALIC byte.
/// 0x0021 has bit 0 and bit 5 set. The output keeps 1.
#[test]
fn italic_mask_keeps_low_bit_only() {
    let os2 = build_os2(1, 400, 0x0021);
    let name = build_name(&[mrec(1, "X")]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();
    assert_eq!(out[27], 1, "0x0021 & 0x01 == 1");

    // A clear bit 0 with other bits set yields 0.
    let os2 = build_os2(1, 400, 0x0040);
    let name = build_name(&[mrec(1, "X")]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();
    assert_eq!(out[27], 0, "0x0040 & 0x01 == 0");
}

/// Only family and version records are present. Subfamily and full keep their
/// one-byte default. A non-matching record (platform 1) and a matching record
/// with an uncaptured nameID (3) are both ignored.
#[test]
fn partial_and_ignored_records() {
    let os2 = build_os2(1, 400, 0x0000);
    let name = build_name(&[
        mrec(1, "Only"),
        mrec(5, "v2"),
        rec(1, 0, 0x0000, 1, "IGNORED"),
        mrec(3, "uid"),
    ]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();

    // family region at offset 82: 8 bytes for "Only", framed as len + data + term.
    assert_eq!(le_u16(&out, 82), 8, "family byte length");
    // subfamily directly after family (82 + 2 + 8 + 2 = 94) is the 1-byte default.
    assert_eq!(out[94], 0, "subfamily default byte");
    assert_bytes_eq(&out, &b64(EOT_B_PARTIAL), "B_partial_names");
}

/// OS/2 version 0 with weight 300. The codepage range stays zero because the
/// version gate skips it. The unicode range above it is still written, and the
/// codepage bytes inside the OS/2 table are never read.
#[test]
fn os2_version_0_skips_codepage() {
    let os2 = build_os2(0, 300, 0x0000);
    let name = build_name(&[
        mrec(1, "Zero"),
        mrec(2, "Reg"),
        mrec(4, "Zero"),
        mrec(5, "0.1"),
    ]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();

    assert_eq!(le_u32(&out, 28), 300, "weight 300");
    assert_eq!(le_u32(&out, 52), 0, "codepage range 1 stays zero");
    assert_eq!(le_u32(&out, 56), 0, "codepage range 2 stays zero");
    assert_eq!(le_u32(&out, 36), 0xAABBCCDD, "unicode range still written");
    assert_bytes_eq(&out, &b64(EOT_C_V0_W300), "C_os2v0_w300");
}

/// Two records share nameID 1. The last one wins. fsSelection 0x0021 also keeps
/// only the low italic bit.
#[test]
fn duplicate_nameid_last_wins() {
    let os2 = build_os2(1, 400, 0x0021);
    let name = build_name(&[
        mrec(1, "First"),
        mrec(1, "Last"),
        mrec(2, "R"),
        mrec(4, "Last"),
        mrec(5, "9"),
    ]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();

    // "Last" is 4 chars, 8 bytes. "First" would be 10. The length proves the win.
    assert_eq!(le_u16(&out, 82), 8, "family is Last, not First");
    assert_eq!(out[27], 1, "italic bit kept from 0x0021");
    assert_bytes_eq(&out, &b64(EOT_D_DUP_MASK), "D_dup_nameid_last_wins");
}

/// A matching record with a zero-length string. The encoder produces a four-byte
/// region: a zero length, no data, and the terminator. This is not the missing
/// record default, which is a single zero byte.
#[test]
fn empty_name_string() {
    let os2 = build_os2(1, 400, 0x0000);
    let name = build_name(&[mrec(1, ""), mrec(2, "R"), mrec(4, "X"), mrec(5, "1")]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();

    // family region: len 0, then the 2-byte terminator. Four zero bytes total.
    assert_eq!(le_u16(&out, 82), 0, "empty family length");
    assert_eq!(&out[82..86], &[0, 0, 0, 0], "len + terminator, no data");
    assert_bytes_eq(&out, &b64(EOT_E_EMPTY), "E_empty_name");
}

/// A name record with no matching platform/encoding/language is skipped, so a
/// font whose only family record is non-matching falls back to the one-byte
/// default for that string.
#[test]
fn non_matching_record_falls_back_to_default() {
    let os2 = build_os2(1, 400, 0x0000);
    // Family record is platform 1 (Mac), which the filter rejects.
    let name = build_name(&[rec(1, 0, 0x0000, 1, "MacFamily"), mrec(2, "Reg")]);
    let ttf = font(os2, name);
    let out = ttf2eot(&ttf).unwrap();

    // family is the 1-byte default, so subfamily "Reg" starts right after it.
    assert_eq!(out[82], 0, "family default byte");
    // subfamily length prefix sits at offset 83.
    assert_eq!(le_u16(&out, 83), 6, "subfamily Reg byte length");
}

/// Build a font whose family record length runs past the end of the name table.
fn font_with_overrun_name_length() -> Vec<u8> {
    let os2 = build_os2(1, 400, 0x0000);
    // One record. Header 6 + record 12 = 18 = stringOffset. Store holds 4 bytes
    // ("Hi" in UTF-16BE) but the record claims length 100.
    let mut header = Vec::new();
    push_be16(&mut header, 0); // format
    push_be16(&mut header, 1); // count
    push_be16(&mut header, 18); // stringOffset
    let mut recs = Vec::new();
    push_be16(&mut recs, 3); // platform
    push_be16(&mut recs, 1); // encoding
    push_be16(&mut recs, 0x0409); // language
    push_be16(&mut recs, 1); // nameID family
    push_be16(&mut recs, 100); // length overruns
    push_be16(&mut recs, 0); // offset
    let store = utf16be("Hi");
    let mut name = header;
    name.extend_from_slice(&recs);
    name.extend_from_slice(&store);
    font(os2, name)
}

/// A name record whose length field overruns the name table.
///
/// The string slice clamps to the table end and the conversion keeps going.
/// "Hi" is the only stored data, so the family string clamps to four bytes and
/// the conversion still returns a buffer. The expected output is the constant
/// below.
#[test]
fn name_length_overrun_clamps() {
    const EOT_F_OVERRUN: &str = "RwEAAOgAAAABAAIAAAAAABAREhMUFRYXGBkBAJABAAAAAExQ3cy7qkQzIhGId2ZVzLuqme++rd7O+u3+vrr+ygAAAAAAAAAAAAAAAAAAAAAAAAQASABpAAAAAAAAAAAAAQAAAAMAAAAAAABPUy8yAAAAAAAAADwAAABgaGVhZAAAAAAAAACcAAAANm5hbWUAAAAAAAAA0gAAABYAAQAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAREhMUFRYXGBmqu8zdESIzRFVmd4iZqrvMAAAAAAAAAAAAAAAAAAAAAAAAAADerb7v/u36zgAAAAAAAAAAAAAAAAAAAAAAAMr+ur4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABABIAAwABBAkAAQBkAAAASABp";
    let ttf = font_with_overrun_name_length();
    let out = ttf2eot(&ttf).expect("overrun should clamp and succeed");
    assert_bytes_eq(&out, &b64(EOT_F_OVERRUN), "F_overrun_clamp");
}

/// The walk breaks once all three required tables are seen. A second OS/2 table
/// placed after the first OS/2, head, and name is never read. The first OS/2
/// holds weight 400 and a clear italic bit. The second holds weight 900 and a
/// set italic bit. The output keeps the first values.
#[test]
fn early_break_ignores_later_duplicate_table() {
    const EOT_G_EARLY_BREAK: &str = "uwEAAFoBAAABAAIAAAAAABAREhMUFRYXGBkBAJABAAAAAExQ3cy7qkQzIhGId2ZVzLuqme++rd7O+u3+vrr+ygAAAAAAAAAAAAAAAAAAAAAAAAYARAB1AHAAAAAAAAAAAAABAAAABAAAAAAAAE9TLzIAAAAAAAAATAAAAGBoZWFkAAAAAAAAAKwAAAA2bmFtZQAAAAAAAADiAAAAGE9TLzIAAAAAAAAA+gAAAGAAAQAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAREhMUFRYXGBmqu8zdESIzRFVmd4iZqrvMAAAAAAAAAAAAAAAAAAAAAAAAAADerb7v/u36zgAAAAAAAAAAAAAAAAAAAAAAAMr+ur4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABABIAAwABBAkAAQAGAAAARAB1AHAAAQAAA4QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAREhMUFRYXGBmqu8zdESIzRFVmd4iZqrvMAAAAAAABAAAAAAAAAAAAAAAAAADerb7v/u36zgAAAAAAAAAAAAA=";

    let os2_first = build_os2(1, 400, 0x0000);
    let os2_second = build_os2(1, 900, 0x0001);
    let name = build_name(&[mrec(1, "Dup")]);
    let ttf = assemble(&[
        (b"OS/2", os2_first),
        (b"head", build_head()),
        (b"name", name),
        (b"OS/2", os2_second),
    ]);
    let out = ttf2eot(&ttf).unwrap();

    assert_eq!(le_u32(&out, 28), 400, "weight from first OS/2");
    assert_eq!(out[27], 0, "italic from first OS/2");
    assert_bytes_eq(&out, &b64(EOT_G_EARLY_BREAK), "G_early_break");
}
