diff --git a/src/constants/cornerDotTypes.ts b/src/constants/cornerDotTypes.ts index f5c7b5bf..490a1c7b 100644 --- a/src/constants/cornerDotTypes.ts +++ b/src/constants/cornerDotTypes.ts @@ -2,5 +2,6 @@ import { CornerDotTypes } from "../types"; export default { dot: "dot", - square: "square" + square: "square", + heart: "heart" } as CornerDotTypes; diff --git a/src/constants/dotTypes.ts b/src/constants/dotTypes.ts index 26d4f498..3b19ad18 100644 --- a/src/constants/dotTypes.ts +++ b/src/constants/dotTypes.ts @@ -2,7 +2,10 @@ import { DotTypes } from "../types"; export default { dots: "dots", + randomDots: "random-dots", rounded: "rounded", + verticalLines: "vertical-lines", + horizontalLines: "horizontal-lines", classy: "classy", classyRounded: "classy-rounded", square: "square", diff --git a/src/core/QRSVG.ts b/src/core/QRSVG.ts index 75d51359..28820198 100644 --- a/src/core/QRSVG.ts +++ b/src/core/QRSVG.ts @@ -378,7 +378,11 @@ export default class QRSVG { } if (options.cornersDotOptions?.type) { - const cornersDot = new QRCornerDot({ svg: this._element, type: options.cornersDotOptions.type }); + const cornersDot = new QRCornerDot({ + svg: this._element, + type: options.cornersDotOptions.type, + color: options.cornersDotOptions.color ?? options.dotsOptions.color + }); cornersDot.draw(x + dotSize * 2, y + dotSize * 2, cornersDotSize, rotation); diff --git a/src/figures/cornerDot/QRCornerDot.ts b/src/figures/cornerDot/QRCornerDot.ts index 6e69e5d7..c948406c 100644 --- a/src/figures/cornerDot/QRCornerDot.ts +++ b/src/figures/cornerDot/QRCornerDot.ts @@ -1,14 +1,17 @@ import cornerDotTypes from "../../constants/cornerDotTypes"; import { CornerDotType, RotateFigureArgs, BasicFigureDrawArgs, DrawArgs } from "../../types"; +import { createHeartSVG } from "../../shapes/createHeartSVG"; export default class QRCornerDot { _element?: SVGElement; _svg: SVGElement; _type: CornerDotType; + _color?: string; - constructor({ svg, type }: { svg: SVGElement; type: CornerDotType }) { + constructor({ svg, type, color }: { svg: SVGElement; type: CornerDotType; color?: string }) { this._svg = svg; this._type = type; + this._color = color; } draw(x: number, y: number, size: number, rotation: number): void { @@ -19,6 +22,9 @@ export default class QRCornerDot { case cornerDotTypes.square: drawFunction = this._drawSquare; break; + case cornerDotTypes.heart: + drawFunction = this._drawHeart; + break; case cornerDotTypes.dot: default: drawFunction = this._drawDot; @@ -64,6 +70,29 @@ export default class QRCornerDot { }); } + _basicHeart(args: BasicFigureDrawArgs): void { + const { x, y, size } = args; + this._rotateFigure({ + ...args, + draw: () => { + const xmlns = "http://www.w3.org/2000/svg"; + + // Note! We have to wrap the SVG with a foreignObject element in order to rotate it!!! + const foreignObject = document.createElementNS(xmlns, "foreignObject"); + foreignObject.setAttribute("x", String(x)); + foreignObject.setAttribute("y", String(y)); + foreignObject.setAttribute("width", String(size)); + foreignObject.setAttribute("height", String(size)); + + const svg = createHeartSVG(size, this._color ?? "black"); + foreignObject.append(svg); + + // IMPORTANT! For embedded SVG corners: Append to 'this._svg' - NOT to 'this._element' because the latter would be added to a clipPath + this._svg.appendChild(foreignObject); + } + }); + } + _drawDot({ x, y, size, rotation }: DrawArgs): void { this._basicDot({ x, y, size, rotation }); } @@ -71,4 +100,14 @@ export default class QRCornerDot { _drawSquare({ x, y, size, rotation }: DrawArgs): void { this._basicSquare({ x, y, size, rotation }); } + + _drawHeart({ x, y, size, rotation }: DrawArgs): void { + const scaleFactor = 0.2; + this._basicHeart({ + x: x - (scaleFactor * size) / 2, + y: y - (scaleFactor * size) / 2, + size: size * (1 + scaleFactor), + rotation + }); + } } diff --git a/src/figures/dot/QRDot.ts b/src/figures/dot/QRDot.ts index 396873ab..e09725fa 100644 --- a/src/figures/dot/QRDot.ts +++ b/src/figures/dot/QRDot.ts @@ -19,6 +19,9 @@ export default class QRDot { case dotTypes.dots: drawFunction = this._drawDot; break; + case dotTypes.randomDots: + drawFunction = this._drawRandomDot; + break; case dotTypes.classy: drawFunction = this._drawClassy; break; @@ -28,6 +31,12 @@ export default class QRDot { case dotTypes.rounded: drawFunction = this._drawRounded; break; + case dotTypes.verticalLines: + drawFunction = this._drawVerticalLines; + break; + case dotTypes.horizontalLines: + drawFunction = this._drawHorizontalLines; + break; case dotTypes.extraRounded: drawFunction = this._drawExtraRounded; break; @@ -159,6 +168,11 @@ export default class QRDot { this._basicDot({ x, y, size, rotation: 0 }); } + _drawRandomDot({ x, y, size }: DrawArgs): void { + const randomFactor = Math.random() * (1 - 0.6) + 0.6; + this._basicDot({ x, y, size: size * randomFactor, rotation: 0 }); + } + _drawSquare({ x, y, size }: DrawArgs): void { this._basicSquare({ x, y, size, rotation: 0 }); } @@ -212,6 +226,76 @@ export default class QRDot { } } + _drawVerticalLines({ x, y, size, getNeighbor }: DrawArgs): void { + const leftNeighbor = getNeighbor ? +getNeighbor(-1, 0) : 0; + const rightNeighbor = getNeighbor ? +getNeighbor(1, 0) : 0; + const topNeighbor = getNeighbor ? +getNeighbor(0, -1) : 0; + const bottomNeighbor = getNeighbor ? +getNeighbor(0, 1) : 0; + + const neighborsCount = leftNeighbor + rightNeighbor + topNeighbor + bottomNeighbor; + + if ( + neighborsCount === 0 || + (leftNeighbor && !(topNeighbor || bottomNeighbor)) || + (rightNeighbor && !(topNeighbor || bottomNeighbor)) + ) { + this._basicDot({ x, y, size, rotation: 0 }); + return; + } + + if (topNeighbor && bottomNeighbor) { + this._basicSquare({ x, y, size, rotation: 0 }); + return; + } + + if (topNeighbor && !bottomNeighbor) { + const rotation = Math.PI / 2; + this._basicSideRounded({ x, y, size, rotation }); + return; + } + + if (bottomNeighbor && !topNeighbor) { + const rotation = -Math.PI / 2; + this._basicSideRounded({ x, y, size, rotation }); + return; + } + } + + _drawHorizontalLines({ x, y, size, getNeighbor }: DrawArgs): void { + const leftNeighbor = getNeighbor ? +getNeighbor(-1, 0) : 0; + const rightNeighbor = getNeighbor ? +getNeighbor(1, 0) : 0; + const topNeighbor = getNeighbor ? +getNeighbor(0, -1) : 0; + const bottomNeighbor = getNeighbor ? +getNeighbor(0, 1) : 0; + + const neighborsCount = leftNeighbor + rightNeighbor + topNeighbor + bottomNeighbor; + + if ( + neighborsCount === 0 || + (topNeighbor && !(leftNeighbor || rightNeighbor)) || + (bottomNeighbor && !(leftNeighbor || rightNeighbor)) + ) { + this._basicDot({ x, y, size, rotation: 0 }); + return; + } + + if (leftNeighbor && rightNeighbor) { + this._basicSquare({ x, y, size, rotation: 0 }); + return; + } + + if (leftNeighbor && !rightNeighbor) { + const rotation = 0; + this._basicSideRounded({ x, y, size, rotation }); + return; + } + + if (rightNeighbor && !leftNeighbor) { + const rotation = Math.PI; + this._basicSideRounded({ x, y, size, rotation }); + return; + } + } + _drawExtraRounded({ x, y, size, getNeighbor }: DrawArgs): void { const leftNeighbor = getNeighbor ? +getNeighbor(-1, 0) : 0; const rightNeighbor = getNeighbor ? +getNeighbor(1, 0) : 0; diff --git a/src/shapes/createHeartSVG.ts b/src/shapes/createHeartSVG.ts new file mode 100644 index 00000000..3621f6f0 --- /dev/null +++ b/src/shapes/createHeartSVG.ts @@ -0,0 +1,16 @@ +export function createHeartSVG(size: number, color: string): SVGSVGElement { + const xmlns = "http://www.w3.org/2000/svg"; + const svg = document.createElementNS(xmlns, "svg"); + svg.setAttribute("width", size.toString()); + svg.setAttribute("height", size.toString()); + svg.setAttribute("viewBox", "0 -960 960 960"); + svg.setAttribute("fill", color); + + const path = document.createElementNS(xmlns, "path"); + path.setAttribute( + "d", + "m480-120-58-52q-101-91-167-157T150-447.5Q111-500 95.5-544T80-634q0-94 63-157t157-63q52 0 99 22t81 62q34-40 81-62t99-22q94 0 157 63t63 157q0 46-15.5 90T810-447.5Q771-395 705-329T538-172l-58 52Z" + ); + svg.appendChild(path); + return svg; +} diff --git a/src/types/index.ts b/src/types/index.ts index 558b2883..af62f6cc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,8 +3,17 @@ export interface UnknownObject { [key: string]: any; } -export type DotType = "dots" | "rounded" | "classy" | "classy-rounded" | "square" | "extra-rounded"; -export type CornerDotType = "dot" | "square"; +export type DotType = + | "dots" + | "random-dots" + | "rounded" + | "vertical-lines" + | "horizontal-lines" + | "classy" + | "classy-rounded" + | "square" + | "extra-rounded"; +export type CornerDotType = "dot" | "square" | "heart"; export type CornerSquareType = "dot" | "square" | "extra-rounded"; export type FileExtension = "svg" | "png" | "jpeg" | "webp"; export type GradientType = "radial" | "linear";