diff --git a/.changeset/silver-traces-turn.md b/.changeset/silver-traces-turn.md new file mode 100644 index 0000000..53ca28b --- /dev/null +++ b/.changeset/silver-traces-turn.md @@ -0,0 +1,5 @@ +--- +'@lglab/react-qr-code': minor +--- + +Add circuit-board data module style and microchip finder pattern inner style. diff --git a/apps/docs/public/llms-full.txt b/apps/docs/public/llms-full.txt index 03abc2d..666cead 100644 --- a/apps/docs/public/llms-full.txt +++ b/apps/docs/public/llms-full.txt @@ -39,7 +39,7 @@ The main component exported by the library. | `randomSize` | `boolean` | `false` | If true, modules will have slightly varied sizes. | **Available Styles (`DataModulesStyle`):** -`'square'`, `'square-sm'`, `'pinched-square'`, `'rounded'`, `'leaf'`, `'vertical-line'`, `'horizontal-line'`, `'circle'`, `'diamond'`, `'star'`, `'heart'`, `'hashtag'` +`'square'`, `'square-sm'`, `'pinched-square'`, `'rounded'`, `'leaf'`, `'vertical-line'`, `'horizontal-line'`, `'circuit-board'`, `'circle'`, `'diamond'`, `'star'`, `'heart'`, `'hashtag'` ### FinderPatternOuterSettings diff --git a/apps/docs/src/app/data-modules-settings/page.tsx b/apps/docs/src/app/data-modules-settings/page.tsx index 4965e93..66cb122 100644 --- a/apps/docs/src/app/data-modules-settings/page.tsx +++ b/apps/docs/src/app/data-modules-settings/page.tsx @@ -24,18 +24,19 @@ const props: Prop[] = [ description: 'The style of the modules', defaultValue: 'square', possibleValues: [ - 'square', - 'square-sm', - 'pinched-square', - 'rounded', - 'leaf', - 'vertical-line', - 'horizontal-line', 'circle', + 'circuit-board', 'diamond', - 'star', - 'heart', 'hashtag', + 'heart', + 'horizontal-line', + 'leaf', + 'pinched-square', + 'rounded', + 'square', + 'square-sm', + 'star', + 'vertical-line', ], }, { diff --git a/apps/docs/src/app/finder-pattern-inner-settings/page.tsx b/apps/docs/src/app/finder-pattern-inner-settings/page.tsx index ffc7264..2d0b55a 100644 --- a/apps/docs/src/app/finder-pattern-inner-settings/page.tsx +++ b/apps/docs/src/app/finder-pattern-inner-settings/page.tsx @@ -24,25 +24,26 @@ const props: Prop[] = [ description: 'The style of the finder patterns inner part', defaultValue: 'square', possibleValues: [ - 'square', - 'pinched-square', - 'rounded-sm', - 'rounded', - 'rounded-lg', 'circle', - 'inpoint-sm', + 'diamond', + 'hashtag', + 'heart', 'inpoint', 'inpoint-lg', - 'outpoint-sm', - 'outpoint', - 'outpoint-lg', - 'leaf-sm', + 'inpoint-sm', 'leaf', 'leaf-lg', - 'diamond', + 'leaf-sm', + 'microchip', + 'outpoint', + 'outpoint-lg', + 'outpoint-sm', + 'pinched-square', + 'rounded', + 'rounded-lg', + 'rounded-sm', + 'square', 'star', - 'heart', - 'hashtag', ], }, ] diff --git a/apps/docs/src/app/finder-pattern-outer-settings/page.tsx b/apps/docs/src/app/finder-pattern-outer-settings/page.tsx index 9914c68..a0ff018 100644 --- a/apps/docs/src/app/finder-pattern-outer-settings/page.tsx +++ b/apps/docs/src/app/finder-pattern-outer-settings/page.tsx @@ -24,21 +24,21 @@ const props: Prop[] = [ description: 'The style of the finder patterns outer part', defaultValue: 'square', possibleValues: [ - 'square', - 'pinched-square', - 'rounded-sm', - 'rounded', - 'rounded-lg', 'circle', - 'inpoint-sm', 'inpoint', 'inpoint-lg', - 'outpoint-sm', - 'outpoint', - 'outpoint-lg', - 'leaf-sm', + 'inpoint-sm', 'leaf', 'leaf-lg', + 'leaf-sm', + 'outpoint', + 'outpoint-lg', + 'outpoint-sm', + 'pinched-square', + 'rounded', + 'rounded-lg', + 'rounded-sm', + 'square', ], }, ] diff --git a/apps/docs/src/components/demo/data-modules.tsx b/apps/docs/src/components/demo/data-modules.tsx index 6693d57..0c66a0c 100644 --- a/apps/docs/src/components/demo/data-modules.tsx +++ b/apps/docs/src/components/demo/data-modules.tsx @@ -11,18 +11,19 @@ interface DataModulesProps { } const styles: DataModulesStyle[] = [ - 'square', - 'square-sm', - 'pinched-square', - 'rounded', - 'leaf', - 'vertical-line', - 'horizontal-line', 'circle', + 'circuit-board', 'diamond', - 'star', - 'heart', 'hashtag', + 'heart', + 'horizontal-line', + 'leaf', + 'pinched-square', + 'rounded', + 'square', + 'square-sm', + 'star', + 'vertical-line', ] export const DataModules = ({ qrProps, setQrProps }: DataModulesProps) => { diff --git a/apps/docs/src/components/demo/finder-pattern-inner.tsx b/apps/docs/src/components/demo/finder-pattern-inner.tsx index 907c50e..8bf5867 100644 --- a/apps/docs/src/components/demo/finder-pattern-inner.tsx +++ b/apps/docs/src/components/demo/finder-pattern-inner.tsx @@ -11,25 +11,26 @@ interface FinderPatternInnerProps { } const styles: FinderPatternInnerStyle[] = [ - 'square', - 'pinched-square', - 'rounded-sm', - 'rounded', - 'rounded-lg', 'circle', - 'inpoint-sm', + 'diamond', + 'hashtag', + 'heart', 'inpoint', 'inpoint-lg', - 'outpoint-sm', - 'outpoint', - 'outpoint-lg', - 'leaf-sm', + 'inpoint-sm', 'leaf', 'leaf-lg', - 'diamond', + 'leaf-sm', + 'microchip', + 'outpoint', + 'outpoint-lg', + 'outpoint-sm', + 'pinched-square', + 'rounded', + 'rounded-lg', + 'rounded-sm', + 'square', 'star', - 'heart', - 'hashtag', ] export const FinderPatternInner = ({ qrProps, setQrProps }: FinderPatternInnerProps) => { diff --git a/apps/docs/src/components/demo/finder-pattern-outer.tsx b/apps/docs/src/components/demo/finder-pattern-outer.tsx index 879856c..38b750a 100644 --- a/apps/docs/src/components/demo/finder-pattern-outer.tsx +++ b/apps/docs/src/components/demo/finder-pattern-outer.tsx @@ -11,21 +11,21 @@ interface DataModulesProps { } const styles: FinderPatternOuterStyle[] = [ - 'square', - 'pinched-square', - 'rounded-sm', - 'rounded', - 'rounded-lg', 'circle', - 'inpoint-sm', 'inpoint', 'inpoint-lg', - 'outpoint-sm', - 'outpoint', - 'outpoint-lg', - 'leaf-sm', + 'inpoint-sm', 'leaf', 'leaf-lg', + 'leaf-sm', + 'outpoint', + 'outpoint-lg', + 'outpoint-sm', + 'pinched-square', + 'rounded', + 'rounded-lg', + 'rounded-sm', + 'square', ] export const FinderPatternOuter = ({ qrProps, setQrProps }: DataModulesProps) => { diff --git a/apps/docs/src/components/demo/main-settings.tsx b/apps/docs/src/components/demo/main-settings.tsx index 141d507..67464a0 100644 --- a/apps/docs/src/components/demo/main-settings.tsx +++ b/apps/docs/src/components/demo/main-settings.tsx @@ -21,9 +21,10 @@ const errorCorrectionLevels: ErrorCorrectionLevel[] = ['L', 'M', 'Q', 'H'] export const MainSettings = ({ qrProps, setQrProps }: MainSettingsProps) => { const onInputChange = (e: React.ChangeEvent, key: string) => { + const { value, type } = e.target setQrProps((prevProps) => ({ ...prevProps, - [key]: e.target.value, + [key]: type === 'number' && value !== '' ? Number(value) : value, })) } const onValueChange = (value: string, key: string) => { diff --git a/packages/react-qr-code/src/components/data-modules.test.tsx b/packages/react-qr-code/src/components/data-modules.test.tsx index 912832f..ca7e6ad 100644 --- a/packages/react-qr-code/src/components/data-modules.test.tsx +++ b/packages/react-qr-code/src/components/data-modules.test.tsx @@ -1,6 +1,7 @@ -import { render } from '@testing-library/react' +import { render, screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' +import { CIRCUIT_BOARD_LINE_WIDTH, CIRCUIT_BOARD_PAD_RADIUS } from '../constants' import { dataModulesHorizontalLineNeighbours, dataModulesLeafNeighbours, @@ -53,6 +54,123 @@ describe('DataModules', () => { }) }) + it('renders circuit-board as traces with pads', () => { + const modules = Array.from({ length: 10 }, () => Array(10).fill(false)) + modules[8][8] = true + modules[8][9] = true + modules[9][8] = true + + render( + , + ) + + const path = screen.getByTestId('data-modules') + + expect(path.tagName.toLowerCase()).toBe('path') + expect(path).toHaveAttribute('fill', '#ffdd99') + expect(path).not.toHaveAttribute('stroke') + const d = path.getAttribute('d') ?? '' + const traceHalf = CIRCUIT_BOARD_LINE_WIDTH / 2 + const traceLength = 1 + CIRCUIT_BOARD_LINE_WIDTH + expect(d).toContain( + dataModulesUtils.rect( + 10.5 - traceHalf, + 10.5 - traceHalf, + traceLength, + CIRCUIT_BOARD_LINE_WIDTH, + ), + ) + expect(d).toContain( + dataModulesUtils.rect( + 10.5 - traceHalf, + 10.5 - traceHalf, + CIRCUIT_BOARD_LINE_WIDTH, + traceLength, + ), + ) + expect(d).toContain( + dataModulesUtils.circuitBoardPad(11.5, 10.5, CIRCUIT_BOARD_PAD_RADIUS), + ) + expect(d).toContain( + dataModulesUtils.circuitBoardPad(10.5, 11.5, CIRCUIT_BOARD_PAD_RADIUS), + ) + expect(d).not.toContain( + dataModulesUtils.circuitBoardPad(10.5, 10.5, CIRCUIT_BOARD_PAD_RADIUS), + ) + }) + + it('renders standalone circuit-board modules as squares', () => { + const modules = Array.from({ length: 10 }, () => Array(10).fill(false)) + modules[8][8] = true + + render( + , + ) + + const path = screen.getByTestId('data-modules') + + expect(path.tagName.toLowerCase()).toBe('path') + expect(path).toHaveAttribute('d', 'M10.125,10.125h0.75v0.75h-0.75Z') + }) + + it('fully covers the cell centre at circuit-board junctions', () => { + // Junction cell at (8,8) with neighbours at (7,8), (9,8), (8,7), (8,9): + // it has top+left+right+bottom = count 4 but draws no traces itself + // (only right/bottom). The incoming traces from each neighbour must + // collectively fill the cell centre or a white notch appears. + const modules = Array.from({ length: 12 }, () => Array(12).fill(false)) + modules[7][8] = true + modules[8][7] = true + modules[8][8] = true + modules[8][9] = true + modules[9][8] = true + + render( + , + ) + + const d = screen.getByTestId('data-modules').getAttribute('d') ?? '' + const traceHalf = CIRCUIT_BOARD_LINE_WIDTH / 2 + const traceLength = 1 + CIRCUIT_BOARD_LINE_WIDTH + + // Incoming horizontal trace from cell (8,7), centre (9.5, 10.5): + // must extend past its own end (10.5) by traceHalf so the junction + // cell's centre is covered from the left. + expect(d).toContain( + dataModulesUtils.rect( + 9.5 - traceHalf, + 10.5 - traceHalf, + traceLength, + CIRCUIT_BOARD_LINE_WIDTH, + ), + ) + // Incoming vertical trace from cell (7,8), centre (10.5, 9.5): + // covers the junction from above. + expect(d).toContain( + dataModulesUtils.rect( + 10.5 - traceHalf, + 9.5 - traceHalf, + CIRCUIT_BOARD_LINE_WIDTH, + traceLength, + ), + ) + }) + it.each(dataModulesRoundedNeighbours)( `with neighbours %j calls the correct shape function %s for style rounded`, (neighbours, method) => { diff --git a/packages/react-qr-code/src/components/data-modules.tsx b/packages/react-qr-code/src/components/data-modules.tsx index 72a2149..10b715d 100644 --- a/packages/react-qr-code/src/components/data-modules.tsx +++ b/packages/react-qr-code/src/components/data-modules.tsx @@ -1,15 +1,23 @@ import { type ReactNode, useCallback, useMemo } from 'react' -import { DEFAULT_NUM_STAR_POINTS } from '../constants' +import { + CIRCUIT_BOARD_LINE_WIDTH, + CIRCUIT_BOARD_PAD_RADIUS, + DEFAULT_NUM_STAR_POINTS, +} from '../constants' import type { DataModulesProps } from '../types/utils' import { bottomRounded, circle, + circuitBoardPad, + circuitBoardShouldDrawPad, dataModuleCanBeRandomSize, diamond, + getRenderableDataModuleNeighbours, getScaleFactor, leaf, leftRounded, + rect, rightRounded, square, topRounded, @@ -64,7 +72,41 @@ export const DataModules = ({ const yPos = y + margin + posOffset if (cell) { - if (style === 'square' || style === 'square-sm') { + if (style === 'circuit-board') { + const cx = x + margin + 0.5 + const cy = y + margin + 0.5 + const traceHalf = CIRCUIT_BOARD_LINE_WIDTH / 2 + // Traces extend traceHalf past both endpoints so that adjacent + // traces fully cover the cell-center square at every junction + // (preventing white notches at L/T/+ bends under nonzero fill). + const traceLength = 1 + CIRCUIT_BOARD_LINE_WIDTH + const neighbours = getRenderableDataModuleNeighbours(x, y, modules, numCells) + const { right, bottom, count } = neighbours + + if (right) { + ops.push( + rect(cx - traceHalf, cy - traceHalf, traceLength, CIRCUIT_BOARD_LINE_WIDTH), + ) + } + if (bottom) { + ops.push( + rect(cx - traceHalf, cy - traceHalf, CIRCUIT_BOARD_LINE_WIDTH, traceLength), + ) + } + if (count === 0) { + const isolatedSize = 0.75 + const isolatedOffset = (1 - isolatedSize) / 2 + ops.push( + square( + x + margin + isolatedOffset, + y + margin + isolatedOffset, + isolatedSize, + ), + ) + } else if (circuitBoardShouldDrawPad({ ...neighbours, count })) { + ops.push(circuitBoardPad(cx, cy, CIRCUIT_BOARD_PAD_RADIUS)) + } + } else if (style === 'square' || style === 'square-sm') { ops.push(square(xPos, yPos, size)) } else if (style === 'pinched-square') { ops.push(pinchedSquare(xPos, yPos, size, 0.25)) @@ -149,9 +191,12 @@ export const DataModules = ({ } }) }) + + const paint = gradient ? `url(#${gradientId})` : color + return ( { }) }) + it('renders correctly with style microchip', () => { + const spy = vi.spyOn(svgUtils, 'microchip') + render( + , + ) + const paths = screen.getAllByTestId('finder-patterns-inner') + expect(paths).toHaveLength(3) + paths.forEach((path) => { + expect(spy).toHaveBeenCalled() + expect(path.getAttribute('fill')).toBe('#ff0000') + }) + }) + it('renders correctly with style hashtag', () => { const spy = vi.spyOn(svgUtils, 'hashtag') render( diff --git a/packages/react-qr-code/src/components/finder-patterns-inner.tsx b/packages/react-qr-code/src/components/finder-patterns-inner.tsx index 6f2be28..0825c38 100644 --- a/packages/react-qr-code/src/components/finder-patterns-inner.tsx +++ b/packages/react-qr-code/src/components/finder-patterns-inner.tsx @@ -13,7 +13,7 @@ import { finderPatternsInnerLeaf, } from '../utils/finder-patterns-inner' import { sanitizeFinderPatternInnerSettings } from '../utils/settings' -import { hashtag, heart, pinchedSquare, star } from '../utils/svg' +import { hashtag, heart, microchip, pinchedSquare, star } from '../utils/svg' const testProps = { 'data-testid': 'finder-patterns-inner', @@ -164,6 +164,13 @@ export const FinderPatternsInner = ({ }) } + if (style === 'microchip') { + return coordinates.map(({ x, y }) => { + const path = microchip(x, y, FINDER_PATTERN_INNER_SIZE) + return + }) + } + if (style === 'hashtag') { return coordinates.map(({ x, y }) => { const path = hashtag(x - 0.25, y - 0.25, 3.5) diff --git a/packages/react-qr-code/src/constants.ts b/packages/react-qr-code/src/constants.ts index 6a63369..ae5594c 100644 --- a/packages/react-qr-code/src/constants.ts +++ b/packages/react-qr-code/src/constants.ts @@ -32,6 +32,8 @@ export const DEFAULT_DATA_MODULES_COLOR = '#000000' export const DEFAULT_FINDER_PATTERN_OUTER_STYLE: FinderPatternOuterStyle = 'square' export const DEFAULT_FINDER_PATTERN_INNER_STYLE: FinderPatternInnerStyle = 'square' export const DEFAULT_DATA_MODULES_STYLE: DataModulesStyle = 'square' +export const CIRCUIT_BOARD_LINE_WIDTH = 0.5 +export const CIRCUIT_BOARD_PAD_RADIUS = 0.5 export const DEFAULT_FILENAME = 'react-qr-code' diff --git a/packages/react-qr-code/src/types/lib.ts b/packages/react-qr-code/src/types/lib.ts index 097db87..389e466 100644 --- a/packages/react-qr-code/src/types/lib.ts +++ b/packages/react-qr-code/src/types/lib.ts @@ -36,6 +36,7 @@ export type DataModulesStyle = | 'leaf' | 'vertical-line' | 'horizontal-line' + | 'circuit-board' | 'circle' | 'diamond' | 'star' @@ -90,6 +91,7 @@ export type FinderPatternInnerStyle = | 'star' | 'heart' | 'hashtag' + | 'microchip' export interface FinderPatternInnerSettings { color?: string diff --git a/packages/react-qr-code/src/utils/data-modules.test.ts b/packages/react-qr-code/src/utils/data-modules.test.ts index db17761..facb457 100644 --- a/packages/react-qr-code/src/utils/data-modules.test.ts +++ b/packages/react-qr-code/src/utils/data-modules.test.ts @@ -1,7 +1,14 @@ import { describe, expect, it, vi } from 'vitest' import type { Modules } from '../types/lib' -import { getModuleNeighbours, getScaleFactor } from './data-modules' +import { + circuitBoardShouldDrawPad, + dataModuleCanBeRandomSize, + getModuleNeighbours, + getRenderableDataModuleNeighbours, + getScaleFactor, + isRenderableDataModule, +} from './data-modules' describe('getScaleFactor', () => { it('returns 0.75 for square-sm style', () => { @@ -20,6 +27,65 @@ describe('getScaleFactor', () => { }) }) +describe('dataModuleCanBeRandomSize', () => { + it('does not allow random size for circuit-board modules', () => { + expect(dataModuleCanBeRandomSize('circuit-board')).toBe(false) + }) +}) + +describe('circuitBoardShouldDrawPad', () => { + it('draws pads only for endpoints', () => { + expect( + circuitBoardShouldDrawPad({ + left: true, + right: false, + top: false, + bottom: false, + count: 1, + }), + ).toBe(true) + }) + + it('does not draw pads for isolated, turn, junction, or straight-through modules', () => { + expect( + circuitBoardShouldDrawPad({ + left: false, + right: false, + top: false, + bottom: false, + count: 0, + }), + ).toBe(false) + expect( + circuitBoardShouldDrawPad({ + left: true, + right: false, + top: true, + bottom: false, + count: 2, + }), + ).toBe(false) + expect( + circuitBoardShouldDrawPad({ + left: true, + right: true, + top: true, + bottom: false, + count: 3, + }), + ).toBe(false) + expect( + circuitBoardShouldDrawPad({ + left: false, + right: false, + top: true, + bottom: true, + count: 2, + }), + ).toBe(false) + }) +}) + describe('getModuleNeighbours', () => { it('should return correct neighbours for a center cell', () => { const modules: Modules = [ @@ -162,3 +228,29 @@ describe('getModuleNeighbours', () => { }) }) }) + +describe('isRenderableDataModule', () => { + it('returns false for finder pattern modules', () => { + const modules: Modules = Array.from({ length: 21 }, () => Array(21).fill(false)) + modules[0][0] = true + + expect(isRenderableDataModule({ x: 0, y: 0, modules, numCells: 21 })).toBe(false) + }) +}) + +describe('getRenderableDataModuleNeighbours', () => { + it('ignores neighbouring finder pattern modules', () => { + const modules: Modules = Array.from({ length: 21 }, () => Array(21).fill(false)) + modules[3][6] = true + modules[3][7] = true + modules[3][8] = true + + expect(getRenderableDataModuleNeighbours(7, 3, modules, 21)).toEqual({ + left: false, + right: true, + top: false, + bottom: false, + count: 1, + }) + }) +}) diff --git a/packages/react-qr-code/src/utils/data-modules.ts b/packages/react-qr-code/src/utils/data-modules.ts index 652bedd..1c18560 100644 --- a/packages/react-qr-code/src/utils/data-modules.ts +++ b/packages/react-qr-code/src/utils/data-modules.ts @@ -1,5 +1,7 @@ import type { DataModulesStyle, Modules } from '../types/lib' import type { DataModulesNeighbours } from '../types/utils' +import { isFinderPatternInnerModule } from './finder-patterns-inner' +import { isFinderPatternOuterModule } from './finder-patterns-outer' export const dataModuleCanBeRandomSize = (style: DataModulesStyle): boolean => style === 'square' || @@ -37,8 +39,51 @@ export const getModuleNeighbours = ( } } -export const square = (x: number, y: number, size: number) => - `M${x},${y}h${size}v${size}h-${size}Z` +export const isRenderableDataModule = ({ + x, + y, + modules, + numCells, +}: { + x: number + y: number + modules: Modules + numCells: number +}) => { + return ( + y >= 0 && + y < modules.length && + x >= 0 && + x < modules[y].length && + modules[y][x] && + !isFinderPatternOuterModule({ x, y, numCells }) && + !isFinderPatternInnerModule({ x, y, numCells }) + ) +} + +export const getRenderableDataModuleNeighbours = ( + x: number, + y: number, + modules: Modules, + numCells: number, +): DataModulesNeighbours => { + const sides = { + left: isRenderableDataModule({ x: x - 1, y, modules, numCells }), + right: isRenderableDataModule({ x: x + 1, y, modules, numCells }), + top: isRenderableDataModule({ x, y: y - 1, modules, numCells }), + bottom: isRenderableDataModule({ x, y: y + 1, modules, numCells }), + } + + return { + ...sides, + count: Object.values(sides).filter(Boolean).length, + } +} + +export const rect = (x: number, y: number, width: number, height: number) => + `M${x},${y}h${width}v${height}h${-width}Z` + +export const square = (x: number, y: number, size: number) => rect(x, y, size, size) export const circle = (x: number, y: number, size: number) => `M${x},${y + size / 2}a${size / 2},${size / 2} 0 1,0 ${size},0a${size / 2},${size / 2} 0 1,0 -${size},0Z` @@ -46,6 +91,15 @@ export const circle = (x: number, y: number, size: number) => export const diamond = (x: number, y: number, size: number) => `M${x},${y + size / 2}l${size / 2},-${size / 2}l${size / 2},${size / 2}l-${size / 2},${size / 2}Z` +// Wound clockwise (sweep-flag 1) so the pad fills correctly when combined +// in a single path with clockwise-wound trace rects under nonzero fill. +// Switching to circle() (counter-clockwise) would XOR the overlap and +// produce donut-shaped pads. +export const circuitBoardPad = (cx: number, cy: number, radius: number) => + `M${cx - radius},${cy}a${radius},${radius} 0 1,1 ${radius * 2},0a${radius},${radius} 0 1,1 ${-radius * 2},0Z` + +export const circuitBoardShouldDrawPad = ({ count }: DataModulesNeighbours) => count === 1 + export const topRightRounded = (x: number, y: number) => `M ${x} ${y} v 1 diff --git a/packages/react-qr-code/src/utils/svg.ts b/packages/react-qr-code/src/utils/svg.ts index 1c24509..a9f49a3 100644 --- a/packages/react-qr-code/src/utils/svg.ts +++ b/packages/react-qr-code/src/utils/svg.ts @@ -49,6 +49,29 @@ export const pinchedSquare = ( `Q ${x + size / 2} ${y + controlOffset}, ${x} ${y}` + 'Z' +const MICROCHIP_LEG_HEIGHT_RATIO = 0.15 +const MICROCHIP_LEG_WIDTH_RATIO = 0.1 +const MICROCHIP_LEG_SPAN_RATIO = 0.7 +const MICROCHIP_NUM_LEGS = 4 + +export const microchip = (x: number, y: number, size: number) => { + const legH = size * MICROCHIP_LEG_HEIGHT_RATIO + const legW = size * MICROCHIP_LEG_WIDTH_RATIO + const bodyH = size - legH * 2 + const body = `M${x},${y + legH}h${size}v${bodyH}h${-size}Z` + const legSpan = size * MICROCHIP_LEG_SPAN_RATIO + const legStart = x + (size - legSpan) / 2 + const legStep = (legSpan - legW) / (MICROCHIP_NUM_LEGS - 1) + const legs = Array.from({ length: MICROCHIP_NUM_LEGS }, (_, i) => { + const lx = legStart + legStep * i + return ( + `M${lx},${y}h${legW}v${legH}h${-legW}Z` + + `M${lx},${y + size - legH}h${legW}v${legH}h${-legW}Z` + ) + }).join('') + return body + legs +} + export const hashtag = (x: number, y: number, size: number) => { const eigth = size / 8 return `M ${x + size} ${y + eigth * 3}