feat(draw): compare template + aspect-fit image painting (T-319)
compareTemplateHandler lowers an `images` array to an SVG of side-by-side <image> cells, each with the per-object data-label/description caption and data-lightbox; paths resolve to absolute up front (injected, honest error on a miss). _paintImage now aspect-fits (contain, centered) so cells of differing-shape images don't distort. Flutter-free handler + painter pixel tests. Card image-loading + registration next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import 'package:clide/src/draw/compare_template.dart';
|
||||
import 'package:clide/src/draw/draw_dispatch.dart';
|
||||
import 'package:clide/src/draw/draw_doc.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
DrawingCardDoc doc(List<Object?> images) => DrawingCardDoc(template: 'compare', fields: {'template': 'compare', 'images': images});
|
||||
|
||||
// Resolver: prefix with /abs; null for a path named "missing.png".
|
||||
final handler = compareTemplateHandler(resolvePath: (p) => p == 'missing.png' ? null : '/abs/$p');
|
||||
|
||||
test('lays out N image cells with hrefs, captions, and lightbox', () async {
|
||||
final r = await handler(
|
||||
doc([
|
||||
{'path': 'before.png', 'label': 'Before', 'description': 'cramped'},
|
||||
{'path': 'after.png', 'label': 'After'},
|
||||
]),
|
||||
);
|
||||
final svg = (r as DrawOk).svg;
|
||||
expect(svg, contains('href="/abs/before.png"'));
|
||||
expect(svg, contains('href="/abs/after.png"'));
|
||||
expect(svg, contains('data-label="Before"'));
|
||||
expect(svg, contains('data-description="cramped"'));
|
||||
expect(svg, contains('data-lightbox=""'));
|
||||
expect(svg, contains('x="340"')); // second cell offset by 320 + 20 gap
|
||||
expect(svg, contains('viewBox="0 0 660 296"')); // 2*320+20 wide, 240+56 tall
|
||||
});
|
||||
|
||||
test('an unresolved path is an honest error', () async {
|
||||
final r = await handler(
|
||||
doc([
|
||||
{'path': 'missing.png'},
|
||||
]),
|
||||
);
|
||||
expect((r as DrawErr).message, contains('no such image'));
|
||||
});
|
||||
|
||||
test('empty or missing images is an honest error', () async {
|
||||
expect(await handler(doc(const [])), isA<DrawErr>());
|
||||
expect(await handler(DrawingCardDoc(template: 'compare', fields: const {'template': 'compare'})), isA<DrawErr>());
|
||||
});
|
||||
|
||||
test('a non-object entry is an honest error', () async {
|
||||
expect(await handler(doc(const ['notanobject'])), isA<DrawErr>());
|
||||
});
|
||||
|
||||
test('escapes special characters in paths and labels', () async {
|
||||
final h = compareTemplateHandler(resolvePath: (p) => '/abs/$p');
|
||||
final r = await h(
|
||||
doc([
|
||||
{'path': 'a&b.png', 'label': '<x>'},
|
||||
]),
|
||||
);
|
||||
final svg = (r as DrawOk).svg;
|
||||
expect(svg, contains('a&b.png'));
|
||||
expect(svg, contains('data-label="<x>"'));
|
||||
});
|
||||
}
|
||||
@@ -110,6 +110,18 @@ void main() {
|
||||
expect(alpha(await argbAt(img, 5, 5)), 0);
|
||||
});
|
||||
|
||||
test('a wide image is aspect-fit (contain) into a square cell, centered (T-319)', () async {
|
||||
// 8x2 source into a 10x10 cell → scaled to 10x2.5, centered vertically
|
||||
// (rows ~3.75–6.25). The center has the image; the top/bottom margins stay
|
||||
// empty — proving contain-fit, not a stretched fill.
|
||||
final wide = await solidImage(8, 2, 0xFF00FF00);
|
||||
const svg = '<svg viewBox="0 0 10 10"><image x="0" y="0" width="10" height="10" href="pic"/></svg>';
|
||||
final img = await render(svg, 10, 10, images: (href) => href == 'pic' ? wide : null);
|
||||
expect(await argbAt(img, 5, 5), 0xFF00FF00); // center: image
|
||||
expect(alpha(await argbAt(img, 5, 0)), 0); // top margin: empty
|
||||
expect(alpha(await argbAt(img, 5, 9)), 0); // bottom margin: empty
|
||||
});
|
||||
|
||||
test('renders the real d2 fixture without error and draws ink', () async {
|
||||
final svg = File('test/svg/fixtures/d2_pipeline.svg').readAsStringSync();
|
||||
final img = await render(svg, 200, 120);
|
||||
|
||||
Reference in New Issue
Block a user