#include "html.h"
#include "gradient.h"
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
namespace litehtml
{
/**
* Parse CSS angle
* @param str css text of angle
* @param with_percent true to pare percent as angle (for conic gradient)
* @param angle output angle value in degrees
* @return
*/
static bool parse_css_angle(const string& str, bool with_percent, float& angle)
{
const char* start = str.c_str();
for(;start[0]; start++)
{
if(!isspace(start[0])) break;
}
if(start[0] == 0) return false;
char* end = nullptr;
auto a = (float) t_strtod(start, &end);
if(end && end[0] == 0) return false;
if(!strcmp(end, "rad"))
{
a = (float) (a * 180.0 / M_PI);
} else if(!strcmp(end, "grad"))
{
a = a * 180.0f / 200.0f;
} else if(!strcmp(end, "turn"))
{
a = a * 360.0f;
} else if(strcmp(end, "deg"))
{
if(with_percent && strcmp(end, "%"))
{
a = a * 360.0f / 100.0f;
} else
{
return false;
}
}
angle = a;
return true;
}
/**
* Parse colors stop list for radial and linear gradients.
* Formal syntax:
* \code
* =
* ?
*
* =
*
*
* =
* |
*
* \endcode
* @param parts
* @param container
* @param grad
*/
static void parse_color_stop_list(const string_vector& parts, document_container *container, std::vector& colors)
{
auto color = web_color::from_string(parts[0], container);
css_length length;
if(parts.size() > 1)
{
length.fromString(parts[1]);
if(!length.is_predefined())
{
background_gradient::gradient_color gc;
gc.color = color;
gc.length = length;
colors.push_back(gc);
}
if(parts.size() > 2)
{
length.fromString(parts[2]);
if(!length.is_predefined())
{
background_gradient::gradient_color gc;
gc.color = color;
gc.length = length;
colors.push_back(gc);
}
}
} else
{
background_gradient::gradient_color gc;
gc.color = color;
colors.push_back(gc);
}
}
static void parse_color_stop_angle_list(const string_vector& parts, document_container *container, background_gradient& grad)
{
auto color = web_color::from_string(parts[0], container);
if(parts.size() > 1)
{
float angle = 0;
if(parse_css_angle(parts[1], true, angle))
{
background_gradient::gradient_color gc;
gc.angle = angle;
gc.color = color;
grad.m_colors.push_back(gc);
}
if(parts.size() > 2)
{
if(parse_css_angle(parts[1], true, angle))
{
background_gradient::gradient_color gc;
gc.color = color;
gc.angle = angle;
grad.m_colors.push_back(gc);
}
}
} else
{
background_gradient::gradient_color gc;
gc.color = color;
grad.m_colors.push_back(gc);
}
}
/**
* Parse linear gradient definition.
* Formal syntax:
* \code{plain}
* =
* linear-gradient( [ ] )
*
* =
* [ | to ]? ,
*
* =
* [ left | right ] ||
* [ top | bottom ]
*
* =
* , [ ? , ]#
*
* =
* ?
*
* =
*
*
* =
* |
*
* \endcode
* @param gradient_str
* @param container
* @param grad
*/
void parse_linear_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad)
{
string_vector items;
split_string(gradient_str, items, ",", "", "()");
for (auto &item: items)
{
trim(item);
string_vector parts;
split_string(item, parts, split_delims_spaces, "", "()");
if (parts.empty()) continue;
if (parts[0] == "to")
{
uint32_t grad_side = 0;
for (size_t part_idx = 1; part_idx < parts.size(); part_idx++)
{
int side = value_index(parts[part_idx], "left;right;top;bottom");
if (side >= 0)
{
grad_side |= 1 << side;
}
}
switch(grad_side)
{
case background_gradient::gradient_side_top:
grad.angle = 0;
break;
case background_gradient::gradient_side_bottom:
grad.angle = 180;
break;
case background_gradient::gradient_side_left:
grad.angle = 270;
break;
case background_gradient::gradient_side_right:
grad.angle = 90;
break;
case background_gradient::gradient_side_top | background_gradient::gradient_side_left:
case background_gradient::gradient_side_top | background_gradient::gradient_side_right:
case background_gradient::gradient_side_bottom | background_gradient::gradient_side_left:
case background_gradient::gradient_side_bottom | background_gradient::gradient_side_right:
grad.m_side = grad_side;
break;
default:
break;
}
} else if (parts.size() == 1 && parse_css_angle(parts[0], false, grad.angle))
{
continue;
} else if (web_color::is_color(parts[0], container))
{
parse_color_stop_list(parts, container, grad.m_colors);
} else
{
css_length length;
length.fromString(parts[0]);
if (!length.is_predefined())
{
background_gradient::gradient_color gc;
gc.length = length;
gc.is_color_hint = true;
grad.m_colors.push_back(gc);
}
}
}
}
/**
* Parse position part for radial gradient.
* Formal syntax:
* \code
* =
* [ left | center | right | top | bottom | ] |
* [ left | center | right ] && [ top | center | bottom ] |
* [ left | center | right | ] [ top | center | bottom | ] |
* [ [ left | right ] ] && [ [ top | bottom ] ]
* \endcode
* @param grad
* @param parts
* @param i
*/
static inline void parse_radial_position(litehtml::background_gradient &grad, const litehtml::string_vector &parts, size_t i)
{
grad.m_side = 0;
while (i < parts.size())
{
int side = litehtml::value_index(parts[i], "left;right;top;bottom;center");
if (side >= 0)
{
if(side == 4)
{
if(grad.m_side & litehtml::background_gradient::gradient_side_x_center)
{
grad.m_side |= litehtml::background_gradient::gradient_side_y_center;
} else
{
grad.m_side |= litehtml::background_gradient::gradient_side_x_center;
}
} else
{
grad.m_side |= 1 << side;
}
} else
{
litehtml::css_length length;
length.fromString(parts[i]);
if (!length.is_predefined())
{
if(grad.m_side & litehtml::background_gradient::gradient_side_x_length)
{
grad.m_side |= litehtml::background_gradient::gradient_side_y_length;
grad.radial_position_y = length;
} else
{
grad.m_side |= litehtml::background_gradient::gradient_side_x_length;
grad.radial_position_x = length;
}
}
}
i++;
}
}
/**
* Parse radial gradient definition
* Formal syntax:
* \code
* =
* radial-gradient( [ ] )
*
* =
* [ || ]? [ at ]? ,
*
* =
* circle |
* ellipse
*
* =
* |
* |
* {2}
*
* =
* [ left | center | right | top | bottom | ] |
* [ left | center | right ] && [ top | center | bottom ] |
* [ left | center | right | ] [ top | center | bottom | ] |
* [ [ left | right ] ] && [ [ top | bottom ] ]
*
* =
* , [ ? , ]#
*
* =
* closest-corner |
* closest-side |
* farthest-corner |
* farthest-side
*
* =
* |
*
* =
* ?
*
* =
*
* \endcode
* @param gradient_str
* @param container
* @param grad
*/
void parse_radial_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad)
{
string_vector items;
split_string(gradient_str, items, ",", "", "()");
for (auto &item: items)
{
trim(item);
string_vector parts;
split_string(item, parts, split_delims_spaces, "", "()");
if (parts.empty()) continue;
if (web_color::is_color(parts[0], container))
{
parse_color_stop_list(parts, container, grad.m_colors);
} else
{
size_t i = 0;
while(i < parts.size())
{
if(parts[i] == "at")
{
parse_radial_position(grad, parts, i + 1);
break;
} else // parts[i] == "at"
{
int val = value_index(parts[i], "closest-corner;closest-side;farthest-corner;farthest-side");
if(val >= 0)
{
grad.radial_extent = (background_gradient::radial_extent_t) (val + 1);
} else
{
val = value_index(parts[i], "circle;ellipse");
if(val >= 0)
{
grad.radial_shape = (background_gradient::radial_shape_t) (val + 1);
} else
{
css_length length;
length.fromString(parts[i]);
if (!length.is_predefined())
{
if(!grad.radial_length_x.is_predefined())
{
grad.radial_length_y = length;
} else
{
grad.radial_length_x = length;
}
}
}
}
} // else parts[i] == "at"
i++;
} // while(i < parts.size())
} // else web_color::is_color(parts[0], container)
} // for
if(grad.radial_extent == background_gradient::radial_extent_none)
{
if(grad.radial_length_x.is_predefined())
{
grad.radial_extent = background_gradient::radial_extent_farthest_corner;
} else if(grad.radial_length_y.is_predefined())
{
grad.radial_length_y = grad.radial_length_x.val();
grad.radial_shape = background_gradient::radial_shape_circle;
}
}
if(grad.radial_shape == background_gradient::radial_shape_none)
{
grad.radial_shape = background_gradient::radial_shape_ellipse;
}
}
/**
* Parse conic gradient definition.
* Formal syntax:
* \code
* conic-gradient-syntax =
* [ [ [ from ]? [ at position ]? ] || ]? ,
*
* =
* [ left | center | right | top | bottom | ] |
* [ left | center | right ] && [ top | center | bottom ] |
* [ left | center | right | ] [ top | center | bottom | ] |
* [ [ left | right ] ] && [ [ top | bottom ] ]
*
* =
* in [ | ? ]
*
* =
* , [ ? , ]#
*
* =
* |
*
*
* =
* srgb |
* srgb-linear |
* display-p3 |
* a98-rgb |
* prophoto-rgb |
* rec2020 |
* lab |
* oklab |
* xyz |
* xyz-d50 |
* xyz-d65
*
* =
* hsl |
* hwb |
* lch |
* oklch
*
* =
* [ shorter | longer | increasing | decreasing ] hue
*
* =
* ?
*
* =
*
*
* =
* {1,2}
*
* =
* |
*
* \endcode
* @param gradient_str
* @param container
* @param grad
*/
void parse_conic_gradient(const std::string& gradient_str, document_container *container, background_gradient& grad)
{
string_vector items;
split_string(gradient_str, items, ",", "", "()");
for (auto &item: items)
{
trim(item);
string_vector parts;
split_string(item, parts, split_delims_spaces, "", "()");
if (parts.empty()) continue;
// Parse colors stop list
if (web_color::is_color(parts[0], container))
{
parse_color_stop_angle_list(parts, container, grad);
continue;
}
size_t i = 0;
while(i < parts.size())
{
// parse position
if(parts[i] == "at")
{
parse_radial_position(grad, parts, i + 1);
break;
}
// parse "from angle"
if(parts[i] == "from")
{
i++;
if(i >= parts.size()) continue;
parse_css_angle(parts[i], false, grad.conic_from_angle);
continue;
}
if(parts[i] == "in")
{
i++;
if(i >= parts.size()) continue;
int val = value_index(parts[i], "srgb;"
"srgb-linear;"
"display-p3;"
"a98-rgb;"
"prophoto-rgb;"
"rec2020;"
"lab;"
"oklab;"
"xyz;"
"xyz-d50;"
"xyz-d65");
if(val >= 0)
{
grad.conic_color_space = (decltype(grad.conic_color_space)) (val + 1);
continue;
}
val = value_index(parts[i], "hsl;"
"hwb;"
"lch;"
"oklch");
if(val < 0) continue;
grad.conic_color_space = (decltype(grad.conic_color_space)) (background_gradient::conic_color_space_polar_start + 1 + val);
int interpol = value_index(parts[i], "hue;shorter;longer;increasing;decreasing");
if(interpol == 0)
{
grad.conic_interpolation = background_gradient::interpolation_method_hue;
continue;
}
if(interpol > 0)
{
i++;
if(i >= parts.size()) continue;
if(parts[i] != "hue") continue;
grad.conic_interpolation = (decltype(grad.conic_interpolation)) (val + 1);
continue;
}
}
i++;
}
}
}
}