///|
fn img_read_u32_be(bytes : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 4 > bytes.length() {
return None
}
let b0 = bytes[offset].to_int()
let b1 = bytes[offset + 1].to_int()
let b2 = bytes[offset + 2].to_int()
let b3 = bytes[offset + 3].to_int()
Some((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
}
///|
fn img_read_u32_le(bytes : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 4 > bytes.length() {
return None
}
let b0 = bytes[offset].to_int()
let b1 = bytes[offset + 1].to_int()
let b2 = bytes[offset + 2].to_int()
let b3 = bytes[offset + 3].to_int()
Some(b0 | (b1 << 8) | (b2 << 16) | (b3 << 24))
}
///|
fn img_read_u16_be(bytes : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 2 > bytes.length() {
return None
}
let b0 = bytes[offset].to_int()
let b1 = bytes[offset + 1].to_int()
Some((b0 << 8) | b1)
}
///|
fn img_read_u16_le(bytes : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 2 > bytes.length() {
return None
}
let b0 = bytes[offset].to_int()
let b1 = bytes[offset + 1].to_int()
Some(b0 | (b1 << 8))
}
///|
fn png_dimensions(bytes : BytesView) -> (Int, Int)? {
// PNG signature: 89 50 4E 47 0D 0A 1A 0A
if bytes.length() < 24 {
return None
}
let sig : Array[Int] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
for i in 0..<8 {
if bytes[i].to_int() != sig[i] {
return None
}
}
// First chunk should be IHDR; width/height are big-endian at offset 16/20.
let is_ihdr = bytes[12].to_int() == 0x49 &&
bytes[13].to_int() == 0x48 &&
bytes[14].to_int() == 0x44 &&
bytes[15].to_int() == 0x52
if !is_ihdr {
return None
}
let w_opt = img_read_u32_be(bytes, 16)
let h_opt = img_read_u32_be(bytes, 20)
match (w_opt, h_opt) {
(Some(w), Some(h)) if w > 0 && h > 0 => Some((w, h))
_ => None
}
}
///|
fn gif_dimensions(bytes : BytesView) -> (Int, Int)? {
if bytes.length() < 10 {
return None
}
let is_gif = bytes[0].to_int() == 0x47 &&
bytes[1].to_int() == 0x49 &&
bytes[2].to_int() == 0x46 &&
bytes[3].to_int() == 0x38 &&
(bytes[4].to_int() == 0x37 || bytes[4].to_int() == 0x39) &&
bytes[5].to_int() == 0x61
if !is_gif {
return None
}
let w_opt = img_read_u16_le(bytes, 6)
let h_opt = img_read_u16_le(bytes, 8)
match (w_opt, h_opt) {
(Some(w), Some(h)) if w > 0 && h > 0 => Some((w, h))
_ => None
}
}
///|
fn jpeg_dimensions(bytes : BytesView) -> (Int, Int)? {
if bytes.length() < 4 {
return None
}
if !(bytes[0].to_int() == 0xFF && bytes[1].to_int() == 0xD8) {
return None
}
let mut i = 2
// Scan segments until we find a SOF marker with dimensions.
while i + 4 <= bytes.length() {
// Find the next marker (0xFF ...), skipping fill bytes.
if bytes[i].to_int() != 0xFF {
i = i + 1
continue
}
while i < bytes.length() && bytes[i].to_int() == 0xFF {
i = i + 1
}
if i >= bytes.length() {
return None
}
let marker = bytes[i].to_int()
i = i + 1
// Standalone markers.
if marker == 0xD9 || marker == 0xDA {
return None
}
let len = match img_read_u16_be(bytes, i) {
Some(v) => v
None => return None
}
if len < 2 {
return None
}
// Segment payload starts after length.
let seg_start = i + 2
let seg_end = i + len
if seg_end > bytes.length() {
return None
}
let is_sof = (marker >= 0xC0 && marker <= 0xC3) ||
(marker >= 0xC5 && marker <= 0xC7) ||
(marker >= 0xC9 && marker <= 0xCB) ||
(marker >= 0xCD && marker <= 0xCF)
if is_sof {
// SOF payload: [precision:1][height:2][width:2]...
if seg_start + 5 > seg_end {
return None
}
let h = match img_read_u16_be(bytes, seg_start + 1) {
Some(v) => v
None => return None
}
let w = match img_read_u16_be(bytes, seg_start + 3) {
Some(v) => v
None => return None
}
if w > 0 && h > 0 {
return Some((w, h))
}
return None
}
i = seg_end
}
None
}
///|
fn bmp_dimensions(bytes : BytesView) -> (Int, Int)? {
// BMP file header is 14 bytes, followed by a DIB header.
if bytes.length() < 26 {
return None
}
if !(bytes[0].to_int() == 0x42 && bytes[1].to_int() == 0x4D) {
return None
}
let dib_size_opt = img_read_u32_le(bytes, 14)
let dib_size = match dib_size_opt {
Some(v) => v
None => return None
}
if dib_size == 12 {
// BITMAPCOREHEADER: width/height are 16-bit at offsets 18/20.
let w_opt = img_read_u16_le(bytes, 18)
let h_opt = img_read_u16_le(bytes, 20)
match (w_opt, h_opt) {
(Some(w), Some(h)) if w > 0 && h > 0 => Some((w, h))
_ => None
}
} else if dib_size >= 40 {
// BITMAPINFOHEADER and later: width/height are signed 32-bit at offsets 18/22.
let w_opt = img_read_u32_le(bytes, 18)
let h_opt = img_read_u32_le(bytes, 22)
match (w_opt, h_opt) {
(Some(w_raw), Some(h_raw)) => {
let w = w_raw
let h = if h_raw < 0 { -h_raw } else { h_raw }
if w > 0 && h > 0 {
Some((w, h))
} else {
None
}
}
_ => None
}
} else {
None
}
}
///|
fn ico_dimensions(bytes : BytesView) -> (Int, Int)? {
// ICONDIR (6 bytes) + at least one ICONDIRENTRY (16 bytes).
if bytes.length() < 22 {
return None
}
let reserved = img_read_u16_le(bytes, 0)
let typ = img_read_u16_le(bytes, 2)
let count = img_read_u16_le(bytes, 4)
match (reserved, typ, count) {
(Some(0), Some(1), Some(c)) if c > 0 => {
let w8 = bytes[6].to_int()
let h8 = bytes[7].to_int()
let w = if w8 == 0 { 256 } else { w8 }
let h = if h8 == 0 { 256 } else { h8 }
if w > 0 && h > 0 {
Some((w, h))
} else {
None
}
}
_ => None
}
}
///|
fn tiff_dimensions(bytes : BytesView) -> (Int, Int)? {
if bytes.length() < 8 {
return None
}
let le = bytes[0].to_int() == 0x49 && bytes[1].to_int() == 0x49
let be = bytes[0].to_int() == 0x4D && bytes[1].to_int() == 0x4D
if !le && !be {
return None
}
let read_u16 = fn(bytes : BytesView, offset : Int) -> Int? {
if le {
img_read_u16_le(bytes, offset)
} else {
img_read_u16_be(bytes, offset)
}
}
let read_u32 = fn(bytes : BytesView, offset : Int) -> Int? {
if le {
img_read_u32_le(bytes, offset)
} else {
img_read_u32_be(bytes, offset)
}
}
let magic = read_u16(bytes, 2)
match magic {
Some(42) => ()
_ => return None
}
let ifd_offset = match read_u32(bytes, 4) {
Some(v) => v
None => return None
}
if ifd_offset < 0 || ifd_offset + 2 > bytes.length() {
return None
}
let entry_count = match read_u16(bytes, ifd_offset) {
Some(v) => v
None => return None
}
let mut width : Int? = None
let mut height : Int? = None
let mut entry_off = ifd_offset + 2
for _ in 0.. bytes.length() {
return None
}
let tag = match read_u16(bytes, entry_off) {
Some(v) => v
None => return None
}
let field_type = match read_u16(bytes, entry_off + 2) {
Some(v) => v
None => return None
}
let count = match read_u32(bytes, entry_off + 4) {
Some(v) => v
None => return None
}
let value_off = match read_u32(bytes, entry_off + 8) {
Some(v) => v
None => return None
}
if (tag == 256 || tag == 257) && count == 1 {
let value = match field_type {
3 => if le { value_off & 0xFFFF } else { (value_off >> 16) & 0xFFFF }
4 => value_off
_ => 0
}
if value > 0 {
if tag == 256 {
width = Some(value)
} else {
height = Some(value)
}
}
}
entry_off = entry_off + 12
}
match (width, height) {
(Some(w), Some(h)) if w > 0 && h > 0 => Some((w, h))
_ => None
}
}
///|
fn svg_parse_length_px(text : StringView) -> Double? {
let raw = text.to_owned().trim().to_lower()
if raw == "" {
return None
}
let mut end = 0
for i in 0..= '0' && ch <= '9') ||
ch == '.' ||
ch == '-' ||
ch == '+' ||
ch == 'e' ||
ch == 'E'
if ok {
end = i + 1
} else {
break
}
}
if end == 0 {
return None
}
let number_text = raw[:end]
let value = @string.parse_double(number_text) catch { _ => return None }
let unit_raw = raw[end:]
let unit = unit_raw.trim()
match unit {
"" | "px" => Some(value)
"in" => Some(value * 96.0)
"cm" => Some(value * (96.0 / 2.54))
"mm" => Some(value * (96.0 / 25.4))
"pt" => Some(value * (96.0 / 72.0))
_ => Some(value)
}
}
///|
fn svg_attr_value(tag : StringView, name : StringView) -> String? {
let key = name.to_owned()
let dq = key + "=\""
match tag.find(dq) {
Some(pos) => {
let rest = tag[pos + dq.length():]
let end = match rest.find("\"") {
Some(v) => v
None => return None
}
let value = rest[:end]
Some(value.to_owned())
}
None => {
let sq = key + "='"
match tag.find(sq) {
Some(pos) => {
let rest = tag[pos + sq.length():]
let end = match rest.find("'") {
Some(v) => v
None => return None
}
let value = rest[:end]
Some(value.to_owned())
}
None => None
}
}
}
}
///|
fn svg_dimensions(bytes : BytesView) -> (Int, Int)? {
let text = @encoding/utf8.decode(bytes) catch { _ => return None }
let start = match text.find("