UNPKG

@progress/kendo-charts

Version:

Kendo UI platform-independent Charts library

403 lines (324 loc) 10.9 kB
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;