mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-22 02:56:52 +03:00
Fix review crop integrity and support repeated component decisions
This commit is contained in:
@@ -97,10 +97,10 @@ fn render_page(
|
||||
coords[3] * height as f64,
|
||||
];
|
||||
if let Some(isolated) = &isolated {
|
||||
let b = &isolated["bounds"];
|
||||
let b = &isolated["paintBounds"];
|
||||
let (x, y, w, h) = (b["x"].as_f64().unwrap(), b["y"].as_f64().unwrap(), b["width"].as_f64().unwrap(), b["height"].as_f64().unwrap());
|
||||
if x >= clip[0] + clip[2] || y >= clip[1] + clip[3] || x + w <= clip[0] || y + h <= clip[1] {
|
||||
return Err("component target does not intersect its measured box".into());
|
||||
if x < clip[0] - 1. || y < clip[1] - 1. || x + w > clip[0] + clip[2] + 1. || y + h > clip[1] + clip[3] + 1. {
|
||||
return Err(format!("{}: review crop clips component content. Measured crop [{:.1}, {:.1}, {:.1}, {:.1}], visible content [{x:.1}, {y:.1}, {w:.1}, {h:.1}]. Check the reference region and component layout before asking the user to review it.", isolated["selector"].as_str().unwrap_or("component"), clip[0], clip[1], clip[2], clip[3]));
|
||||
}
|
||||
}
|
||||
let first = page
|
||||
@@ -390,6 +390,19 @@ mod tests {
|
||||
assert_eq!(impeccable_comp::png_io::decode_png(&crop).unwrap().image.data,pixels.data);
|
||||
}
|
||||
#[test]
|
||||
#[ignore = "requires Chromium"]
|
||||
fn isolated_capture_refuses_overflow_clipped_by_review_box() {
|
||||
let image=impeccable_comp::raster::create_image(200,100,[255,255,255,255]);
|
||||
let reference=impeccable_comp::png_io::encode_png(&image,&[]).unwrap();
|
||||
let html=br#"<!doctype html><style>html,body{margin:0}#nav{width:60px;height:40px}span{display:block;width:150px;height:20px;background:red}</style><nav id="nav"><span></span></nav>"#;
|
||||
let inputs=BTreeMap::from([("comp.png".into(),reference),("kit.html".into(),html.to_vec())]);
|
||||
let original=json!({"schemaVersion":2,"stage":"components","comp":{"url":"/files/comp.png","width":200,"height":100},"components":[{"id":"nav","box":{"x":0,"y":0,"w":0.3,"h":0.4},"preview":{"kind":"page","url":"/files/kit.html","selector":"#nav"},"dependencies":[]}]});
|
||||
let error=NativeComponentCapturer.capture(&mut original.clone(),&inputs).err().unwrap();
|
||||
assert!(error.contains("review crop clips component content"),"{error}");
|
||||
let mut valid=original;valid["components"][0]["box"]["w"]=json!(0.75);
|
||||
NativeComponentCapturer.capture(&mut valid,&inputs).unwrap();
|
||||
}
|
||||
#[test]
|
||||
fn component_crops_copy_verified_pixels_without_resizing_or_synthetic_edges() {
|
||||
let image = impeccable_comp::raster::Image {width:3,height:2,data:(0u8..24).collect()};
|
||||
let png = impeccable_comp::png_io::encode_png(&image, &[]).unwrap();
|
||||
|
||||
@@ -27,6 +27,28 @@
|
||||
main: owns ? computed.visibility : 'hidden',
|
||||
pseudo: ['::before','::after','::marker'].map(pseudo => owns ? getComputedStyle(element, pseudo).visibility : 'hidden') };
|
||||
});
|
||||
// Record visible layout/text extents before isolation. A small wrapper can
|
||||
// have overflowing children; its border box alone would certify a clipped crop.
|
||||
let paint = {left:rect.left,top:rect.top,right:rect.right,bottom:rect.bottom};
|
||||
function include(bounds, element) {
|
||||
let b = {left:bounds.left,top:bounds.top,right:bounds.right,bottom:bounds.bottom};
|
||||
for(let parent=element;parent;parent=parent.parentElement){
|
||||
const style=getComputedStyle(parent), clip=parent.getBoundingClientRect();
|
||||
if(/hidden|clip|scroll|auto/.test(style.overflowX)){b.left=Math.max(b.left,clip.left);b.right=Math.min(b.right,clip.right);}
|
||||
if(/hidden|clip|scroll|auto/.test(style.overflowY)){b.top=Math.max(b.top,clip.top);b.bottom=Math.min(b.bottom,clip.bottom);}
|
||||
}
|
||||
if(b.right<=b.left||b.bottom<=b.top)return;
|
||||
paint={left:Math.min(paint.left,b.left),top:Math.min(paint.top,b.top),right:Math.max(paint.right,b.right),bottom:Math.max(paint.bottom,b.bottom)};
|
||||
}
|
||||
elements.forEach((element,index)=>{
|
||||
if(!visibility[index].owns || visibility[index].main!=='visible')return;
|
||||
include(element.getBoundingClientRect(),element.parentElement);
|
||||
for(const node of element.childNodes){
|
||||
if(node.nodeType!==Node.TEXT_NODE||!node.textContent.trim())continue;
|
||||
const range=document.createRange();range.selectNodeContents(node);
|
||||
for(const bounds of range.getClientRects())include(bounds,element);
|
||||
}
|
||||
});
|
||||
// Apply only after reading all original computed styles. Descendant components
|
||||
// keep their layout space but cannot paint inside their parent's preview.
|
||||
if (document.querySelector('[data-impeccable-capture]')) throw Error('Reserved capture attribute is already present.');
|
||||
@@ -55,5 +77,6 @@
|
||||
const after = selected.element.getBoundingClientRect();
|
||||
if (['x','y','width','height'].some(key => Math.abs(rect[key] - after[key]) > .01)) throw Error('Isolating the component changed its layout.');
|
||||
return { method: 'dom-component-v1', selector: selected.selector, excludedComponents: otherRoots.map(root => root.id),
|
||||
bounds: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
|
||||
bounds: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
||||
paintBounds: {x:paint.left,y:paint.top,width:paint.right-paint.left,height:paint.bottom-paint.top} };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user