@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
403 lines (324 loc) • 10.9 kB
JavaScript
import {
geometry as geom,
drawing as draw
} from '@progress/kendo-drawing';
import {
addClass,
setDefaultOptions,
deepExtend,
defaultErrorHandler
} from '../common';
import { Box } from '../core';
import { encodeData } from './encodings/encoding';
import { extend } from './utils';
import { surfaceSize } from '../barcode/surface-size';
const round = Math.round;
const crossPattern = [[0, 1], [1, 1], [1, 2], [2, 2], [2, 1], [3, 1], [3, 0], [2, 0], [2, -1], [1, -1], [1, 0]];
const squarePattern = [[0, 1], [1, 1], [1, 0]];
const QRCodeDefaults = {
DEFAULT_SIZE: 200,
QUIET_ZONE_LENGTH: 4,
DEFAULT_ERROR_CORRECTION_LEVEL: "L",
DEFAULT_BACKGROUND: "#fff",
DEFAULT_DARK_MODULE_COLOR: "#000",
MIN_BASE_UNIT_SIZE: 1,
DEFAULT_LOGO_SIZE: 7
};
class QRCode {
constructor(element, options, errorHandler = defaultErrorHandler) {
this.options = deepExtend({}, this.options, options);
this.element = element;
this.wrapper = this.element;
this.onError = errorHandler;
this._initElement();
this._initSurface();
this.setOptions(options);
}
destroy() {
this._destroySurface();
}
_initElement() {
addClass(this.element, "k-qrcode");
}
_initSurface() {
const { options, surface } = this;
if (!surface || surface.options.type !== options.renderAs) {
this._destroySurface();
this._initSurfaceElement();
this.surface = this._createSurface();
}
}
_createSurface() {
return draw.Surface.create(this.surfaceElement, {
type: this.options.renderAs
});
}
_destroySurface() {
if (this.surface) {
this.surface.destroy();
this.surface = null;
this._destroySurfaceElement();
}
}
_initSurfaceElement() {
if (!this.surfaceElement) {
this.surfaceElement = document.createElement('div');
this.surfaceElement.style.position = "relative";
this.element.appendChild(this.surfaceElement);
}
}
_destroySurfaceElement() {
if (this.surfaceElement && this.surfaceElement.parentNode) {
this.surfaceElement.parentNode.removeChild(this.surfaceElement);
this.surfaceElement = null;
}
}
redraw() {
let size = this._getSize();
this.surface.clear();
this.surface.setSize({
width: size,
height: size
});
this.createVisual();
this.surface.draw(this.visual);
}
getSize() {
const element = this.element;
const elementWidth = element.clientWidth;
const elementHeight = element.clientHeight;
const size = { width: 0, height: 0 };
if (elementWidth > 0) {
size.width = elementWidth;
}
if (elementHeight) {
size.height = elementHeight;
}
return size;
}
_resize() {
this.redraw();
}
createVisual() {
this.visual = this._render();
}
exportVisual() {
return this._render();
}
_render() {
let value = this._value,
baseUnit,
border = this.options.border || {},
padding = this.options.padding || 0,
borderWidth = border.width || 0,
quietZoneSize,
matrix,
size,
dataSize,
contentSize;
border.width = borderWidth;
let visual = new draw.Group();
try {
if (value) {
matrix = encodeData(value, this.options.errorCorrection, this.options.encoding);
size = this._getSize();
contentSize = size - 2 * (borderWidth + padding);
baseUnit = this._calculateBaseUnit(contentSize, matrix.length);
dataSize = matrix.length * baseUnit;
quietZoneSize = borderWidth + padding + (contentSize - dataSize) / 2;
visual.append(this._renderBackground(size, border));
visual.append(this._renderMatrix(matrix, baseUnit, quietZoneSize));
if (this._hasCustomLogo()) {
visual.append(this._renderLogo(size, baseUnit));
} else if (this._isSwiss()) {
visual.append(this._renderSwissCode(size, baseUnit));
}
}
} catch (error) {
this.onError(error);
}
return visual;
}
_renderLogo(qrSize, baseUnit) {
let image;
let imageRect;
let center = round(qrSize / 2);
let logoSize = this._getLogoSize(baseUnit * QRCodeDefaults.DEFAULT_LOGO_SIZE);
let logoUrl = this.options.overlay.imageUrl;
let position = {
x: center - logoSize.width / 2,
y: center - logoSize.height / 2
};
imageRect = new geom.Rect(
new geom.Point(position.x, position.y),
new geom.Size(logoSize.width, logoSize.height)
);
image = new draw.Image(logoUrl, imageRect);
return image;
}
_renderSwissCode(qrSize, baseUnit) {
let logoSize = this._getLogoSize(baseUnit * QRCodeDefaults.DEFAULT_LOGO_SIZE);
logoSize = Math.max(logoSize.width, logoSize.height);
let crossSize = logoSize / 4;
let crossOffset = crossSize / 2;
let center = qrSize / 2;
let start = {};
let visual = new draw.Group();
start.x = start.y = Math.ceil(center - baseUnit - logoSize / 2);
visual.append(this._renderShape(start, Math.ceil(logoSize + baseUnit * 2), squarePattern, "#fff"));
start.x = start.y = center - logoSize / 2;
visual.append(this._renderShape(start, logoSize, squarePattern, this.options.color));
start.x = center + crossOffset - logoSize / 2;
start.y = center + crossOffset + crossSize - logoSize / 2;
visual.append(this._renderShape(start, crossSize, crossPattern, "#fff"));
return visual;
}
_renderShape(start, step, pattern, color) {
let path = new draw.MultiPath({
fill: {
color: color
},
stroke: null
});
path.moveTo(start.x, start.y);
for (let i = 0; i < pattern.length; i++) {
path.lineTo(start.x + step * pattern[i][0], start.y + step * pattern[i][1]);
}
path.close();
return path;
}
_getSize() {
let size;
if (this.options.size) {
size = parseInt(this.options.size, 10);
} else {
const element = this.element;
const elementSize = surfaceSize(element, this.options.renderAs);
const min = Math.min(elementSize.width, elementSize.height);
if (min > 0) {
size = min;
} else {
size = QRCodeDefaults.DEFAULT_SIZE;
}
}
return size;
}
_calculateBaseUnit(size, matrixSize) {
let baseUnit = Math.floor(size / matrixSize);
if (baseUnit < QRCodeDefaults.MIN_BASE_UNIT_SIZE) {
const minSize = Math.ceil(matrixSize * QRCodeDefaults.MIN_BASE_UNIT_SIZE);
this.onError(new Error(
`Insufficient size for QR Code: the current size is ${size}px and the minimum size is ${minSize}px.`
));
} else if (baseUnit * matrixSize >= size &&
baseUnit - 1 >= QRCodeDefaults.MIN_BASE_UNIT_SIZE) {
baseUnit--;
}
return baseUnit;
}
_renderMatrix(matrix, baseUnit, quietZoneSize) {
let path = new draw.MultiPath({
fill: {
color: this.options.color
},
stroke: null
});
for (let row = 0; row < matrix.length; row++) {
let y = quietZoneSize + row * baseUnit;
let column = 0;
while (column < matrix.length) {
while (matrix[row][column] === 0 && column < matrix.length) {
column++;
}
if (column < matrix.length) {
let x = column;
while (matrix[row][column] === 1) {
column++;
}
let x1 = round(quietZoneSize + x * baseUnit);
let y1 = round(y);
let x2 = round(quietZoneSize + column * baseUnit);
let y2 = round(y + baseUnit);
path.moveTo(x1, y1)
.lineTo(x1, y2)
.lineTo(x2, y2)
.lineTo(x2, y1)
.close();
}
}
}
return path;
}
_renderBackground(size, border) {
const box = new Box(0, 0, size, size).unpad(border.width / 2);
const background = draw.Path.fromRect(box.toRect(), {
fill: {
color: this.options.background
},
stroke: {
color: border.color,
width: border.width
}
});
return background;
}
setOptions(options) {
let newOptions = options || {};
this.options = extend(this.options, newOptions);
if (options.value !== undefined) {
this._value = String(this.options.value);
}
this._initSurface();
this.redraw();
}
value(value) {
if (value === undefined) {
return this._value;
}
this._value = String(value);
this.redraw();
}
_hasCustomLogo() {
return Boolean(this.options.overlay.imageUrl);
}
_isSwiss() {
return this.options.overlay.type === "swiss";
}
_getLogoSize(defautLogoSize) {
let width = this.options.overlay.width;
let height = this.options.overlay.height;
if (!width && !height) {
width = height = defautLogoSize;
} else if (width && !height) {
height = width;
} else if (!width && height) {
width = height;
}
return {
width: width,
height: height
};
}
}
setDefaultOptions(QRCode, {
name: "QRCode",
renderAs: "svg",
encoding: "ISO_8859_1",
value: "",
errorCorrection: QRCodeDefaults.DEFAULT_ERROR_CORRECTION_LEVEL,
background: QRCodeDefaults.DEFAULT_BACKGROUND,
color: QRCodeDefaults.DEFAULT_DARK_MODULE_COLOR,
size: "",
padding: 0,
border: {
color: "",
width: 0
},
overlay: {
type: "image",
imageUrl: "",
width: 0,
height: 0
}
});
export default QRCode;