Skip to content

Commit

Permalink
Merge pull request #422 from jahow/make-addimage-async
Browse files Browse the repository at this point in the history
PNG output / Make sure rule icons are correctly drawn
  • Loading branch information
jansule authored Jan 2, 2023
2 parents 64bac81 + f1a92c2 commit bd47184
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 52 deletions.
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

0 comments on commit bd47184

Please sign in to comment.