Compare commits

..
7 changed files with 408 additions and 589 deletions
+64 -85
View File
@@ -5,7 +5,7 @@ use std::collections::HashMap;
use deckcraft_color::{ColorTransform, Rgba, SchemeSlot};
use deckcraft_model::style::{Dash, Glow, Gradient, GradientShape, GradientStop, LineEnd, PatternFill, PictureFill, PictureMode, Reflection, Shadow};
use deckcraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeId, ShapeStyle};
use deckcraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeStyle};
use serde_json::{Value, json};
use super::{
@@ -556,102 +556,81 @@ fn edit_group_appearance(s: &mut Session, p: &Value, cmd: &str, f: impl Fn(&mut
edit_shapes(s, p, cmd, |sh| apply(sh, 0, &f))
}
/// The fill a `shape.fill` request sets: `Some(None)` clears it, `Some(Some(_))` sets it, and `None`
/// leaves it as it is.
fn requested_fill(s: &mut Session, p: &Value) -> Result<Option<Option<Fill>>> {
if bool_or(p, "reset", false) {
return Ok(Some(None));
}
if bool_or(p, "none", false) {
return Ok(Some(Some(Fill::None)));
}
if let Some(c) = color_param(p, "color") {
return Ok(Some(Some(Fill::solid(c))));
}
if let Some(g) = p.get("gradient") {
let gradient = gradient_param(g).ok_or_else(|| bad("shape.fill", "gradient needs at least two `stops`: [[0, \"#fff\"], [1, \"#000\"]]"))?;
return Ok(Some(Some(Fill::Gradient(gradient))));
}
if let Some(pt) = p.get("pattern") {
return Ok(Some(Some(Fill::Pattern(PatternFill {
fn fill(s: &mut Session, p: &Value) -> Result<Value> {
let new_fill: Option<Option<Fill>> = if bool_or(p, "reset", false) {
Some(None)
} else if bool_or(p, "none", false) {
Some(Some(Fill::None))
} else if let Some(c) = color_param(p, "color") {
Some(Some(Fill::solid(c)))
} else if let Some(g) = p.get("gradient") {
Some(Some(Fill::Gradient(
gradient_param(g).ok_or_else(|| bad("shape.fill", "gradient needs at least two `stops`: [[0, \"#fff\"], [1, \"#000\"]]"))?,
)))
} else if let Some(pt) = p.get("pattern") {
Some(Some(Fill::Pattern(PatternFill {
preset: pt.get("preset").and_then(Value::as_str).unwrap_or("pct50").to_string(),
fg: pt.get("fg").and_then(color_value).unwrap_or(ColorRef::scheme(SchemeSlot::Accent1)),
bg: pt.get("bg").and_then(color_value).unwrap_or(ColorRef::scheme(SchemeSlot::Bg1)),
}))));
}
if p.get("picture").is_some() {
})))
} else if p.get("picture").is_some() {
let (name, bytes) = super::insert::media_bytes(&with_param(p, "data", p.get("picture").cloned().unwrap_or_default()), "shape.fill")?;
let ct = super::insert::content_type(&name, &bytes);
let media = s.edit(|doc, _| doc.add_media(&name, ct, bytes).ok_or_else(|| bad("shape.fill", NO_ID_SPACE)))?;
return Ok(Some(Some(Fill::Picture(PictureFill { media, ..Default::default() }))));
}
Ok(None)
}
/// The effective fill of each targeted shape, for a transparency change on an inherited (style) fill.
fn inherited_fills(s: &Session, p: &Value) -> Result<HashMap<ShapeId, Option<Fill>>> {
let st = s.doc()?;
let ctx = super::text::ctx_for(&st.doc, &st.selection);
let mut fills = HashMap::new();
for id in targets(s, p)? {
if let Some(sh) = st.shape(id) {
deckcraft_model::walk(std::slice::from_ref(sh), &mut |sh, _| {
let fill = ctx.as_ref().and_then(|c| {
let (f, ph) = deckcraft_model::resolve::fill(c, sh);
f.map(|f| match f {
Fill::Solid { color } => Fill::solid(ColorRef::rgb(c.color(&color, ph))),
other => other,
})
});
fills.insert(sh.id, fill);
});
}
}
Ok(fills)
}
/// Applies a `shape.fill` request to one shape: the fill, then its picture options, then transparency.
fn apply_fill(
sh: &mut deckcraft_model::Shape,
new_fill: Option<&Option<Fill>>,
pic_props: Option<&Value>,
transparency: Option<f64>,
inherited: &HashMap<ShapeId, Option<Fill>>,
) -> Result<()> {
if let Some(f) = new_fill {
sh.fill.clone_from(f);
}
if let (Some(pp), Some(Fill::Picture(pf))) = (pic_props, sh.fill.as_mut()) {
picture_props(pf, pp);
}
if let (Some(t), Some(Fill::Picture(pf))) = (transparency, sh.fill.as_mut()) {
let a = 1.0 - t.clamp(0.0, 1.0);
pf.alpha = (a < 1.0).then_some(a);
return Ok(());
}
if let Some(t) = transparency {
let base = sh.fill.clone().or_else(|| inherited.get(&sh.id).cloned().flatten());
if let Some(Fill::Solid { mut color }) = base {
color.mods.retain(|m| !matches!(m, ColorTransform::Alpha(_)));
if t > 0.0 {
color.mods.push(ColorTransform::Alpha(((1.0 - t.clamp(0.0, 1.0)) * 100_000.0).round() as i32));
}
sh.fill = Some(Fill::Solid { color });
}
}
Ok(())
}
fn fill(s: &mut Session, p: &Value) -> Result<Value> {
let new_fill = requested_fill(s, p)?;
Some(Some(Fill::Picture(PictureFill { media, ..Default::default() })))
} else {
None
};
let transparency = f64_param(p, "transparency");
let pic_props = p.get("pictureProps").filter(|v| v.is_object());
if new_fill.is_none() && transparency.is_none() && pic_props.is_none() {
return Err(bad("shape.fill", "give `color`, `none`, `gradient`, `pattern`, `picture`, `pictureProps`, `transparency` or `reset`"));
}
// Transparency on an inherited (style) fill needs the effective colour.
let inherited = inherited_fills(s, p)?;
edit_group_appearance(s, p, "shape.fill", |sh| apply_fill(sh, new_fill.as_ref(), pic_props, transparency, &inherited))
let st = s.doc()?;
let ctx_fill = {
let ctx = super::text::ctx_for(&st.doc, &st.selection);
let mut fills = HashMap::new();
for id in targets(s, p)? {
if let Some(sh) = st.shape(id) {
deckcraft_model::walk(std::slice::from_ref(sh), &mut |sh, _| {
let fill = ctx.as_ref().and_then(|c| {
let (f, ph) = deckcraft_model::resolve::fill(c, sh);
f.map(|f| match f {
Fill::Solid { color } => Fill::solid(ColorRef::rgb(c.color(&color, ph))),
other => other,
})
});
fills.insert(sh.id, fill);
});
}
}
fills
};
edit_group_appearance(s, p, "shape.fill", |sh| {
if let Some(f) = &new_fill {
sh.fill.clone_from(f);
}
if let (Some(pp), Some(Fill::Picture(pf))) = (pic_props, sh.fill.as_mut()) {
picture_props(pf, pp);
}
if let (Some(t), Some(Fill::Picture(pf))) = (transparency, sh.fill.as_mut()) {
let a = 1.0 - t.clamp(0.0, 1.0);
pf.alpha = (a < 1.0).then_some(a);
return Ok(());
}
if let Some(t) = transparency {
let base = sh.fill.clone().or_else(|| ctx_fill.get(&sh.id).cloned().flatten());
if let Some(Fill::Solid { mut color }) = base {
color.mods.retain(|m| !matches!(m, ColorTransform::Alpha(_)));
if t > 0.0 {
color.mods.push(ColorTransform::Alpha(((1.0 - t.clamp(0.0, 1.0)) * 100_000.0).round() as i32));
}
sh.fill = Some(Fill::Solid { color });
}
}
Ok(())
})
}
/// Applies Format Shape ▸ Picture or texture fill options to a picture fill.
+140 -201
View File
@@ -126,216 +126,155 @@ fn advance(rtf: &str, i: usize, n: usize) -> usize {
if rtf.is_char_boundary(i + n) { i + n } else { i + rtf.get(i..).and_then(|r| r.chars().next()).map_or(1, char::len_utf8) }
}
/// Group state: whether the text is being skipped, and the `\uc` fallback count.
#[derive(Clone, Copy)]
struct St {
skip: bool,
uc: usize,
}
/// Parser state for [`rtf_to_outline`]: the cursor, the group stack and the outline so far.
struct Outline<'a> {
rtf: &'a str,
b: &'a [u8],
i: usize,
stack: Vec<St>,
st: St,
level: Option<u32>,
indent: i64,
pending_skip: usize,
line: String,
out: String,
}
/// Read an RTF outline into tab-indented outline text: a paragraph's outline level (or its left
/// indent in 0.25" steps) becomes its number of tabs.
pub fn rtf_to_outline(rtf: &str) -> String {
let mut p = Outline {
rtf,
b: rtf.as_bytes(),
i: 0,
stack: vec![],
st: St { skip: false, uc: 1 },
level: None,
indent: 0,
pending_skip: 0,
line: String::new(),
out: String::new(),
#[derive(Clone, Copy)]
struct St {
skip: bool,
uc: usize,
}
let b = rtf.as_bytes();
let mut stack: Vec<St> = vec![];
let mut st = St { skip: false, uc: 1 };
let (mut level, mut indent): (Option<u32>, i64) = (None, 0);
let mut line = String::new();
let mut out = String::new();
let mut pending_skip = 0usize;
let mut i = 0;
let flush = |line: &mut String, level: Option<u32>, indent: i64, out: &mut String| {
let text = line.trim();
if !text.is_empty() {
let n = level.map(|l| l as i64).unwrap_or(indent.saturating_add(180) / 360).clamp(0, 9) as usize;
out.push_str(&"\t".repeat(n));
out.push_str(text);
out.push('\n');
}
line.clear();
};
p.run();
p.flush();
finish_surrogates(p.out)
}
impl Outline<'_> {
fn run(&mut self) {
while self.i < self.b.len() {
match self.b[self.i] {
b'{' => self.open_group(),
b'}' => self.close_group(),
b'\\' => {
self.i += 1;
if !self.escape() {
break;
while i < b.len() {
let c = b[i];
match c {
b'{' => {
stack.push(st);
i += 1;
// `{\*\dest …}`: an ignorable destination.
if b[i..].starts_with(b"\\*") {
st.skip = true;
}
}
b'}' => {
st = stack.pop().unwrap_or(St { skip: false, uc: 1 });
i += 1;
}
b'\\' => {
i += 1;
let Some(&n) = b.get(i) else { break };
if n.is_ascii_alphabetic() {
let s = i;
while i < b.len() && b[i].is_ascii_alphabetic() {
i += 1;
}
let word = &rtf[s..i];
let ns = i;
if i < b.len() && (b[i] == b'-' || b[i].is_ascii_digit()) {
i += 1;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
}
let arg: Option<i64> = rtf[ns..i].parse().ok();
if i < b.len() && b[i] == b' ' {
i += 1;
}
if pending_skip > 0 {
pending_skip -= 1;
continue;
}
if SKIP.contains(&word) {
st.skip = true;
}
if st.skip {
continue;
}
match word {
"par" => {
flush(&mut line, level, indent, &mut out);
}
"pard" => {
level = None;
indent = 0;
}
"outlinelevel" => level = arg.map(|a| a.clamp(0, 9) as u32),
"li" => indent = arg.unwrap_or(0),
"tab" | "line" => line.push(' '),
"uc" => st.uc = arg.unwrap_or(1).clamp(0, 8) as usize,
"u" => {
let v = arg.unwrap_or(0);
// RTF writes UTF-16 units as signed 16-bit values.
push_utf16(&mut line, (v & 0xffff) as u32);
pending_skip = 0;
// Skip the fallback characters.
let mut k = st.uc;
while k > 0 && i < b.len() {
if b[i] == b'\\' && b.get(i + 1) == Some(&b'\'') {
i = advance(rtf, i, 4);
} else if b[i] == b'{' || b[i] == b'}' || b[i] == b'\\' {
break;
} else {
i = advance(rtf, i, 1);
}
k -= 1;
}
}
"emdash" => line.push('—'),
"endash" => line.push('–'),
"bullet" => line.push('•'),
"lquote" => line.push('‘'),
"rquote" => line.push('’'),
"ldblquote" => line.push('“'),
"rdblquote" => line.push('”'),
_ => {}
}
} else if n == b'\'' {
let hex = rtf.get(i + 1..i + 3).filter(|h| h.bytes().all(|b| b.is_ascii_hexdigit())).and_then(|h| u8::from_str_radix(h, 16).ok());
i = advance(rtf, i, 3);
if !st.skip
&& let Some(h) = hex
&& h != 0
{
line.push(cp1252(h));
}
} else {
i += 1;
if st.skip {
continue;
}
match n {
b'\\' | b'{' | b'}' => line.push(n as char),
b'~' => line.push('\u{a0}'),
b'_' => line.push('\u{2011}'),
b'\n' | b'\r' => flush(&mut line, level, indent, &mut out),
_ => {}
}
}
b'\r' | b'\n' => self.i += 1,
_ => self.text(),
}
b'\r' | b'\n' => i += 1,
_ => {
let s = i;
while i < b.len() && !matches!(b[i], b'{' | b'}' | b'\\' | b'\r' | b'\n') {
i += 1;
}
if !st.skip
&& let Some(t) = rtf.get(s..i)
{
line.push_str(&t.replace('\u{0}', ""));
}
}
}
}
/// `{`: push the group; `{\*\dest …}` is an ignorable destination.
fn open_group(&mut self) {
self.stack.push(self.st);
self.i += 1;
if self.b[self.i..].starts_with(b"\\*") {
self.st.skip = true;
}
}
fn close_group(&mut self) {
self.st = self.stack.pop().unwrap_or(St { skip: false, uc: 1 });
self.i += 1;
}
/// The character after a backslash: a control word, a hex escape or a symbol. False at the
/// end of the input.
fn escape(&mut self) -> bool {
let Some(&n) = self.b.get(self.i) else { return false };
if n.is_ascii_alphabetic() {
self.control_word();
} else if n == b'\'' {
self.hex_escape();
} else {
self.symbol(n);
}
true
}
fn control_word(&mut self) {
let rtf = self.rtf;
let s = self.i;
while self.i < self.b.len() && self.b[self.i].is_ascii_alphabetic() {
self.i += 1;
}
let word = &rtf[s..self.i];
let ns = self.i;
if self.i < self.b.len() && (self.b[self.i] == b'-' || self.b[self.i].is_ascii_digit()) {
self.i += 1;
while self.i < self.b.len() && self.b[self.i].is_ascii_digit() {
self.i += 1;
}
}
let arg: Option<i64> = rtf[ns..self.i].parse().ok();
if self.i < self.b.len() && self.b[self.i] == b' ' {
self.i += 1;
}
if self.pending_skip > 0 {
self.pending_skip -= 1;
return;
}
if SKIP.contains(&word) {
self.st.skip = true;
}
if !self.st.skip {
self.apply(word, arg);
}
}
fn apply(&mut self, word: &str, arg: Option<i64>) {
match word {
"par" => self.flush(),
"pard" => {
self.level = None;
self.indent = 0;
}
"outlinelevel" => self.level = arg.map(|a| a.clamp(0, 9) as u32),
"li" => self.indent = arg.unwrap_or(0),
"tab" | "line" => self.line.push(' '),
"uc" => self.st.uc = arg.unwrap_or(1).clamp(0, 8) as usize,
"u" => self.unicode(arg.unwrap_or(0)),
"emdash" => self.line.push('—'),
"endash" => self.line.push('–'),
"bullet" => self.line.push('•'),
"lquote" => self.line.push('‘'),
"rquote" => self.line.push('’'),
"ldblquote" => self.line.push('“'),
"rdblquote" => self.line.push('”'),
_ => {}
}
}
/// `\uN`: one UTF-16 unit, then the `\uc` fallback characters, which are skipped.
fn unicode(&mut self, v: i64) {
// RTF writes UTF-16 units as signed 16-bit values.
push_utf16(&mut self.line, (v & 0xffff) as u32);
self.pending_skip = 0;
let mut k = self.st.uc;
while k > 0 && self.i < self.b.len() {
if self.b[self.i] == b'\\' && self.b.get(self.i + 1) == Some(&b'\'') {
self.i = advance(self.rtf, self.i, 4);
} else if matches!(self.b[self.i], b'{' | b'}' | b'\\') {
break;
} else {
self.i = advance(self.rtf, self.i, 1);
}
k -= 1;
}
}
/// `\'hh`: one cp1252 byte.
fn hex_escape(&mut self) {
let hex =
self.rtf.get(self.i + 1..self.i + 3).filter(|h| h.bytes().all(|b| b.is_ascii_hexdigit())).and_then(|h| u8::from_str_radix(h, 16).ok());
self.i = advance(self.rtf, self.i, 3);
if !self.st.skip
&& let Some(h) = hex
&& h != 0
{
self.line.push(cp1252(h));
}
}
/// A one-character control symbol (`\\`, `\{`, `\~`, ...).
fn symbol(&mut self, n: u8) {
self.i += 1;
if self.st.skip {
return;
}
match n {
b'\\' | b'{' | b'}' => self.line.push(n as char),
b'~' => self.line.push('\u{a0}'),
b'_' => self.line.push('\u{2011}'),
b'\n' | b'\r' => self.flush(),
_ => {}
}
}
/// Plain text up to the next special character.
fn text(&mut self) {
let s = self.i;
while self.i < self.b.len() && !matches!(self.b[self.i], b'{' | b'}' | b'\\' | b'\r' | b'\n') {
self.i += 1;
}
if !self.st.skip
&& let Some(t) = self.rtf.get(s..self.i)
{
self.line.push_str(&t.replace('\u{0}', ""));
}
}
/// Ends the current line: a non-empty line becomes one outline entry, indented by its level.
fn flush(&mut self) {
let text = self.line.trim();
if !text.is_empty() {
let n = self.level.map(|l| l as i64).unwrap_or(self.indent.saturating_add(180) / 360).clamp(0, 9) as usize;
self.out.push_str(&"\t".repeat(n));
self.out.push_str(text);
self.out.push('\n');
}
self.line.clear();
}
flush(&mut line, level, indent, &mut out);
finish_surrogates(out)
}
/// Push a UTF-16 unit; surrogate halves are parked as private markers (NUL plus four hex digits)
+31 -44
View File
@@ -204,48 +204,6 @@ impl Dpb {
entry_lists
}
/// picNumLXNoWrap (8-34, 8-35) of a short-term modification from the predictor `pred`, and the
/// picNum it selects.
fn short_term_pic_nums(pred: i32, idc: u32, value: u32, curr_pic_num: i32, max_pic_num: i32) -> (i32, i32) {
let d = value as i32 + 1;
let no_wrap = if idc == 0 {
if pred - d < 0 { pred - d + max_pic_num } else { pred - d }
} else if pred + d >= max_pic_num {
pred + d - max_pic_num
} else {
pred + d
};
let pn = if no_wrap > curr_pic_num { no_wrap - max_pic_num } else { no_wrap };
(no_wrap, pn)
}
/// Puts `target` at `ref_idx` and shifts the rest of the list up, then drops the later entries
/// that repeat it (8.2.4.3.1, 8.2.4.3.2) and keeps the list `n` long.
fn insert_ref<'a>(list: &mut Vec<Option<&'a DpbEntry>>, ref_idx: usize, n: usize, target: &'a DpbEntry, is_long: bool) {
for c in (ref_idx + 1..=n).rev() {
list[c] = list[c - 1];
}
list[ref_idx] = Some(target);
let mut nidx = ref_idx + 1;
for c in ref_idx + 1..=n {
let keep = match list[c] {
None => true,
Some(e) => {
if is_long {
!(e.mark == RefMark::Long && e.long_term_frame_idx == target.long_term_frame_idx)
} else {
!(e.mark == RefMark::Short && std::ptr::eq(e, target))
}
}
};
if keep {
list[nidx] = list[c];
nidx += 1;
}
}
list.truncate(n);
}
/// Reference picture list modification (8.2.4.3) of one list; `n` is num_ref_idx_active.
fn modify_ref_list<'a>(
list: &mut Vec<Option<&'a DpbEntry>>,
@@ -265,8 +223,16 @@ impl Dpb {
list.push(None); // temporarily one longer
let (target, is_long): (Option<&DpbEntry>, bool) = match m.idc {
0 | 1 => {
let (no_wrap, pn) = Self::short_term_pic_nums(pred, m.idc, m.value, curr_pic_num, max_pic_num);
let d = m.value as i32 + 1;
let no_wrap = if m.idc == 0 {
if pred - d < 0 { pred - d + max_pic_num } else { pred - d }
} else if pred + d >= max_pic_num {
pred + d - max_pic_num
} else {
pred + d
};
pred = no_wrap;
let pn = if no_wrap > curr_pic_num { no_wrap - max_pic_num } else { no_wrap };
(short.iter().copied().find(|e| Self::frame_num_wrap(e, cur_fn, max_frame_num) == pn), false)
}
2 => (long.iter().copied().find(|e| e.long_term_frame_idx == m.value), true),
@@ -278,8 +244,29 @@ impl Dpb {
ref_idx += 1;
continue;
};
Self::insert_ref(list, ref_idx, n, target, is_long);
for c in (ref_idx + 1..=n).rev() {
list[c] = list[c - 1];
}
list[ref_idx] = Some(target);
ref_idx += 1;
let mut nidx = ref_idx;
for c in ref_idx..=n {
let keep = match list[c] {
None => true,
Some(e) => {
if is_long {
!(e.mark == RefMark::Long && e.long_term_frame_idx == target.long_term_frame_idx)
} else {
!(e.mark == RefMark::Short && std::ptr::eq(e, target))
}
}
};
if keep {
list[nidx] = list[c];
nidx += 1;
}
}
list.truncate(n);
}
Ok(())
}
+32 -53
View File
@@ -74,39 +74,16 @@ impl<'a> SliceDecoder<'a> {
/// Merge mode motion (8.5.3.2.2).
pub fn derive_merge(&self, g: PbGeom, part: PartMode, merge_idx: usize) -> MvField {
let (w0, h0) = (g.w, g.h);
// With a parallel merge level above 4x4, an 8x8 coding block shares one candidate set.
let g = if self.pps.log2_parallel_merge_level > 2 && g.ncb == 8 {
PbGeom { xp: g.xc, yp: g.yc, w: g.ncb, h: g.ncb, part_idx: 0, ..g }
} else {
g
};
let max = self.sh.max_num_merge_cand as usize;
let (list, n) = self.spatial_merge_candidates(&g, part);
if merge_idx < n {
return self.finish_merge(list[merge_idx], w0, h0);
}
let mut cands: Vec<MvField> = list[..n].to_vec();
cands.extend(self.temporal_merge_candidate(g.xp, g.yp, g.w, g.h));
if merge_idx < cands.len() {
return self.finish_merge(cands[merge_idx], w0, h0);
}
self.combined_merge_candidates(&mut cands, max);
if merge_idx < cands.len() {
return self.finish_merge(cands[merge_idx], w0, h0);
}
self.zero_merge_candidates(&mut cands, merge_idx);
self.finish_merge(cands[merge_idx], w0, h0)
}
/// The spatial candidates A1, B1, B0, A0 and B2 (8.5.3.2.3): the candidates, and how many.
fn spatial_merge_candidates(&self, g: &PbGeom, part: PartMode) -> ([MvField; 5], usize) {
let PbGeom { xp, yp, w, h, part_idx, .. } = *g;
let PbGeom { xc, yc, ncb, xp: xp0, yp: yp0, w: w0, h: h0, part_idx: part_idx0 } = g;
let pml = self.pps.log2_parallel_merge_level;
let (xp, yp, w, h, part_idx) = if pml > 2 && ncb == 8 { (xc, yc, ncb, ncb, 0) } else { (xp0, yp0, w0, h0, part_idx0) };
let g = PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx };
let same_mer = |xn: i32, yn: i32| (xp >> pml) == (xn >> pml) && (yp >> pml) == (yn >> pml);
let avail = |xn: i32, yn: i32| self.pb_avail(g, xn, yn) && !same_mer(xn, yn);
let avail = |xn: i32, yn: i32| self.pb_avail(&g, xn, yn) && !same_mer(xn, yn);
let mut list: [MvField; 5] = [MvField::default(); 5];
let mut n = 0;
let max = self.sh.max_num_merge_cand as usize;
// spatial candidates (8.5.3.2.3)
let pa1 = (xp - 1, yp + h - 1);
let av_a1 = avail(pa1.0, pa1.1) && !(matches!(part, PartMode::PNx2N | PartMode::PnLx2N | PartMode::PnRx2N) && part_idx == 1);
let m_a1 = if av_a1 { self.mv_at(pa1.0, pa1.1) } else { MvField::default() };
@@ -153,11 +130,10 @@ impl<'a> SliceDecoder<'a> {
list[n] = self.mv_at(pb2.0, pb2.1);
n += 1;
}
(list, n)
}
/// The temporal candidate (8.5.3.2.8): list 0 from the collocated picture, and list 1 for B slices.
fn temporal_merge_candidate(&self, xp: i32, yp: i32, w: i32, h: i32) -> Option<MvField> {
if merge_idx < n {
return self.finish_merge(list[merge_idx], w0, h0);
}
// temporal candidate
let mut col = MvField::default();
let mut col_avail = false;
if let Some(mv) = self.temporal(xp, yp, w, h, 0, 0) {
@@ -172,28 +148,30 @@ impl<'a> SliceDecoder<'a> {
col.ref_idx[1] = 0;
col_avail = true;
}
col_avail.then_some(col)
}
/// Combined bi-predictive candidates (8.5.3.2.4) from pairs of the original candidates.
fn combined_merge_candidates(&self, cands: &mut Vec<MvField>, max: usize) {
let mut cands: Vec<MvField> = list[..n].to_vec();
if col_avail {
cands.push(col);
}
if merge_idx < cands.len() {
return self.finish_merge(cands[merge_idx], w0, h0);
}
// combined bi-predictive candidates (8.5.3.2.4)
let num_orig = cands.len();
if !(self.sh.is_b() && num_orig > 1 && num_orig < max) {
return;
}
let mut comb = 0;
while comb < num_orig * (num_orig - 1) && cands.len() < max {
let (i0, i1) = COMB[comb];
let (c0, c1) = (cands[i0], cands[i1]);
if c0.pred(0) && c1.pred(1) && (self.ref_pic(0, c0.ref_idx[0]).poc != self.ref_pic(1, c1.ref_idx[1]).poc || c0.mv[0] != c1.mv[1]) {
cands.push(MvField { mv: [c0.mv[0], c1.mv[1]], ref_idx: [c0.ref_idx[0], c1.ref_idx[1]] });
if self.sh.is_b() && num_orig > 1 && num_orig < max {
let mut comb = 0;
while comb < num_orig * (num_orig - 1) && cands.len() < max {
let (i0, i1) = COMB[comb];
let (c0, c1) = (cands[i0], cands[i1]);
if c0.pred(0) && c1.pred(1) && (self.ref_pic(0, c0.ref_idx[0]).poc != self.ref_pic(1, c1.ref_idx[1]).poc || c0.mv[0] != c1.mv[1]) {
cands.push(MvField { mv: [c0.mv[0], c1.mv[1]], ref_idx: [c0.ref_idx[0], c1.ref_idx[1]] });
}
comb += 1;
}
comb += 1;
}
}
/// Zero candidates (8.5.3.2.5) up to and including `merge_idx`.
fn zero_merge_candidates(&self, cands: &mut Vec<MvField>, merge_idx: usize) {
if merge_idx < cands.len() {
return self.finish_merge(cands[merge_idx], w0, h0);
}
// zero candidates (8.5.3.2.5)
let num_ref = if self.sh.is_b() { self.sh.num_ref_idx[0].min(self.sh.num_ref_idx[1]) } else { self.sh.num_ref_idx[0] } as usize;
let mut zero_idx = 0;
while cands.len() <= merge_idx {
@@ -201,6 +179,7 @@ impl<'a> SliceDecoder<'a> {
cands.push(MvField { mv: [[0; 2]; 2], ref_idx: [r, if self.sh.is_b() { r } else { -1 }] });
zero_idx += 1;
}
self.finish_merge(cands[merge_idx], w0, h0)
}
fn finish_merge(&self, mut f: MvField, w: i32, h: i32) -> MvField {
+79 -112
View File
@@ -277,30 +277,6 @@ pub struct SliceDecoder<'a> {
pub ctb_y: i32,
}
/// Scaling process (8.6.3): dequantises the top-left `max_x` × `max_y` coefficients of the `n` × `n`
/// block `coeffs` with scaling matrix `m` (flat when `None`).
fn dequantize(coeffs: &mut [i32], n: usize, (max_x, max_y): (usize, usize), m: Option<&[u8]>, scale: i64, round: i64, bd_shift: u32) {
for y in 0..=max_y {
for x in 0..=max_x {
let v = coeffs[y * n + x];
if v != 0 {
let f = m.map_or(16, |m| m[y * n + x] as i64);
coeffs[y * n + x] = ((v as i64 * f * scale + round) >> bd_shift).clamp(-32768, 32767) as i32;
}
}
}
}
/// Adds the `n` × `n` residual `coeffs` to the samples of `plane` at (x0, y0), clipped to 0..=`max`.
fn add_residual(plane: &mut [u16], stride: usize, (x0, y0): (usize, usize), n: usize, coeffs: &[i32], max: i32) {
for y in 0..n {
let row = &mut plane[(y0 + y) * stride + x0..][..n];
for (x, s) in row.iter_mut().enumerate() {
*s = (*s as i32 + coeffs[y * n + x]).clamp(0, max) as u16;
}
}
}
impl<'a> SliceDecoder<'a> {
#[inline(always)]
fn dd(&mut self, ctx: usize) -> u32 {
@@ -1355,7 +1331,15 @@ impl<'a> SliceDecoder<'a> {
}
_ => None,
};
dequantize(coeffs, n, (max_x, max_y), m, scale, round, bd_shift);
for y in 0..=max_y {
for x in 0..=max_x {
let v = coeffs[y * n + x];
if v != 0 {
let f = m.map_or(16, |m| m[y * n + x] as i64);
coeffs[y * n + x] = ((v as i64 * f * scale + round) >> bd_shift).clamp(-32768, 32767) as i32;
}
}
}
let bd_shift = 20 - bd;
if ts {
transform::transform_skip(coeffs, n, bd_shift);
@@ -1371,112 +1355,95 @@ impl<'a> SliceDecoder<'a> {
let _ = dc_only;
let (stride, max) = (if c == 0 { self.pic.width } else { self.pic.cwidth }, (1i32 << bd) - 1);
let plane = &mut self.pic.planes[c];
add_residual(plane, stride, (x0 as usize, y0 as usize), n, coeffs, max);
for y in 0..n {
let row = &mut plane[(y0 as usize + y) * stride + x0 as usize..][..n];
for (x, s) in row.iter_mut().enumerate() {
*s = (*s as i32 + coeffs[y * n + x]).clamp(0, max) as u16;
}
}
Ok(())
}
/// prediction_unit(): parse, derive motion, store it and predict. Returns merge_flag.
fn prediction_unit(&mut self, g: PbGeom, part: PartMode, skip: bool) -> Result<bool> {
let PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx } = g;
let merge = if skip { true } else { self.dd(MERGE_FLAG) == 1 };
let mvf = if merge {
let idx = self.merge_idx();
self.derive_merge(g, part, idx as usize)
let idx = if self.sh.max_num_merge_cand > 1 {
let mut i = 0;
if self.dd(MERGE_IDX) == 1 {
i = 1;
while i < self.sh.max_num_merge_cand - 1 && self.bypass() == 1 {
i += 1;
}
}
i
} else {
0
};
self.derive_merge(PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx }, part, idx as usize)
} else {
self.amvp_motion(g)?
let pred_idc = if self.sh.is_b() {
if w + h != 12 {
let depth = self.pic.blk_at(xc, yc).depth as usize;
if self.dd(INTER_PRED_IDC + depth) == 1 { 2 } else { self.dd(INTER_PRED_IDC + 4) }
} else {
self.dd(INTER_PRED_IDC + 4)
}
} else {
0
};
let mut ref_idx = [-1i8; 2];
let mut mvd = [[0i32; 2]; 2];
let mut mvp_flag = [0u32; 2];
for l in 0..2 {
let used = if l == 0 { pred_idc != 1 } else { pred_idc != 0 };
if !used {
continue;
}
let nref = self.sh.num_ref_idx[l];
let mut ri = 0;
if nref > 1 {
while ri < nref - 1 {
let bin = if ri < 2 { self.dd(REF_IDX + ri as usize) } else { self.bypass() };
if bin == 0 {
break;
}
ri += 1;
}
}
ref_idx[l] = ri as i8;
if l == 1 && self.sh.mvd_l1_zero && pred_idc == 2 {
mvd[1] = [0, 0];
} else {
mvd[l] = self.mvd_coding()?;
}
mvp_flag[l] = self.dd(MVP_FLAG);
}
let mut f = MvField { mv: [[0; 2]; 2], ref_idx };
for l in 0..2 {
if ref_idx[l] < 0 {
continue;
}
let mvp = self.derive_amvp(PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx }, l, ref_idx[l] as usize, mvp_flag[l] as usize);
// mvLX = (mvp + mvd) wrapped to 16 bits (8-94)
f.mv[l] = [(mvp[0] as i32 + mvd[l][0]) as i16, (mvp[1] as i32 + mvd[l][1]) as i16];
}
f
};
for l in 0..2 {
if mvf.ref_idx[l] >= 0 {
ensure!((mvf.ref_idx[l] as usize) < self.refs[l].len(), "reference index out of range");
}
}
self.store_motion(g.xp, g.yp, g.w, g.h, mvf);
self.motion_compensate(g.xp, g.yp, g.w as usize, g.h as usize, &mvf);
Ok(merge)
}
/// merge_idx(): the merge candidate index, truncated unary (0 when there is one candidate).
fn merge_idx(&mut self) -> u32 {
if self.sh.max_num_merge_cand <= 1 {
return 0;
}
let mut i = 0;
if self.dd(MERGE_IDX) == 1 {
i = 1;
while i < self.sh.max_num_merge_cand - 1 && self.bypass() == 1 {
i += 1;
}
}
i
}
/// inter_pred_idc: 0 = list 0, 1 = list 1, 2 = both.
fn inter_pred_idc(&mut self, xc: i32, yc: i32, w: i32, h: i32) -> u32 {
if !self.sh.is_b() {
return 0;
}
if w + h != 12 {
let depth = self.pic.blk_at(xc, yc).depth as usize;
if self.dd(INTER_PRED_IDC + depth) == 1 { 2 } else { self.dd(INTER_PRED_IDC + 4) }
} else {
self.dd(INTER_PRED_IDC + 4)
}
}
/// ref_idx_lX: truncated unary, the first two bins context-coded.
fn ref_idx(&mut self, l: usize) -> u32 {
let nref = self.sh.num_ref_idx[l];
let mut ri = 0;
if nref > 1 {
while ri < nref - 1 {
let bin = if ri < 2 { self.dd(REF_IDX + ri as usize) } else { self.bypass() };
if bin == 0 {
break;
}
ri += 1;
}
}
ri
}
/// The AMVP branch: parse each used list's reference index, mvd and predictor flag, then add
/// every mvd to its predictor.
fn amvp_motion(&mut self, g: PbGeom) -> Result<MvField> {
let pred_idc = self.inter_pred_idc(g.xc, g.yc, g.w, g.h);
let mut ref_idx = [-1i8; 2];
let mut mvd = [[0i32; 2]; 2];
let mut mvp_flag = [0u32; 2];
for l in 0..2 {
let used = if l == 0 { pred_idc != 1 } else { pred_idc != 0 };
if !used {
continue;
}
ref_idx[l] = self.ref_idx(l) as i8;
if l == 1 && self.sh.mvd_l1_zero && pred_idc == 2 {
mvd[1] = [0, 0];
} else {
mvd[l] = self.mvd_coding()?;
}
mvp_flag[l] = self.dd(MVP_FLAG);
}
let mut f = MvField { mv: [[0; 2]; 2], ref_idx };
for l in 0..2 {
if ref_idx[l] < 0 {
continue;
}
let mvp = self.derive_amvp(g, l, ref_idx[l] as usize, mvp_flag[l] as usize);
// mvLX = (mvp + mvd) wrapped to 16 bits (8-94)
f.mv[l] = [(mvp[0] as i32 + mvd[l][0]) as i16, (mvp[1] as i32 + mvd[l][1]) as i16];
}
Ok(f)
}
/// Writes a prediction block's motion into the picture's 4x4 motion field.
fn store_motion(&mut self, xp: i32, yp: i32, w: i32, h: i32, mvf: MvField) {
// store motion
let w4 = self.pic.w4;
for by in (yp >> 2)..((yp + h) >> 2) {
let o = by as usize * w4;
self.pic.mvf[o + (xp >> 2) as usize..o + ((xp + w) >> 2) as usize].fill(mvf);
}
self.motion_compensate(xp, yp, w as usize, h as usize, &mvf);
Ok(merge)
}
fn mvd_coding(&mut self) -> Result<[i32; 2]> {
+31 -51
View File
@@ -802,7 +802,6 @@ impl Chart {
/// The (min, max, major unit) of an axis over the data range [lo, hi]: `fmin`/`fmax`/`major`
/// where the chart fixes them; otherwise Office's automatic limits, rounded out to the smallest
/// 1-2-5 unit giving at most `max_intervals` intervals:
/// The data range [lo, hi] an axis covers, clamped to ±1e100; (0, 1) without finite values.
///
/// - **Maximum:** `hi + (hi − lo)/20` (with `headroom`), rounded up to the unit.
/// - **Minimum:** zero when every value is ≥ 0 and `lo` is below 5/6 of `hi`; otherwise
@@ -810,14 +809,22 @@ impl Chart {
/// near them.
/// - All-negative values mirror this: the axis ends at zero when `hi` is above 5/6 of `lo`.
/// Mixed signs get headroom at both ends.
fn axis_data_range(lo: f64, hi: f64) -> (f64, f64) {
if lo.is_finite() && hi.is_finite() { (lo.min(hi).clamp(-1e100, 1e100), lo.max(hi).clamp(-1e100, 1e100)) } else { (0.0, 1.0) }
}
/// Office's automatic (min, max) of the data range [lo, hi]. "How chart axis limits are determined",
/// as Jon Peltier summarises it: 5 % of the data range past the data, and zero when the values don't
/// sit within the top sixth of their largest magnitude. A single value is measured from zero.
fn auto_limits(lo: f64, hi: f64, headroom: bool) -> (f64, f64) {
pub fn axis_scale(
lo: f64,
hi: f64,
fmin: Option<f64>,
fmax: Option<f64>,
major: Option<f64>,
max_intervals: usize,
headroom: bool,
) -> (f64, f64, f64) {
// Values past ±1e100 aren't axis values; clamping keeps the arithmetic finite.
let fin = |v: Option<f64>| v.filter(|v| v.is_finite()).map(|v| v.clamp(-1e100, 1e100));
let (fmin, fmax) = (fin(fmin), fin(fmax));
let (lo, hi) = if lo.is_finite() && hi.is_finite() { (lo.min(hi).clamp(-1e100, 1e100), lo.max(hi).clamp(-1e100, 1e100)) } else { (0.0, 1.0) };
// Office's automatic limits ("How chart axis limits are determined", as Jon Peltier
// summarises it): 5 % of the data range past the data, and zero when the values don't
// sit within the top sixth of their largest magnitude. A single value is measured from zero.
let flat = hi - lo <= 1e-12 * lo.abs().max(hi.abs());
let pad = if !headroom {
0.0
@@ -828,23 +835,21 @@ fn auto_limits(lo: f64, hi: f64, headroom: bool) -> (f64, f64) {
};
let auto_lo = if lo >= 0.0 && (flat || lo < hi * 5.0 / 6.0) { 0.0 } else { lo - pad };
let auto_hi = if hi <= 0.0 && (flat || hi > lo * 5.0 / 6.0) { 0.0 } else { hi + pad };
(auto_lo, auto_hi)
}
/// The ends of a span too small to show: with one fixed limit on the far side of the data, the
/// automatic end gives way; otherwise the span is one `unit` wide.
fn flat_span_ends(fmin: Option<f64>, fmax: Option<f64>, lo: f64, hi: f64, unit: f64) -> (f64, f64) {
match (fmin, fmax) {
(None, Some(_)) => (if hi > 0.0 { 0.0 } else { hi - hi.abs().max(unit) }, hi),
(Some(_), None) if lo < 0.0 => (lo, 0.0),
_ => (lo, lo + unit),
let mut lo = fmin.unwrap_or(auto_lo);
let mut hi = fmax.unwrap_or(auto_hi);
let mag = lo.abs().max(hi.abs());
// Relative to the data's magnitude, so tiny values don't count as flat; all-zero falls back to 1.
let unit = if mag == 0.0 { 1.0 } else { mag };
if hi - lo <= 1e-12 * mag {
// A fixed limit on the far side of the data: the automatic end gives way.
match (fmin, fmax) {
(None, Some(_)) => lo = if hi > 0.0 { 0.0 } else { hi - hi.abs().max(unit) },
(Some(_), None) if lo < 0.0 => hi = 0.0,
_ => hi = lo + unit,
}
}
}
/// The major step: `major` when it gives at most 1000 intervals, else the smallest 1-2-5 step
/// that fits `n` intervals over [lo, hi].
fn axis_step(major: Option<f64>, lo: f64, hi: f64, n: f64, unit: f64) -> f64 {
match major.filter(|m| *m > 0.0 && (hi - lo) / m <= 1000.0) {
let n = max_intervals.clamp(1, 50) as f64;
let step = match fin(major).filter(|m| *m > 0.0 && (hi - lo) / m <= 1000.0) {
Some(m) => m,
None => {
let mut step = 10f64.powf(((hi - lo) / n).log10().floor());
@@ -860,32 +865,7 @@ fn axis_step(major: Option<f64>, lo: f64, hi: f64, n: f64, unit: f64) -> f64 {
}
step
}
}
}
pub fn axis_scale(
lo: f64,
hi: f64,
fmin: Option<f64>,
fmax: Option<f64>,
major: Option<f64>,
max_intervals: usize,
headroom: bool,
) -> (f64, f64, f64) {
// Values past ±1e100 aren't axis values; clamping keeps the arithmetic finite.
let fin = |v: Option<f64>| v.filter(|v| v.is_finite()).map(|v| v.clamp(-1e100, 1e100));
let (fmin, fmax) = (fin(fmin), fin(fmax));
let (lo, hi) = axis_data_range(lo, hi);
let (auto_lo, auto_hi) = auto_limits(lo, hi, headroom);
let (mut lo, mut hi) = (fmin.unwrap_or(auto_lo), fmax.unwrap_or(auto_hi));
let mag = lo.abs().max(hi.abs());
// Relative to the data's magnitude, so tiny values don't count as flat; all-zero falls back to 1.
let unit = if mag == 0.0 { 1.0 } else { mag };
if hi - lo <= 1e-12 * mag {
(lo, hi) = flat_span_ends(fmin, fmax, lo, hi, unit);
}
let n = max_intervals.clamp(1, 50) as f64;
let step = axis_step(fin(major), lo, hi, n, unit);
};
let min = fmin.unwrap_or((lo / step + 1e-9).floor() * step);
let max = fmax.unwrap_or((hi / step - 1e-9).ceil() * step).max(min + step);
(min, max, step)
+31 -43
View File
@@ -576,44 +576,6 @@ pub fn text_body(imp: &mut Imp, part: &Part, tx: &El) -> TextBody {
const MAX_PARAGRAPH_RUNS: usize = 10_000;
/// The run properties of `e`'s `rPr` child, or the defaults.
fn run_props(imp: &mut Imp, part: &Part, e: &El) -> RunProps {
e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default()
}
/// Appends the runs of an `a:r` text, where a vertical tab or line feed is a line break, as it
/// lays out and as export writes it.
fn push_text_runs(para: &mut Paragraph, props: RunProps, text: &str) {
for (i, piece) in text.split(['\u{b}', '\n']).enumerate() {
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
break;
}
if i > 0 {
para.runs.push(Run { text: String::new(), props: props.clone(), kind: RunKind::Break });
}
if !piece.is_empty() {
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
break;
}
para.runs.push(Run { text: piece.to_string(), props: props.clone(), kind: RunKind::Text });
}
}
}
/// Appends an Office Math (a14:m) run: its plain text and its OMML.
fn push_math_run(para: &mut Paragraph, part: &Part, e: &El) {
if para.runs.len() < MAX_PARAGRAPH_RUNS {
let mut text = String::new();
let mut ts = vec![];
e.find_all("t", &mut ts);
for t in ts {
text.push_str(&t.text());
}
let omml = e.elements().map(|c| part.keep_inner(c)).collect::<String>();
para.runs.push(Run { text, props: RunProps::default(), kind: RunKind::Math { omml } });
}
}
pub fn paragraph(imp: &mut Imp, part: &Part, p: &El) -> Paragraph {
let mut para = Paragraph::default();
for e in p.elements() {
@@ -627,24 +589,50 @@ pub fn paragraph(imp: &mut Imp, part: &Part, p: &El) -> Paragraph {
}
}
"r" => {
let props = run_props(imp, part, e);
let props = e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default();
let text: String = e.child("t").map(|t| t.text()).unwrap_or_default();
push_text_runs(&mut para, props, &text);
// A vertical tab or line feed inside text is a line break, as it lays out and as export writes it.
for (i, piece) in text.split(['\u{b}', '\n']).enumerate() {
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
break;
}
if i > 0 {
para.runs.push(Run { text: String::new(), props: props.clone(), kind: RunKind::Break });
}
if !piece.is_empty() {
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
break;
}
para.runs.push(Run { text: piece.to_string(), props: props.clone(), kind: RunKind::Text });
}
}
}
"br" => {
if para.runs.len() < MAX_PARAGRAPH_RUNS {
let props = run_props(imp, part, e);
let props = e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default();
para.runs.push(Run { text: String::new(), props, kind: RunKind::Break });
}
}
"fld" => {
if para.runs.len() < MAX_PARAGRAPH_RUNS {
let props = run_props(imp, part, e);
let props = e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default();
let text = e.child("t").map(|t| t.text()).unwrap_or_default();
para.runs.push(Run { text, props, kind: RunKind::Field { field: e.attr("type").unwrap_or("").to_string() } });
}
}
"m" => push_math_run(&mut para, part, e),
"m" => {
if para.runs.len() < MAX_PARAGRAPH_RUNS {
// Office Math (a14:m) — keep the OMML and its plain text.
let mut text = String::new();
let mut ts = vec![];
e.find_all("t", &mut ts);
for t in ts {
text.push_str(&t.text());
}
let omml = e.elements().map(|c| part.keep_inner(c)).collect::<String>();
para.runs.push(Run { text, props: RunProps::default(), kind: RunKind::Math { omml } });
}
}
"endParaRPr" => para.end_props = rpr(imp, part, e),
_ => {}
}