use crate::ffi::{Handle, HandleStore};
use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char};
use std::ptr;
use std::sync::LazyLock;
type ContextHandle = Handle;
type StreamHandle = Handle;
type DeviceHandle = Handle;
type OutputHandle = Handle;
pub const SVG_TEXT_AS_PATH: i32 = 0;
pub const SVG_TEXT_AS_TEXT: i32 = 1;
pub const SVG_PATH_MOVE: i32 = 0;
pub const SVG_PATH_LINE: i32 = 1;
pub const SVG_PATH_HLINE: i32 = 2;
pub const SVG_PATH_VLINE: i32 = 3;
pub const SVG_PATH_CUBIC: i32 = 4;
pub const SVG_PATH_SMOOTH_CUBIC: i32 = 5;
pub const SVG_PATH_QUAD: i32 = 6;
pub const SVG_PATH_SMOOTH_QUAD: i32 = 7;
pub const SVG_PATH_ARC: i32 = 8;
pub const SVG_PATH_CLOSE: i32 = 9;
pub const SVG_ELEM_SVG: i32 = 0;
pub const SVG_ELEM_G: i32 = 1;
pub const SVG_ELEM_DEFS: i32 = 2;
pub const SVG_ELEM_SYMBOL: i32 = 3;
pub const SVG_ELEM_USE: i32 = 4;
pub const SVG_ELEM_RECT: i32 = 5;
pub const SVG_ELEM_CIRCLE: i32 = 6;
pub const SVG_ELEM_ELLIPSE: i32 = 7;
pub const SVG_ELEM_LINE: i32 = 8;
pub const SVG_ELEM_POLYLINE: i32 = 9;
pub const SVG_ELEM_POLYGON: i32 = 10;
pub const SVG_ELEM_PATH: i32 = 11;
pub const SVG_ELEM_TEXT: i32 = 12;
pub const SVG_ELEM_TSPAN: i32 = 13;
pub const SVG_ELEM_IMAGE: i32 = 14;
pub const SVG_ELEM_LINEAR_GRADIENT: i32 = 15;
pub const SVG_ELEM_RADIAL_GRADIENT: i32 = 16;
pub const SVG_ELEM_STOP: i32 = 17;
pub const SVG_ELEM_CLIPPATH: i32 = 18;
pub const SVG_ELEM_MASK: i32 = 19;
pub const SVG_ELEM_PATTERN: i32 = 20;
pub const SVG_ELEM_FILTER: i32 = 21;
pub const SVG_ELEM_UNKNOWN: i32 = 99;
pub const SVG_TRANSFORM_MATRIX: i32 = 0;
pub const SVG_TRANSFORM_TRANSLATE: i32 = 1;
pub const SVG_TRANSFORM_SCALE: i32 = 2;
pub const SVG_TRANSFORM_ROTATE: i32 = 3;
pub const SVG_TRANSFORM_SKEWX: i32 = 4;
pub const SVG_TRANSFORM_SKEWY: i32 = 5;
#[derive(Debug, Clone)]
pub struct SvgPathCommand {
pub cmd: i32,
pub relative: bool,
pub args: Vec<f32>,
}
impl SvgPathCommand {
pub fn new(cmd: i32, relative: bool) -> Self {
Self {
cmd,
relative,
args: Vec::new(),
}
}
pub fn with_args(mut self, args: &[f32]) -> Self {
self.args = args.to_vec();
self
}
}
#[derive(Debug, Clone)]
pub struct SvgTransform {
pub transform_type: i32,
pub values: Vec<f32>,
}
impl SvgTransform {
pub fn matrix(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
Self {
transform_type: SVG_TRANSFORM_MATRIX,
values: vec![a, b, c, d, e, f],
}
}
pub fn translate(tx: f32, ty: f32) -> Self {
Self {
transform_type: SVG_TRANSFORM_TRANSLATE,
values: vec![tx, ty],
}
}
pub fn scale(sx: f32, sy: f32) -> Self {
Self {
transform_type: SVG_TRANSFORM_SCALE,
values: vec![sx, sy],
}
}
pub fn rotate(angle: f32, cx: f32, cy: f32) -> Self {
Self {
transform_type: SVG_TRANSFORM_ROTATE,
values: vec![angle, cx, cy],
}
}
pub fn skew_x(angle: f32) -> Self {
Self {
transform_type: SVG_TRANSFORM_SKEWX,
values: vec![angle],
}
}
pub fn skew_y(angle: f32) -> Self {
Self {
transform_type: SVG_TRANSFORM_SKEWY,
values: vec![angle],
}
}
pub fn identity() -> Self {
Self::matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SvgColor {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl SvgColor {
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub fn from_hex(hex: u32) -> Self {
Self {
r: ((hex >> 16) & 0xFF) as u8,
g: ((hex >> 8) & 0xFF) as u8,
b: (hex & 0xFF) as u8,
a: 255,
}
}
pub fn black() -> Self {
Self::rgb(0, 0, 0)
}
pub fn white() -> Self {
Self::rgb(255, 255, 255)
}
pub fn transparent() -> Self {
Self::rgba(0, 0, 0, 0)
}
}
#[derive(Debug, Clone, Default)]
pub struct SvgStyle {
pub fill: Option<SvgColor>,
pub stroke: Option<SvgColor>,
pub stroke_width: f32,
pub fill_opacity: f32,
pub stroke_opacity: f32,
pub opacity: f32,
pub font_family: Option<String>,
pub font_size: f32,
pub font_weight: i32,
pub font_style: String,
}
impl SvgStyle {
pub fn new() -> Self {
Self {
fill: Some(SvgColor::black()),
stroke: None,
stroke_width: 1.0,
fill_opacity: 1.0,
stroke_opacity: 1.0,
opacity: 1.0,
font_family: None,
font_size: 16.0,
font_weight: 400,
font_style: "normal".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct SvgElement {
pub element_type: i32,
pub id: Option<String>,
pub classes: Vec<String>,
pub transform: Option<SvgTransform>,
pub style: SvgStyle,
pub attributes: HashMap<String, String>,
pub children: Vec<SvgElement>,
pub text_content: Option<String>,
pub path_commands: Vec<SvgPathCommand>,
}
impl SvgElement {
pub fn new(element_type: i32) -> Self {
Self {
element_type,
id: None,
classes: Vec::new(),
transform: None,
style: SvgStyle::new(),
attributes: HashMap::new(),
children: Vec::new(),
text_content: None,
path_commands: Vec::new(),
}
}
pub fn with_id(mut self, id: &str) -> Self {
self.id = Some(id.to_string());
self
}
pub fn add_child(&mut self, child: SvgElement) {
self.children.push(child);
}
pub fn set_attribute(&mut self, name: &str, value: &str) {
self.attributes.insert(name.to_string(), value.to_string());
}
pub fn get_attribute(&self, name: &str) -> Option<&String> {
self.attributes.get(name)
}
}
pub struct SvgDocument {
pub context: ContextHandle,
pub width: f32,
pub height: f32,
pub viewbox: Option<(f32, f32, f32, f32)>,
pub root: Option<SvgElement>,
pub id_map: HashMap<String, usize>,
pub defs: HashMap<String, SvgElement>,
pub base_uri: String,
pub content: Vec<u8>,
}
impl SvgDocument {
pub fn new(context: ContextHandle) -> Self {
Self {
context,
width: 300.0,
height: 150.0,
viewbox: None,
root: None,
id_map: HashMap::new(),
defs: HashMap::new(),
base_uri: String::new(),
content: Vec::new(),
}
}
fn parse_from_content(&mut self) {
let text = String::from_utf8_lossy(&self.content);
if let Some(svg_start) = text.find("<svg") {
let svg_end = text[svg_start..].find('>').map(|i| svg_start + i);
let tag = if let Some(end) = svg_end {
&text[svg_start..=end]
} else {
&text[svg_start..]
};
if let Some(w) = Self::extract_attr(tag, "width") {
if let Some(val) = Self::parse_length(&w) {
self.width = val;
}
}
if let Some(h) = Self::extract_attr(tag, "height") {
if let Some(val) = Self::parse_length(&h) {
self.height = val;
}
}
if let Some(vb) = Self::extract_attr(tag, "viewBox") {
let parts: Vec<f32> = vb
.split(|c: char| c == ',' || c.is_whitespace())
.filter(|s| !s.is_empty())
.filter_map(|s| s.parse::<f32>().ok())
.collect();
if parts.len() == 4 {
self.viewbox = Some((parts[0], parts[1], parts[2], parts[3]));
}
}
let mut root = SvgElement::new(SVG_ELEM_SVG);
if let Some(id_val) = Self::extract_attr(tag, "id") {
root.id = Some(id_val);
}
self.root = Some(root);
}
}
fn extract_attr(tag: &str, name: &str) -> Option<String> {
let patterns = [format!("{}=\"", name), format!("{}='", name)];
for pat in &patterns {
if let Some(start) = tag.find(pat.as_str()) {
let val_start = start + pat.len();
let quote = pat.as_bytes()[pat.len() - 1];
if let Some(val_end) = tag[val_start..].find(quote as char) {
return Some(tag[val_start..val_start + val_end].to_string());
}
}
}
None
}
fn parse_length(s: &str) -> Option<f32> {
let trimmed = s
.trim()
.trim_end_matches("px")
.trim_end_matches("pt")
.trim_end_matches("em")
.trim_end_matches("mm")
.trim_end_matches("cm")
.trim_end_matches("in")
.trim_end_matches('%');
trimmed.parse::<f32>().ok()
}
pub fn set_size(&mut self, width: f32, height: f32) {
self.width = width;
self.height = height;
}
pub fn set_viewbox(&mut self, min_x: f32, min_y: f32, width: f32, height: f32) {
self.viewbox = Some((min_x, min_y, width, height));
}
pub fn add_def(&mut self, id: &str, element: SvgElement) {
self.defs.insert(id.to_string(), element);
}
pub fn get_def(&self, id: &str) -> Option<&SvgElement> {
self.defs.get(id)
}
}
#[derive(Debug, Clone)]
pub struct SvgDeviceOptions {
pub text_format: i32,
pub reuse_images: bool,
pub resolution: i32,
}
impl Default for SvgDeviceOptions {
fn default() -> Self {
Self {
text_format: SVG_TEXT_AS_PATH,
reuse_images: true,
resolution: 96,
}
}
}
pub static SVG_DOCUMENTS: LazyLock<HandleStore<SvgDocument>> = LazyLock::new(HandleStore::new);
pub static SVG_ELEMENTS: LazyLock<HandleStore<SvgElement>> = LazyLock::new(HandleStore::new);
pub struct SvgDevice {
pub output: OutputHandle,
pub page_width: f32,
pub page_height: f32,
pub options: SvgDeviceOptions,
}
pub static SVG_DEVICES: LazyLock<HandleStore<SvgDevice>> = LazyLock::new(HandleStore::new);
#[unsafe(no_mangle)]
pub extern "C" fn svg_new_document(ctx: ContextHandle) -> Handle {
let doc = SvgDocument::new(ctx);
SVG_DOCUMENTS.insert(doc)
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_drop_document(_ctx: ContextHandle, doc: Handle) {
SVG_DOCUMENTS.remove(doc);
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_open_document(ctx: ContextHandle, filename: *const c_char) -> Handle {
if filename.is_null() {
return 0;
}
let path = unsafe { CStr::from_ptr(filename).to_string_lossy() };
let content = match std::fs::read(path.as_ref()) {
Ok(data) => data,
Err(_) => return 0,
};
let mut doc = SvgDocument::new(ctx);
doc.base_uri = path.to_string();
doc.content = content;
doc.parse_from_content();
SVG_DOCUMENTS.insert(doc)
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_open_document_with_stream(
ctx: ContextHandle,
stream: StreamHandle,
) -> Handle {
let content = if let Some(s) = crate::ffi::STREAMS.get(stream) {
let guard = s.lock().unwrap();
guard.data.clone()
} else {
return 0;
};
let mut doc = SvgDocument::new(ctx);
doc.content = content;
doc.parse_from_content();
SVG_DOCUMENTS.insert(doc)
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_get_width(_ctx: ContextHandle, doc: Handle) -> f32 {
if let Some(d) = SVG_DOCUMENTS.get(doc) {
let d = d.lock().unwrap();
return d.width;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_get_height(_ctx: ContextHandle, doc: Handle) -> f32 {
if let Some(d) = SVG_DOCUMENTS.get(doc) {
let d = d.lock().unwrap();
return d.height;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_size(_ctx: ContextHandle, doc: Handle, width: f32, height: f32) -> i32 {
if let Some(d) = SVG_DOCUMENTS.get(doc) {
let mut d = d.lock().unwrap();
d.set_size(width, height);
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_viewbox(
_ctx: ContextHandle,
doc: Handle,
min_x: f32,
min_y: f32,
width: f32,
height: f32,
) -> i32 {
if let Some(d) = SVG_DOCUMENTS.get(doc) {
let mut d = d.lock().unwrap();
d.set_viewbox(min_x, min_y, width, height);
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_get_viewbox(
_ctx: ContextHandle,
doc: Handle,
min_x: *mut f32,
min_y: *mut f32,
width: *mut f32,
height: *mut f32,
) -> i32 {
if let Some(d) = SVG_DOCUMENTS.get(doc) {
let d = d.lock().unwrap();
if let Some((vb_x, vb_y, vb_w, vb_h)) = d.viewbox {
if !min_x.is_null() {
unsafe {
*min_x = vb_x;
}
}
if !min_y.is_null() {
unsafe {
*min_y = vb_y;
}
}
if !width.is_null() {
unsafe {
*width = vb_w;
}
}
if !height.is_null() {
unsafe {
*height = vb_h;
}
}
return 1;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_new_element(_ctx: ContextHandle, element_type: i32) -> Handle {
let elem = SvgElement::new(element_type);
SVG_ELEMENTS.insert(elem)
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_drop_element(_ctx: ContextHandle, elem: Handle) {
SVG_ELEMENTS.remove(elem);
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_element_id(_ctx: ContextHandle, elem: Handle, id: *const c_char) -> i32 {
if id.is_null() {
return 0;
}
if let Some(e) = SVG_ELEMENTS.get(elem) {
let mut e = e.lock().unwrap();
let elem_id = unsafe { CStr::from_ptr(id).to_string_lossy().to_string() };
e.id = Some(elem_id);
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_get_element_id(_ctx: ContextHandle, elem: Handle) -> *mut c_char {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let e = e.lock().unwrap();
if let Some(ref id) = e.id {
if let Ok(cstr) = CString::new(id.clone()) {
return cstr.into_raw();
}
}
}
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_get_element_type(_ctx: ContextHandle, elem: Handle) -> i32 {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let e = e.lock().unwrap();
return e.element_type;
}
SVG_ELEM_UNKNOWN
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_attribute(
_ctx: ContextHandle,
elem: Handle,
name: *const c_char,
value: *const c_char,
) -> i32 {
if name.is_null() || value.is_null() {
return 0;
}
if let Some(e) = SVG_ELEMENTS.get(elem) {
let mut e = e.lock().unwrap();
let attr_name = unsafe { CStr::from_ptr(name).to_string_lossy().to_string() };
let attr_value = unsafe { CStr::from_ptr(value).to_string_lossy().to_string() };
e.set_attribute(&attr_name, &attr_value);
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_get_attribute(
_ctx: ContextHandle,
elem: Handle,
name: *const c_char,
) -> *mut c_char {
if name.is_null() {
return ptr::null_mut();
}
if let Some(e) = SVG_ELEMENTS.get(elem) {
let e = e.lock().unwrap();
let attr_name = unsafe { CStr::from_ptr(name).to_string_lossy() };
if let Some(value) = e.get_attribute(&attr_name) {
if let Ok(cstr) = CString::new(value.clone()) {
return cstr.into_raw();
}
}
}
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_transform_matrix(
_ctx: ContextHandle,
elem: Handle,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
) -> i32 {
if let Some(el) = SVG_ELEMENTS.get(elem) {
let mut el = el.lock().unwrap();
el.transform = Some(SvgTransform::matrix(a, b, c, d, e, f));
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_transform_translate(
_ctx: ContextHandle,
elem: Handle,
tx: f32,
ty: f32,
) -> i32 {
if let Some(el) = SVG_ELEMENTS.get(elem) {
let mut el = el.lock().unwrap();
el.transform = Some(SvgTransform::translate(tx, ty));
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_transform_scale(
_ctx: ContextHandle,
elem: Handle,
sx: f32,
sy: f32,
) -> i32 {
if let Some(el) = SVG_ELEMENTS.get(elem) {
let mut el = el.lock().unwrap();
el.transform = Some(SvgTransform::scale(sx, sy));
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_transform_rotate(
_ctx: ContextHandle,
elem: Handle,
angle: f32,
cx: f32,
cy: f32,
) -> i32 {
if let Some(el) = SVG_ELEMENTS.get(elem) {
let mut el = el.lock().unwrap();
el.transform = Some(SvgTransform::rotate(angle, cx, cy));
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_fill(
_ctx: ContextHandle,
elem: Handle,
r: u8,
g: u8,
b: u8,
a: u8,
) -> i32 {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let mut e = e.lock().unwrap();
e.style.fill = Some(SvgColor::rgba(r, g, b, a));
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_stroke(
_ctx: ContextHandle,
elem: Handle,
r: u8,
g: u8,
b: u8,
a: u8,
) -> i32 {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let mut e = e.lock().unwrap();
e.style.stroke = Some(SvgColor::rgba(r, g, b, a));
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_stroke_width(_ctx: ContextHandle, elem: Handle, width: f32) -> i32 {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let mut e = e.lock().unwrap();
e.style.stroke_width = width;
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_set_opacity(_ctx: ContextHandle, elem: Handle, opacity: f32) -> i32 {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let mut e = e.lock().unwrap();
e.style.opacity = opacity;
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_add_path_command(
_ctx: ContextHandle,
elem: Handle,
cmd: i32,
relative: i32,
args: *const f32,
num_args: i32,
) -> i32 {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let mut e = e.lock().unwrap();
let mut path_cmd = SvgPathCommand::new(cmd, relative != 0);
if !args.is_null() && num_args > 0 {
let arg_slice = unsafe { std::slice::from_raw_parts(args, num_args as usize) };
path_cmd = path_cmd.with_args(arg_slice);
}
e.path_commands.push(path_cmd);
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_path_command_count(_ctx: ContextHandle, elem: Handle) -> i32 {
if let Some(e) = SVG_ELEMENTS.get(elem) {
let e = e.lock().unwrap();
return e.path_commands.len() as i32;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_new_device(
_ctx: ContextHandle,
output: OutputHandle,
page_width: f32,
page_height: f32,
text_format: i32,
reuse_images: i32,
) -> Handle {
let device = SvgDevice {
output,
page_width,
page_height,
options: SvgDeviceOptions {
text_format,
reuse_images: reuse_images != 0,
resolution: 96,
},
};
SVG_DEVICES.insert(device)
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_parse_device_options(
_ctx: ContextHandle,
args: *const c_char,
text_format: *mut i32,
reuse_images: *mut i32,
resolution: *mut i32,
) -> i32 {
if args.is_null() {
return 0;
}
let args_str = unsafe { CStr::from_ptr(args).to_string_lossy() };
let mut opts = SvgDeviceOptions::default();
for token in args_str.split(|c: char| c == ',' || c == ' ' || c == ';') {
let token = token.trim();
if token.is_empty() {
continue;
}
if let Some((key, value)) = token.split_once('=') {
let key = key.trim();
let value = value.trim();
match key {
"text" => match value {
"text" => opts.text_format = SVG_TEXT_AS_TEXT,
"path" | "paths" => opts.text_format = SVG_TEXT_AS_PATH,
_ => {}
},
"reuse-images" | "reuse_images" => match value {
"no" | "false" | "0" => opts.reuse_images = false,
"yes" | "true" | "1" => opts.reuse_images = true,
_ => {}
},
"resolution" => {
if let Ok(res) = value.parse::<i32>() {
if res > 0 {
opts.resolution = res;
}
}
}
_ => {} }
}
}
if !text_format.is_null() {
unsafe {
*text_format = opts.text_format;
}
}
if !reuse_images.is_null() {
unsafe {
*reuse_images = if opts.reuse_images { 1 } else { 0 };
}
}
if !resolution.is_null() {
unsafe {
*resolution = opts.resolution;
}
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_parse_color(
_ctx: ContextHandle,
str: *const c_char,
r: *mut u8,
g: *mut u8,
b: *mut u8,
) -> i32 {
if str.is_null() {
return 0;
}
let color_str = unsafe { CStr::from_ptr(str).to_string_lossy() };
let color_str = color_str.trim();
let color = if color_str.starts_with('#') {
let hex = &color_str[1..];
if hex.len() == 3 {
let r_val = u8::from_str_radix(&hex[0..1], 16).unwrap_or(0) * 17;
let g_val = u8::from_str_radix(&hex[1..2], 16).unwrap_or(0) * 17;
let b_val = u8::from_str_radix(&hex[2..3], 16).unwrap_or(0) * 17;
Some(SvgColor::rgb(r_val, g_val, b_val))
} else if hex.len() == 6 {
let r_val = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
let g_val = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
let b_val = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
Some(SvgColor::rgb(r_val, g_val, b_val))
} else {
None
}
} else {
match color_str.to_lowercase().as_str() {
"black" => Some(SvgColor::rgb(0, 0, 0)),
"white" => Some(SvgColor::rgb(255, 255, 255)),
"red" => Some(SvgColor::rgb(255, 0, 0)),
"green" => Some(SvgColor::rgb(0, 128, 0)),
"blue" => Some(SvgColor::rgb(0, 0, 255)),
"yellow" => Some(SvgColor::rgb(255, 255, 0)),
"cyan" | "aqua" => Some(SvgColor::rgb(0, 255, 255)),
"magenta" | "fuchsia" => Some(SvgColor::rgb(255, 0, 255)),
"gray" | "grey" => Some(SvgColor::rgb(128, 128, 128)),
"orange" => Some(SvgColor::rgb(255, 165, 0)),
"purple" => Some(SvgColor::rgb(128, 0, 128)),
"none" | "transparent" => Some(SvgColor::transparent()),
_ => None,
}
};
if let Some(c) = color {
if !r.is_null() {
unsafe {
*r = c.r;
}
}
if !g.is_null() {
unsafe {
*g = c.g;
}
}
if !b.is_null() {
unsafe {
*b = c.b;
}
}
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_free_string(s: *mut c_char) {
if !s.is_null() {
unsafe {
drop(CString::from_raw(s));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_element_type_name(_ctx: ContextHandle, element_type: i32) -> *mut c_char {
let name = match element_type {
SVG_ELEM_SVG => "svg",
SVG_ELEM_G => "g",
SVG_ELEM_DEFS => "defs",
SVG_ELEM_SYMBOL => "symbol",
SVG_ELEM_USE => "use",
SVG_ELEM_RECT => "rect",
SVG_ELEM_CIRCLE => "circle",
SVG_ELEM_ELLIPSE => "ellipse",
SVG_ELEM_LINE => "line",
SVG_ELEM_POLYLINE => "polyline",
SVG_ELEM_POLYGON => "polygon",
SVG_ELEM_PATH => "path",
SVG_ELEM_TEXT => "text",
SVG_ELEM_TSPAN => "tspan",
SVG_ELEM_IMAGE => "image",
SVG_ELEM_LINEAR_GRADIENT => "linearGradient",
SVG_ELEM_RADIAL_GRADIENT => "radialGradient",
SVG_ELEM_STOP => "stop",
SVG_ELEM_CLIPPATH => "clipPath",
SVG_ELEM_MASK => "mask",
SVG_ELEM_PATTERN => "pattern",
SVG_ELEM_FILTER => "filter",
_ => "unknown",
};
if let Ok(cstr) = CString::new(name) {
return cstr.into_raw();
}
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn svg_path_command_name(
_ctx: ContextHandle,
cmd: i32,
relative: i32,
) -> *mut c_char {
let name = match (cmd, relative != 0) {
(SVG_PATH_MOVE, false) => "M",
(SVG_PATH_MOVE, true) => "m",
(SVG_PATH_LINE, false) => "L",
(SVG_PATH_LINE, true) => "l",
(SVG_PATH_HLINE, false) => "H",
(SVG_PATH_HLINE, true) => "h",
(SVG_PATH_VLINE, false) => "V",
(SVG_PATH_VLINE, true) => "v",
(SVG_PATH_CUBIC, false) => "C",
(SVG_PATH_CUBIC, true) => "c",
(SVG_PATH_SMOOTH_CUBIC, false) => "S",
(SVG_PATH_SMOOTH_CUBIC, true) => "s",
(SVG_PATH_QUAD, false) => "Q",
(SVG_PATH_QUAD, true) => "q",
(SVG_PATH_SMOOTH_QUAD, false) => "T",
(SVG_PATH_SMOOTH_QUAD, true) => "t",
(SVG_PATH_ARC, false) => "A",
(SVG_PATH_ARC, true) => "a",
(SVG_PATH_CLOSE, _) => "Z",
_ => "?",
};
if let Ok(cstr) = CString::new(name) {
return cstr.into_raw();
}
ptr::null_mut()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_format_constants() {
assert_eq!(SVG_TEXT_AS_PATH, 0);
assert_eq!(SVG_TEXT_AS_TEXT, 1);
}
#[test]
fn test_path_command_constants() {
assert_eq!(SVG_PATH_MOVE, 0);
assert_eq!(SVG_PATH_LINE, 1);
assert_eq!(SVG_PATH_CLOSE, 9);
}
#[test]
fn test_element_type_constants() {
assert_eq!(SVG_ELEM_SVG, 0);
assert_eq!(SVG_ELEM_PATH, 11);
assert_eq!(SVG_ELEM_TEXT, 12);
}
#[test]
fn test_svg_color() {
let black = SvgColor::black();
assert_eq!(black.r, 0);
assert_eq!(black.g, 0);
assert_eq!(black.b, 0);
let hex = SvgColor::from_hex(0xFF5500);
assert_eq!(hex.r, 255);
assert_eq!(hex.g, 85);
assert_eq!(hex.b, 0);
}
#[test]
fn test_svg_transform() {
let t = SvgTransform::translate(10.0, 20.0);
assert_eq!(t.transform_type, SVG_TRANSFORM_TRANSLATE);
assert_eq!(t.values, vec![10.0, 20.0]);
let r = SvgTransform::rotate(45.0, 0.0, 0.0);
assert_eq!(r.transform_type, SVG_TRANSFORM_ROTATE);
}
#[test]
fn test_svg_element() {
let mut elem = SvgElement::new(SVG_ELEM_RECT).with_id("my-rect");
elem.set_attribute("width", "100");
elem.set_attribute("height", "50");
assert_eq!(elem.id, Some("my-rect".to_string()));
assert_eq!(elem.get_attribute("width"), Some(&"100".to_string()));
}
#[test]
fn test_svg_document() {
let mut doc = SvgDocument::new(0);
doc.set_size(800.0, 600.0);
doc.set_viewbox(0.0, 0.0, 800.0, 600.0);
assert_eq!(doc.width, 800.0);
assert_eq!(doc.height, 600.0);
assert_eq!(doc.viewbox, Some((0.0, 0.0, 800.0, 600.0)));
}
#[test]
fn test_ffi_document() {
let ctx = 0;
let doc = svg_new_document(ctx);
assert!(doc > 0);
assert_eq!(svg_get_width(ctx, doc), 300.0);
assert_eq!(svg_get_height(ctx, doc), 150.0);
svg_set_size(ctx, doc, 800.0, 600.0);
assert_eq!(svg_get_width(ctx, doc), 800.0);
svg_drop_document(ctx, doc);
}
#[test]
fn test_svg_invalid_handle() {
assert_eq!(svg_get_width(0, 0), 0.0);
assert_eq!(svg_get_height(0, 0), 0.0);
svg_set_size(0, 0, 100.0, 100.0);
svg_drop_document(0, 0);
}
#[test]
fn test_svg_path_command_name() {
let name = svg_path_command_name(0, SVG_PATH_MOVE, 0);
assert!(!name.is_null());
let name_rel = svg_path_command_name(0, SVG_PATH_MOVE, 1);
assert!(!name_rel.is_null());
}
#[test]
fn test_ffi_element() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_RECT);
assert!(elem > 0);
assert_eq!(svg_get_element_type(ctx, elem), SVG_ELEM_RECT);
let id = CString::new("my-rect").unwrap();
svg_set_element_id(ctx, elem, id.as_ptr());
let result = svg_get_element_id(ctx, elem);
assert!(!result.is_null());
unsafe {
let s = CStr::from_ptr(result).to_string_lossy();
assert_eq!(s, "my-rect");
svg_free_string(result);
}
svg_drop_element(ctx, elem);
}
#[test]
fn test_ffi_transform() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_G);
svg_set_transform_translate(ctx, elem, 50.0, 100.0);
if let Some(e) = SVG_ELEMENTS.get(elem) {
let e = e.lock().unwrap();
assert!(e.transform.is_some());
if let Some(ref t) = e.transform {
assert_eq!(t.transform_type, SVG_TRANSFORM_TRANSLATE);
}
}
svg_drop_element(ctx, elem);
}
#[test]
fn test_ffi_style() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_RECT);
svg_set_fill(ctx, elem, 255, 0, 0, 255);
svg_set_stroke(ctx, elem, 0, 0, 0, 255);
svg_set_stroke_width(ctx, elem, 2.0);
if let Some(e) = SVG_ELEMENTS.get(elem) {
let e = e.lock().unwrap();
assert_eq!(e.style.fill, Some(SvgColor::rgba(255, 0, 0, 255)));
assert_eq!(e.style.stroke_width, 2.0);
}
svg_drop_element(ctx, elem);
}
#[test]
fn test_ffi_path_commands() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_PATH);
let args1 = [10.0f32, 20.0];
svg_add_path_command(ctx, elem, SVG_PATH_MOVE, 0, args1.as_ptr(), 2);
let args2 = [100.0f32, 20.0];
svg_add_path_command(ctx, elem, SVG_PATH_LINE, 0, args2.as_ptr(), 2);
svg_add_path_command(ctx, elem, SVG_PATH_CLOSE, 0, ptr::null(), 0);
assert_eq!(svg_path_command_count(ctx, elem), 3);
svg_drop_element(ctx, elem);
}
#[test]
fn test_ffi_parse_color() {
let ctx = 0;
let mut r: u8 = 0;
let mut g: u8 = 0;
let mut b: u8 = 0;
let hex = CString::new("#FF5500").unwrap();
svg_parse_color(ctx, hex.as_ptr(), &mut r, &mut g, &mut b);
assert_eq!(r, 255);
assert_eq!(g, 85);
assert_eq!(b, 0);
let red = CString::new("red").unwrap();
svg_parse_color(ctx, red.as_ptr(), &mut r, &mut g, &mut b);
assert_eq!(r, 255);
assert_eq!(g, 0);
assert_eq!(b, 0);
}
#[test]
fn test_ffi_element_type_name() {
let ctx = 0;
let name = svg_element_type_name(ctx, SVG_ELEM_RECT);
assert!(!name.is_null());
unsafe {
let s = CStr::from_ptr(name).to_string_lossy();
assert_eq!(s, "rect");
svg_free_string(name);
}
}
#[test]
fn test_ffi_path_command_name() {
let ctx = 0;
let name = svg_path_command_name(ctx, SVG_PATH_MOVE, 0);
unsafe {
let s = CStr::from_ptr(name).to_string_lossy();
assert_eq!(s, "M");
svg_free_string(name);
}
let name = svg_path_command_name(ctx, SVG_PATH_LINE, 1);
unsafe {
let s = CStr::from_ptr(name).to_string_lossy();
assert_eq!(s, "l");
svg_free_string(name);
}
}
#[test]
fn test_svg_path_command_with_args() {
let cmd = SvgPathCommand::new(SVG_PATH_MOVE, false).with_args(&[10.0, 20.0]);
assert_eq!(cmd.cmd, SVG_PATH_MOVE);
assert!(!cmd.relative);
assert_eq!(cmd.args, vec![10.0, 20.0]);
}
#[test]
fn test_svg_color_rgba_white_transparent() {
let rgba = SvgColor::rgba(128, 64, 32, 200);
assert_eq!(rgba.r, 128);
assert_eq!(rgba.g, 64);
assert_eq!(rgba.b, 32);
assert_eq!(rgba.a, 200);
assert_eq!(SvgColor::white(), SvgColor::rgb(255, 255, 255));
let t = SvgColor::transparent();
assert_eq!(t.a, 0);
}
#[test]
fn test_svg_style_new() {
let style = SvgStyle::new();
assert!(style.fill.is_some());
assert_eq!(style.stroke_width, 1.0);
assert_eq!(style.font_size, 16.0);
}
#[test]
fn test_svg_transform_constructors() {
let m = SvgTransform::matrix(1.0, 0.0, 0.0, 1.0, 10.0, 20.0);
assert_eq!(m.transform_type, SVG_TRANSFORM_MATRIX);
assert_eq!(m.values.len(), 6);
let s = SvgTransform::scale(2.0, 3.0);
assert_eq!(s.transform_type, SVG_TRANSFORM_SCALE);
assert_eq!(s.values, vec![2.0, 3.0]);
let skx = SvgTransform::skew_x(15.0);
assert_eq!(skx.transform_type, SVG_TRANSFORM_SKEWX);
let sky = SvgTransform::skew_y(15.0);
assert_eq!(sky.transform_type, SVG_TRANSFORM_SKEWY);
let id = SvgTransform::identity();
assert_eq!(id.values, vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
}
#[test]
fn test_svg_element_add_child_get_attribute() {
let mut parent = SvgElement::new(SVG_ELEM_G);
let child = SvgElement::new(SVG_ELEM_RECT);
parent.add_child(child);
assert_eq!(parent.children.len(), 1);
assert!(parent.get_attribute("nonexistent").is_none());
}
#[test]
fn test_svg_document_parse_from_content() {
let svg_content = r#"<svg width="400px" height="300pt" viewBox="0 0 400 300" id="root">
</svg>"#;
let mut doc = SvgDocument::new(0);
doc.content = svg_content.as_bytes().to_vec();
doc.parse_from_content();
assert_eq!(doc.width, 400.0);
assert_eq!(doc.height, 300.0);
assert_eq!(doc.viewbox, Some((0.0, 0.0, 400.0, 300.0)));
assert!(doc.root.is_some());
}
#[test]
fn test_svg_document_add_def_get_def() {
let mut doc = SvgDocument::new(0);
let grad = SvgElement::new(SVG_ELEM_LINEAR_GRADIENT);
doc.add_def("grad1", grad);
assert!(doc.get_def("grad1").is_some());
assert!(doc.get_def("missing").is_none());
}
#[test]
fn test_svg_open_document_from_file() {
let tmp = tempfile::NamedTempFile::new().unwrap();
let path = tmp.path();
let svg = r#"<svg width="100" height="100"></svg>"#;
std::fs::write(path, svg).unwrap();
let c_path = CString::new(path.to_str().unwrap()).unwrap();
let doc = svg_open_document(0, c_path.as_ptr());
assert!(doc > 0);
assert_eq!(svg_get_width(0, doc), 100.0);
svg_drop_document(0, doc);
}
#[test]
fn test_svg_open_document_null() {
assert_eq!(svg_open_document(0, std::ptr::null()), 0);
}
#[test]
fn test_svg_open_document_with_stream() {
let svg_data = b"<svg width=\"50\" height=\"50\"></svg>";
let stream = crate::ffi::stream::fz_open_memory(0, svg_data.as_ptr(), svg_data.len());
assert!(stream > 0);
let doc = svg_open_document_with_stream(0, stream);
assert!(doc > 0);
assert_eq!(svg_get_width(0, doc), 50.0);
svg_drop_document(0, doc);
crate::ffi::stream::fz_drop_stream(0, stream);
}
#[test]
fn test_svg_open_document_with_stream_invalid() {
assert_eq!(svg_open_document_with_stream(0, 99999), 0);
}
#[test]
fn test_svg_set_viewbox_get_viewbox() {
let ctx = 0;
let doc = svg_new_document(ctx);
svg_set_viewbox(ctx, doc, 0.0, 0.0, 100.0, 100.0);
let mut min_x = 0.0f32;
let mut min_y = 0.0f32;
let mut w = 0.0f32;
let mut h = 0.0f32;
assert_eq!(
svg_get_viewbox(ctx, doc, &mut min_x, &mut min_y, &mut w, &mut h),
1
);
assert_eq!((min_x, min_y, w, h), (0.0, 0.0, 100.0, 100.0));
svg_drop_document(ctx, doc);
}
#[test]
fn test_svg_get_viewbox_no_viewbox() {
let ctx = 0;
let doc = svg_new_document(ctx);
let mut min_x = 0.0f32;
assert_eq!(
svg_get_viewbox(
ctx,
doc,
&mut min_x,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut()
),
0
);
svg_drop_document(ctx, doc);
}
#[test]
fn test_svg_set_attribute_get_attribute() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_RECT);
let name = CString::new("width").unwrap();
let value = CString::new("200").unwrap();
assert_eq!(
svg_set_attribute(ctx, elem, name.as_ptr(), value.as_ptr()),
1
);
let result = svg_get_attribute(ctx, elem, name.as_ptr());
assert!(!result.is_null());
unsafe {
let s = CStr::from_ptr(result).to_string_lossy();
assert_eq!(s, "200");
svg_free_string(result);
}
svg_drop_element(ctx, elem);
}
#[test]
fn test_svg_set_attribute_null() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_RECT);
let value = CString::new("x").unwrap();
assert_eq!(
svg_set_attribute(ctx, elem, std::ptr::null(), value.as_ptr()),
0
);
assert_eq!(
svg_set_attribute(ctx, elem, value.as_ptr(), std::ptr::null()),
0
);
svg_drop_element(ctx, elem);
}
#[test]
fn test_svg_set_transform_matrix_scale_rotate() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_G);
assert_eq!(
svg_set_transform_matrix(ctx, elem, 1.0, 0.0, 0.0, 1.0, 10.0, 20.0),
1
);
assert_eq!(svg_set_transform_scale(ctx, elem, 2.0, 2.0), 1);
assert_eq!(svg_set_transform_rotate(ctx, elem, 45.0, 50.0, 50.0), 1);
svg_drop_element(ctx, elem);
}
#[test]
fn test_svg_set_opacity() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_RECT);
assert_eq!(svg_set_opacity(ctx, elem, 0.5), 1);
svg_drop_element(ctx, elem);
}
#[test]
fn test_svg_add_path_command_null_args() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_PATH);
assert_eq!(
svg_add_path_command(ctx, elem, SVG_PATH_CLOSE, 0, std::ptr::null(), 0),
1
);
assert_eq!(svg_path_command_count(ctx, elem), 1);
svg_drop_element(ctx, elem);
}
#[test]
fn test_svg_new_device() {
let ctx = 0;
let buf = crate::ffi::buffer::fz_new_buffer(ctx, 0);
let out = crate::ffi::output::fz_new_output_with_buffer(ctx, buf);
let dev = svg_new_device(ctx, out, 612.0, 792.0, SVG_TEXT_AS_PATH, 1);
assert!(dev > 0);
crate::ffi::output::fz_drop_output(ctx, out);
crate::ffi::buffer::fz_drop_buffer(ctx, buf);
}
#[test]
fn test_svg_parse_device_options() {
let ctx = 0;
let mut text_fmt = 0i32;
let mut reuse = 0i32;
let mut res = 0i32;
let opts = CString::new("text=text,reuse-images=no,resolution=150").unwrap();
assert_eq!(
svg_parse_device_options(ctx, opts.as_ptr(), &mut text_fmt, &mut reuse, &mut res),
1
);
assert_eq!(text_fmt, SVG_TEXT_AS_TEXT);
assert_eq!(reuse, 0);
assert_eq!(res, 150);
}
#[test]
fn test_svg_parse_device_options_path_resolution() {
let ctx = 0;
let mut text_fmt = 99i32;
let opts = CString::new("text=path").unwrap();
assert_eq!(
svg_parse_device_options(
ctx,
opts.as_ptr(),
&mut text_fmt,
std::ptr::null_mut(),
std::ptr::null_mut()
),
1
);
assert_eq!(text_fmt, SVG_TEXT_AS_PATH);
}
#[test]
fn test_svg_parse_color_hex_short() {
let ctx = 0;
let mut r = 0u8;
let mut g = 0u8;
let mut b = 0u8;
let hex = CString::new("#f00").unwrap();
assert_eq!(
svg_parse_color(ctx, hex.as_ptr(), &mut r, &mut g, &mut b),
1
);
assert_eq!(r, 255);
assert_eq!(g, 0);
assert_eq!(b, 0);
}
#[test]
fn test_svg_parse_color_hex_long() {
let ctx = 0;
let mut r = 0u8;
let mut g = 0u8;
let mut b = 0u8;
let hex = CString::new("#00ff80").unwrap();
assert_eq!(
svg_parse_color(ctx, hex.as_ptr(), &mut r, &mut g, &mut b),
1
);
assert_eq!(r, 0);
assert_eq!(g, 255);
assert_eq!(b, 128);
}
#[test]
fn test_svg_parse_color_named() {
let ctx = 0;
let mut r = 0u8;
let mut g = 0u8;
let mut b = 0u8;
for (name, expected) in [
("black", (0, 0, 0)),
("white", (255, 255, 255)),
("green", (0, 128, 0)),
("blue", (0, 0, 255)),
("yellow", (255, 255, 0)),
("cyan", (0, 255, 255)),
("magenta", (255, 0, 255)),
("gray", (128, 128, 128)),
("orange", (255, 165, 0)),
("purple", (128, 0, 128)),
("none", (0, 0, 0)),
] {
let c = CString::new(name).unwrap();
svg_parse_color(ctx, c.as_ptr(), &mut r, &mut g, &mut b);
assert_eq!((r, g, b), expected, "failed for {}", name);
}
}
#[test]
fn test_svg_parse_color_invalid() {
let ctx = 0;
let mut r = 0u8;
let mut g = 0u8;
let mut b = 0u8;
assert_eq!(
svg_parse_color(ctx, std::ptr::null(), &mut r, &mut g, &mut b),
0
);
let bad = CString::new("#xx").unwrap();
assert_eq!(
svg_parse_color(ctx, bad.as_ptr(), &mut r, &mut g, &mut b),
0
);
}
#[test]
fn test_svg_free_string_null() {
svg_free_string(std::ptr::null_mut());
}
#[test]
fn test_svg_element_type_name_unknown() {
let ctx = 0;
let name = svg_element_type_name(ctx, 999);
assert!(!name.is_null());
unsafe {
let s = CStr::from_ptr(name).to_string_lossy();
assert_eq!(s, "unknown");
svg_free_string(name);
}
}
#[test]
fn test_svg_path_command_name_all() {
let ctx = 0;
for (cmd, rel, expected) in [
(SVG_PATH_HLINE, 0, "H"),
(SVG_PATH_VLINE, 1, "v"),
(SVG_PATH_CUBIC, 0, "C"),
(SVG_PATH_ARC, 1, "a"),
(SVG_PATH_CLOSE, 0, "Z"),
(99, 0, "?"),
] {
let name = svg_path_command_name(ctx, cmd, if rel != 0 { 1 } else { 0 });
assert!(!name.is_null());
unsafe {
let s = CStr::from_ptr(name).to_string_lossy();
assert_eq!(s, expected);
svg_free_string(name);
}
}
}
#[test]
fn test_svg_set_element_id_null() {
assert_eq!(svg_set_element_id(0, 99999, std::ptr::null()), 0);
}
#[test]
fn test_svg_get_element_id_no_id() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_RECT);
let result = svg_get_element_id(ctx, elem);
assert!(result.is_null());
svg_drop_element(ctx, elem);
}
#[test]
fn test_svg_get_attribute_null_name() {
let ctx = 0;
let elem = svg_new_element(ctx, SVG_ELEM_RECT);
assert!(svg_get_attribute(ctx, elem, std::ptr::null()).is_null());
svg_drop_element(ctx, elem);
}
#[test]
fn test_svg_device_options_default() {
let opts = SvgDeviceOptions::default();
assert_eq!(opts.text_format, SVG_TEXT_AS_PATH);
assert!(opts.reuse_images);
assert_eq!(opts.resolution, 96);
}
#[test]
fn test_svg_parse_length_units() {
let mut doc = SvgDocument::new(0);
doc.content = b"<svg width='100px' height='50pt' viewBox='0 0 200 100'></svg>".to_vec();
doc.parse_from_content();
assert_eq!(doc.width, 100.0);
assert_eq!(doc.height, 50.0);
assert_eq!(doc.viewbox, Some((0.0, 0.0, 200.0, 100.0)));
}
}