Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PNG output / Make sure rule icons are correctly drawn #422

Merged
merged 3 commits into from
Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ module.exports = {
'\\.(ts)$': '<rootDir>/node_modules/babel-jest'
},
coverageDirectory: '<rootDir>/coverage',
testEnvironment: 'jsdom'
testEnvironment: 'jsdom',
testEnvironmentOptions: {
resources: "usable"
}
};
2 changes: 1 addition & 1 deletion src/LegendRenderer/AbstractOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ abstract class AbstractOutput {
x: number|string,
y: number|string,
drawRect: boolean,
): void;
): Promise<void>;
abstract generate(finalHeight: number): Element;
}
export default AbstractOutput;
6 changes: 3 additions & 3 deletions src/LegendRenderer/LegendRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ class LegendRenderer {
if (item.rule) {
output.useContainer(item.title);
return this.getRuleIcon(item.rule)
.then((uri) => {
output.addImage(uri, ...iconSize, position[0] + 1, position[1], !hideRect);
.then(async (uri) => {
await output.addImage(uri, ...iconSize, position[0] + 1, position[1], !hideRect);
output.addLabel(item.title, position[0] + iconSize[0] + 5, position[1] + 20);
position[1] += iconSize[1] + 5;
if (maxColumnHeight && position[1] + iconSize[1] + 5 >= maxColumnHeight) {
Expand Down Expand Up @@ -279,7 +279,7 @@ class LegendRenderer {
output.addTitle(legendTitle, ...position);
position[1] += titleSpacing;
}
output.addImage(base64.toString(), img.width, img.height,...position, false);
await output.addImage(base64.toString(), img.width, img.height,...position, false);

position[1] += img.height;
} catch (err) {
Expand Down
38 changes: 17 additions & 21 deletions src/LegendRenderer/PngOutput.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

import PngOutput from './PngOutput';
import {
makeSampleOutput,
makeSampleOutput, SAMPLE_IMAGE_SRC,
SAMPLE_OUTPUT_FINAL_HEIGHT,
SAMPLE_PNG_EVENTS,
SAMPLE_PNG_EVENTS_HEIGHT_TOO_LOW
} from '../fixtures/outputs';

function instrumentContext(context: CanvasRenderingContext2D) {
context.drawImage = jest.fn(context.drawImage) as any;
context.fillText = jest.fn(context.fillText) as any;
context.strokeRect = jest.fn(context.strokeRect) as any;
}

function getContextEvents(context: CanvasRenderingContext2D) {
// eslint-disable-next-line no-underscore-dangle
return (context as any).__getEvents();
Expand All @@ -29,7 +23,9 @@ describe('PngOutput', () => {
describe('individual actions', () => {
beforeEach(() => {
output = new PngOutput([500, 700], null, null);
instrumentContext(output.context);
jest.spyOn(output.context, 'drawImage');
jest.spyOn(output.context, 'fillText');
jest.spyOn(output.context, 'strokeRect');
});

describe('#addTitle', () => {
Expand All @@ -47,29 +43,29 @@ describe('PngOutput', () => {
});
});
describe('#addImage', () => {
it('inserts an image (no frame)', () => {
output.addImage('bla', 100, 50, 200, 250, false);
it('inserts an image (no frame)', async () => {
await output.addImage(SAMPLE_IMAGE_SRC, 100, 50, 200, 250, false);
const calledImg = (output.context.drawImage as any).mock.calls[0][0];
expect(output.context.drawImage).toHaveBeenCalledWith(expect.any(Image), 200, 250, 100, 50);
expect(calledImg.src).toBe('http://localhost/bla');
expect(calledImg.src).toBe(SAMPLE_IMAGE_SRC);
expect(output.context.strokeRect).not.toHaveBeenCalled();
expect(output.context.strokeStyle).toEqual('#000000');
});
it('inserts an image (with frame)', () => {
output.addImage('bla', 100, 50, 200, 250, true);
it('inserts an image (with frame)', async() => {
await output.addImage(SAMPLE_IMAGE_SRC, 100, 50, 200, 250, true);
const calledImg = (output.context.drawImage as any).mock.calls[0][0];
expect(output.context.drawImage).toHaveBeenCalledWith(expect.any(Image), 200, 250, 100, 50);
expect(calledImg.src).toBe('http://localhost/bla');
expect(calledImg.src).toBe(SAMPLE_IMAGE_SRC);
expect(output.context.strokeRect).toHaveBeenCalledWith(200, 250, 100, 50);
expect(output.context.strokeStyle).toEqual('#000000');
});
});
});

describe('without column constraints', () => {
beforeEach(() => {
beforeEach(async () => {
output = new PngOutput([500, 700], null, null);
makeSampleOutput(output);
await makeSampleOutput(output);
});
it('generates the right output', () => {
const canvas = output.generate(SAMPLE_OUTPUT_FINAL_HEIGHT);
Expand All @@ -78,20 +74,20 @@ describe('PngOutput', () => {
});

describe('with column constraints', () => {
beforeEach(() => {
beforeEach(async () => {
output = new PngOutput([500, 700], 50, 200);
makeSampleOutput(output);
await makeSampleOutput(output);
});
it('generates the same output as without constraints', () => {
const canvas = output.generate(SAMPLE_OUTPUT_FINAL_HEIGHT);
expect(getContextEvents(canvas.getContext('2d')!)).toEqual(SAMPLE_PNG_EVENTS);
});
});

describe('with a height too low', () => {
beforeEach(() => {
describe('with a height too low', () => {
beforeEach(async () => {
output = new PngOutput([500, 200], 50, 200);
makeSampleOutput(output);
await makeSampleOutput(output);
});
it('resizes the final canvas', () => {
const canvas = output.generate(SAMPLE_OUTPUT_FINAL_HEIGHT);
Expand Down
4 changes: 3 additions & 1 deletion src/LegendRenderer/PngOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class PngOutput extends AbstractOutput {
this.context.fillText(text, cssDimensionToPx(x), cssDimensionToPx(y));
}

addImage(
async addImage(
dataUrl: string,
imgWidth: number,
imgHeight: number,
Expand All @@ -52,7 +52,9 @@ export default class PngOutput extends AbstractOutput {
const yPx = cssDimensionToPx(y);
this.expandHeight(yPx + imgHeight);
const image = new Image();
const imageLoaded = new Promise(resolve => image.onload = resolve);
image.src = dataUrl;
await imageLoaded;
this.context.drawImage(image, xPx, yPx, imgWidth, imgHeight);
if (drawRect) {
this.context.strokeStyle = '1px solid black';
Expand Down
12 changes: 6 additions & 6 deletions src/LegendRenderer/SvgOutput.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,29 +95,29 @@ describe('SvgOutput', () => {
});

describe('without column constraints', () => {
beforeEach(() => {
beforeEach(async () => {
output = new SvgOutput([500, 700], undefined, undefined);
makeSampleOutput(output);
await makeSampleOutput(output);
});
it('generates the right output', () => {
expect(output.generate(SAMPLE_OUTPUT_FINAL_HEIGHT).outerHTML).toEqual(SAMPLE_SVG);
});
});

describe('with column constraints', () => {
beforeEach(() => {
beforeEach(async () => {
output = new SvgOutput([500, 700], 50, 200);
makeSampleOutput(output);
await makeSampleOutput(output);
});
it('generates the right output', () => {
expect(output.generate(SAMPLE_OUTPUT_FINAL_HEIGHT).outerHTML).toEqual(SAMPLE_SVG_COLUMN_CONSTRAINTS);
});
});

describe('with a height too low', () => {
beforeEach(() => {
beforeEach(async () => {
output = new SvgOutput([500, 200], 50, 200);
makeSampleOutput(output);
await makeSampleOutput(output);
});
it('sets the height on the final canvas', () => {
expect(output.generate(SAMPLE_OUTPUT_FINAL_HEIGHT).outerHTML).toEqual(SAMPLE_SVG_COLUMN_CONSTRAINTS
Expand Down
1 change: 1 addition & 0 deletions src/LegendRenderer/SvgOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default class SvgOutput extends AbstractOutput {
.attr('height', imgHeight)
.attr('href', dataUrl);
this.root.attr('xmlns', 'http://www.w3.org/2000/svg');
return Promise.resolve()
};

generate(finalHeight: number) {
Expand Down
42 changes: 23 additions & 19 deletions src/fixtures/outputs.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import AbstractOutput from '../LegendRenderer/AbstractOutput';

export function makeSampleOutput(output: AbstractOutput) {
// a single pixel
// eslint-disable-next-line max-len
export const SAMPLE_IMAGE_SRC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjyHNz+g8ABBIB9kHiDqIAAAAASUVORK5CYII=';

export async function makeSampleOutput(output: AbstractOutput) {
output.useContainer('My Container');
output.addTitle('Inside a container', 180, 180);
output.addLabel('An image in a container', 200, 220);
output.addImage('https://my-domain/image.png', 100, 50, 200, 250, false);
await output.addImage(SAMPLE_IMAGE_SRC, 100, 50, 200, 250, false);
output.useRoot();
output.addTitle('Outside a container', 180, 480);
output.addLabel('An image', 200, 520);
output.addImage('https://my-domain/image2.png', 100, 50, 200, 550, true);
await output.addImage(SAMPLE_IMAGE_SRC, 100, 50, 200, 550, true);
}

export const SAMPLE_OUTPUT_FINAL_HEIGHT = 600;
Expand All @@ -21,14 +25,14 @@ export const SAMPLE_SVG =
'<text class="legend-title" text-anchor="start" dx="180" dy="180">Inside a container</text>' +
'</g>' +
'<text x="200" y="220">An image in a container</text>' +
'<image x="200" y="250" width="100" height="50" href="https://my-domain/image.png"></image>' +
'<image x="200" y="250" width="100" height="50" href="' + SAMPLE_IMAGE_SRC + '"></image>' +
'</g>' +
'<g>' +
'<text class="legend-title" text-anchor="start" dx="180" dy="480">Outside a container</text>' +
'</g>' +
'<text x="200" y="520">An image</text>' +
'<rect x="200" y="550" width="100" height="50" style="fill-opacity: 0; stroke: black;"></rect>' +
'<image x="200" y="550" width="100" height="50" href="https://my-domain/image2.png"></image>' +
'<image x="200" y="550" width="100" height="50" href="' + SAMPLE_IMAGE_SRC + '"></image>' +
'</svg>';

// max column width: 50, max column height: 200
Expand All @@ -40,14 +44,14 @@ export const SAMPLE_SVG_COLUMN_CONSTRAINTS =
'<text class="legend-title" text-anchor="start" dx="180" dy="180">Insid...</text>' +
'</g>' +
'<text x="200" y="220">An image in a container</text>' +
'<image x="200" y="250" width="100" height="50" href="https://my-domain/image.png"></image>' +
'<image x="200" y="250" width="100" height="50" href="' + SAMPLE_IMAGE_SRC + '"></image>' +
'</g>' +
'<g>' +
'<text class="legend-title" text-anchor="start" dx="180" dy="480">Outside a container</text>' +
'</g>' +
'<text x="200" y="520">An image</text>' +
'<rect x="200" y="550" width="100" height="50" style="fill-opacity: 0; stroke: black;"></rect>' +
'<image x="200" y="550" width="100" height="50" href="https://my-domain/image2.png"></image>' +
'<image x="200" y="550" width="100" height="50" href="' + SAMPLE_IMAGE_SRC + '"></image>' +
'</svg>';

// as reported by jest-canvas-mock
Expand Down Expand Up @@ -102,13 +106,13 @@ export const SAMPLE_PNG_EVENTS = [
},
{
'props': {
'dHeight': 0,
'dWidth': 0,
'dHeight': 1,
'dWidth': 1,
'dx': 200,
'dy': 250,
'img': expect.any(Image),
'sHeight': 0,
'sWidth': 0,
'sHeight': 1,
'sWidth': 1,
'sx': 0,
'sy': 0
},
Expand Down Expand Up @@ -158,13 +162,13 @@ export const SAMPLE_PNG_EVENTS = [
},
{
'props': {
'dHeight': 0,
'dWidth': 0,
'dHeight': 1,
'dWidth': 1,
'dx': 200,
'dy': 550,
'img': expect.any(Image),
'sHeight': 0,
'sWidth': 0,
'sHeight': 1,
'sWidth': 1,
'sx': 0,
'sy': 0
},
Expand Down Expand Up @@ -237,13 +241,13 @@ export const SAMPLE_PNG_EVENTS_HEIGHT_TOO_LOW = [
},
{
'props': {
'dHeight': 0,
'dWidth': 0,
'dHeight': 1,
'dWidth': 1,
'dx': 200,
'dy': 550,
'img': expect.any(Image),
'sHeight': 0,
'sWidth': 0,
'sHeight': 1,
'sWidth': 1,
'sx': 0,
'sy': 0
},
Expand Down