mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Improve convert_gradient_stops function.
Implement Specification from CSS Images 3. Improvements: 1. Stops with positions less than preceding stops are moved. A common pattern is blue `70%, white 0` for sharp transitions. 2. Stop runs are correct if first stop has a position. The list `black 0%, red, gold` now renders the same as `black, red, gold`. Other runs may also be corrected. 3. Absolute length are no longer capped to 100%.
This commit is contained in:
parent
e62ad7e680
commit
a956e3fd52
1 changed files with 57 additions and 36 deletions
|
@ -608,12 +608,48 @@ fn convert_gradient_stops(gradient_items: &[GradientItem],
|
||||||
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
||||||
//
|
//
|
||||||
// FIXME(#3908, pcwalton): Make sure later stops can't be behind earlier stops.
|
// FIXME(#3908, pcwalton): Make sure later stops can't be behind earlier stops.
|
||||||
let stop_items = gradient_items.iter().filter_map(|item| {
|
|
||||||
|
// Only keep the color stops. Discard the color items.
|
||||||
|
let mut stop_items = gradient_items.iter().filter_map(|item| {
|
||||||
match *item {
|
match *item {
|
||||||
GradientItem::ColorStop(ref stop) => Some(stop),
|
GradientItem::ColorStop(ref stop) => Some(*stop),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}).collect::<Vec<_>>();
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert!(stop_items.len() >= 2);
|
||||||
|
|
||||||
|
// Run the algorithm from
|
||||||
|
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
|
||||||
|
|
||||||
|
// Step 1: If nothing else is specified the first stop is
|
||||||
|
// at 0% and the last stop is at 100%.
|
||||||
|
{
|
||||||
|
let first = stop_items.first_mut().unwrap();
|
||||||
|
if first.position.is_none() {
|
||||||
|
first.position = Some(LengthOrPercentage::Percentage(0.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let last = stop_items.last_mut().unwrap();
|
||||||
|
if last.position.is_none() {
|
||||||
|
last.position = Some(LengthOrPercentage::Percentage(1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Move any stops placed before earlier stops to the
|
||||||
|
// same position as the preceding stop.
|
||||||
|
let mut state = stop_items.first().unwrap().position.unwrap();
|
||||||
|
for stop in stop_items.iter_mut() {
|
||||||
|
if let Some(pos) = stop.position {
|
||||||
|
if position_to_offset(state, length) > position_to_offset(pos, length) {
|
||||||
|
stop.position = Some(state);
|
||||||
|
}
|
||||||
|
state = stop.position.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Evenly space stops without position.
|
||||||
let mut stops = Vec::with_capacity(stop_items.len());
|
let mut stops = Vec::with_capacity(stop_items.len());
|
||||||
let mut stop_run = None;
|
let mut stop_run = None;
|
||||||
for (i, stop) in stop_items.iter().enumerate() {
|
for (i, stop) in stop_items.iter().enumerate() {
|
||||||
|
@ -621,44 +657,31 @@ fn convert_gradient_stops(gradient_items: &[GradientItem],
|
||||||
None => {
|
None => {
|
||||||
if stop_run.is_none() {
|
if stop_run.is_none() {
|
||||||
// Initialize a new stop run.
|
// Initialize a new stop run.
|
||||||
let start_offset = if i == 0 {
|
// `unwrap()` here should never fail because this is the beginning of
|
||||||
0.0
|
// a stop run, which is always bounded by a length or percentage.
|
||||||
} else {
|
let start_offset =
|
||||||
// `unwrap()` here should never fail because this is the beginning of
|
position_to_offset(stop_items[i - 1].position.unwrap(), length);
|
||||||
// a stop run, which is always bounded by a length or percentage.
|
// `unwrap()` here should never fail because this is the end of
|
||||||
position_to_offset(stop_items[i - 1].position.unwrap(), length)
|
// a stop run, which is always bounded by a length or percentage.
|
||||||
};
|
let (end_index, end_stop) = stop_items[(i + 1)..]
|
||||||
let (end_index, end_offset) =
|
.iter()
|
||||||
match stop_items[i..]
|
.enumerate()
|
||||||
.iter()
|
.find(|&(_, ref stop)| stop.position.is_some())
|
||||||
.enumerate()
|
.unwrap();
|
||||||
.find(|&(_, ref stop)| stop.position.is_some()) {
|
let end_offset = position_to_offset(end_stop.position.unwrap(), length);
|
||||||
None => (stop_items.len() - 1, 1.0),
|
|
||||||
Some((end_index, end_stop)) => {
|
|
||||||
// `unwrap()` here should never fail because this is the end of
|
|
||||||
// a stop run, which is always bounded by a length or
|
|
||||||
// percentage.
|
|
||||||
(end_index,
|
|
||||||
position_to_offset(end_stop.position.unwrap(), length))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
stop_run = Some(StopRun {
|
stop_run = Some(StopRun {
|
||||||
start_offset: start_offset,
|
start_offset: start_offset,
|
||||||
end_offset: end_offset,
|
end_offset: end_offset,
|
||||||
start_index: i,
|
start_index: i - 1,
|
||||||
stop_count: end_index - i,
|
stop_count: end_index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop_run = stop_run.unwrap();
|
let stop_run = stop_run.unwrap();
|
||||||
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
||||||
if stop_run.stop_count == 0 {
|
stop_run.start_offset +
|
||||||
stop_run.end_offset
|
stop_run_length * (i - stop_run.start_index) as f32 /
|
||||||
} else {
|
((2 + stop_run.stop_count) as f32)
|
||||||
stop_run.start_offset +
|
|
||||||
stop_run_length * (i - stop_run.start_index) as f32 /
|
|
||||||
(stop_run.stop_count as f32)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(position) => {
|
Some(position) => {
|
||||||
stop_run = None;
|
stop_run = None;
|
||||||
|
@ -2692,12 +2715,10 @@ struct StopRun {
|
||||||
|
|
||||||
fn position_to_offset(position: LengthOrPercentage, Au(total_length): Au) -> f32 {
|
fn position_to_offset(position: LengthOrPercentage, Au(total_length): Au) -> f32 {
|
||||||
match position {
|
match position {
|
||||||
LengthOrPercentage::Length(Au(length)) => {
|
LengthOrPercentage::Length(Au(length)) => length as f32 / total_length as f32,
|
||||||
(1.0f32).min(length as f32 / total_length as f32)
|
|
||||||
}
|
|
||||||
LengthOrPercentage::Percentage(percentage) => percentage as f32,
|
LengthOrPercentage::Percentage(percentage) => percentage as f32,
|
||||||
LengthOrPercentage::Calc(calc) =>
|
LengthOrPercentage::Calc(calc) =>
|
||||||
(1.0f32).min(calc.percentage() + (calc.length().0 as f32) / (total_length as f32)),
|
calc.percentage() + (calc.length().0 as f32) / (total_length as f32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue