Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@pdfme/generator

Package Overview
Dependencies
Maintainers
1
Versions
273
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pdfme/generator - npm Package Compare versions

Comparing version 2.1.0 to 2.2.0

6

dist/cjs/__tests__/assets/templates/index.js

@@ -46,2 +46,5 @@ "use strict";

const dynamicFontSizeVertical = require('./dynamicFontSizeVertical.json');
const verticalAlignmentTop = require('./verticalAlignmentTop.json');
const verticalAlignmentMiddle = require('./verticalAlignmentMiddle.json');
const verticalAlignmentBottom = require('./verticalAlignmentBottom.json');
exports.default = {

@@ -115,3 +118,6 @@ test: {

dynamicFontSizeVertical,
verticalAlignmentTop,
verticalAlignmentMiddle,
verticalAlignmentBottom,
};
//# sourceMappingURL=index.js.map

@@ -48,2 +48,5 @@ "use strict";

const NotoSerifJPRegularData = (0, fs_1.readFileSync)(path.join(__dirname, `/assets/fonts/NotoSerifJP-Regular.otf`));
const GloriaHallelujahRegularData = (0, fs_1.readFileSync)(path.join(__dirname, `/assets/fonts/GloriaHallelujah-Regular.ttf`));
const GreatVibesRegularData = (0, fs_1.readFileSync)(path.join(__dirname, `/assets/fonts/GreatVibes-Regular.ttf`));
const JuliusSansOneRegularData = (0, fs_1.readFileSync)(path.join(__dirname, `/assets/fonts/JuliusSansOne-Regular.ttf`));
const getFont = () => ({

@@ -53,2 +56,5 @@ SauceHanSansJP: { fallback: true, data: SauceHanSansJPData },

'NotoSerifJP-Regular': { data: NotoSerifJPRegularData },
'GloriaHallelujah-Regular': { data: GloriaHallelujahRegularData },
'GreatVibes-Regular': { data: GreatVibesRegularData },
'JuliusSansOne-Regular': { data: JuliusSansOneRegularData },
});

@@ -55,0 +61,0 @@ const getPdf = (pdfFilePath) => {

77

dist/cjs/src/helper.js

@@ -105,9 +105,10 @@ "use strict";

const getFontProp = ({ input, font, schema }) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
const size = schema.dynamicFontSize ? yield (0, common_1.calculateDynamicFontSize)({ textSchema: schema, font, input }) : (_a = schema.fontSize) !== null && _a !== void 0 ? _a : common_1.DEFAULT_FONT_SIZE;
var _a, _b, _c, _d, _e, _f;
const fontSize = schema.dynamicFontSize ? yield (0, common_1.calculateDynamicFontSize)({ textSchema: schema, font, input }) : (_a = schema.fontSize) !== null && _a !== void 0 ? _a : common_1.DEFAULT_FONT_SIZE;
const color = hex2RgbColor((_b = schema.fontColor) !== null && _b !== void 0 ? _b : common_1.DEFAULT_FONT_COLOR);
const alignment = (_c = schema.alignment) !== null && _c !== void 0 ? _c : common_1.DEFAULT_ALIGNMENT;
const lineHeight = (_d = schema.lineHeight) !== null && _d !== void 0 ? _d : common_1.DEFAULT_LINE_HEIGHT;
const characterSpacing = (_e = schema.characterSpacing) !== null && _e !== void 0 ? _e : common_1.DEFAULT_CHARACTER_SPACING;
return { size, color, alignment, lineHeight, characterSpacing };
const verticalAlignment = (_d = schema.verticalAlignment) !== null && _d !== void 0 ? _d : common_1.DEFAULT_VERTICAL_ALIGNMENT;
const lineHeight = (_e = schema.lineHeight) !== null && _e !== void 0 ? _e : common_1.DEFAULT_LINE_HEIGHT;
const characterSpacing = (_f = schema.characterSpacing) !== null && _f !== void 0 ? _f : common_1.DEFAULT_CHARACTER_SPACING;
return { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing };
});

@@ -124,3 +125,3 @@ const calcX = (x, alignment, boxWidth, textWidth) => {

};
const calcY = (y, height, itemHeight) => height - (0, common_1.mm2pt)(y) - itemHeight;
const calcY = (y, pageHeight, itemHeight) => pageHeight - (0, common_1.mm2pt)(y) - itemHeight;
const drawBackgroundColor = (arg) => {

@@ -148,3 +149,3 @@ const { templateSchema, page, pageHeight } = arg;

const { width, height, rotate } = convertSchemaDimensionsToPt(templateSchema);
const { size, color, alignment, lineHeight, characterSpacing } = yield getFontProp({
const { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing } = yield getFontProp({
input,

@@ -155,34 +156,44 @@ font,

page.pushOperators((0, pdf_lib_1.setCharacterSpacing)(characterSpacing));
let beforeLineOver = 0;
const firstLineTextHeight = (0, common_1.heightOfFontAtSize)(fontKitFont, fontSize);
const descent = (0, common_1.getFontDescentInPt)(fontKitFont, fontSize);
const halfLineHeightAdjustment = lineHeight === 0 ? 0 : ((lineHeight - 1) * fontSize) / 2;
const fontWidthCalcValues = {
font: fontKitFont,
fontSize: size,
fontSize,
characterSpacing,
boxWidthInPt: width,
};
input.split(/\r|\n|\r\n/g).forEach((inputLine, inputLineIndex) => {
const splitLines = (0, common_1.getSplittedLines)(inputLine, fontWidthCalcValues);
const drawLine = (line, lineIndex) => {
const textWidth = (0, common_1.widthOfTextAtSize)(line, fontKitFont, size, characterSpacing);
const textHeight = (0, common_1.heightOfFontAtSize)(fontKitFont, size);
page.drawText(line, {
x: calcX(templateSchema.position.x, alignment, width, textWidth),
y: calcY(templateSchema.position.y, pageHeight, height) +
height -
textHeight -
lineHeight * size * (inputLineIndex + lineIndex + beforeLineOver) -
(lineHeight === 0 ? 0 : ((lineHeight - 1) * size) / 2),
rotate,
size,
color,
lineHeight: lineHeight * size,
maxWidth: width,
font: pdfFontValue,
wordBreaks: [''],
});
if (splitLines.length === lineIndex + 1)
beforeLineOver += lineIndex;
};
splitLines.forEach(drawLine);
let lines = [];
input.split(/\r|\n|\r\n/g).forEach((inputLine) => {
lines = lines.concat((0, common_1.getSplittedLines)(inputLine, fontWidthCalcValues));
});
// Text lines are rendered from the bottom upwards, we need to adjust the position down
let yOffset = 0;
if (verticalAlignment === common_1.VERTICAL_ALIGN_TOP) {
yOffset = firstLineTextHeight + halfLineHeightAdjustment;
}
else {
const otherLinesHeight = lineHeight * fontSize * (lines.length - 1);
if (verticalAlignment === common_1.VERTICAL_ALIGN_BOTTOM) {
yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment;
}
else if (verticalAlignment === common_1.VERTICAL_ALIGN_MIDDLE) {
yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight;
}
}
lines.forEach((line, rowIndex) => {
const textWidth = (0, common_1.widthOfTextAtSize)(line, fontKitFont, fontSize, characterSpacing);
const rowYOffset = lineHeight * fontSize * rowIndex;
page.drawText(line, {
x: calcX(templateSchema.position.x, alignment, width, textWidth),
y: calcY(templateSchema.position.y, pageHeight, yOffset) - rowYOffset,
rotate,
size: fontSize,
color,
lineHeight: lineHeight * fontSize,
maxWidth: width,
font: pdfFontValue,
wordBreaks: [''],
});
});
});

@@ -189,0 +200,0 @@ const getCacheKey = (templateSchema, input) => `${templateSchema.type}${input}`;

@@ -44,2 +44,5 @@ import { BLANK_PDF } from '@pdfme/common';

const dynamicFontSizeVertical = require('./dynamicFontSizeVertical.json');
const verticalAlignmentTop = require('./verticalAlignmentTop.json');
const verticalAlignmentMiddle = require('./verticalAlignmentMiddle.json');
const verticalAlignmentBottom = require('./verticalAlignmentBottom.json');
export default {

@@ -113,3 +116,6 @@ test: {

dynamicFontSizeVertical,
verticalAlignmentTop,
verticalAlignmentMiddle,
verticalAlignmentBottom,
};
//# sourceMappingURL=index.js.map

@@ -20,2 +20,5 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

const NotoSerifJPRegularData = readFileSync(path.join(__dirname, `/assets/fonts/NotoSerifJP-Regular.otf`));
const GloriaHallelujahRegularData = readFileSync(path.join(__dirname, `/assets/fonts/GloriaHallelujah-Regular.ttf`));
const GreatVibesRegularData = readFileSync(path.join(__dirname, `/assets/fonts/GreatVibes-Regular.ttf`));
const JuliusSansOneRegularData = readFileSync(path.join(__dirname, `/assets/fonts/JuliusSansOne-Regular.ttf`));
const getFont = () => ({

@@ -25,2 +28,5 @@ SauceHanSansJP: { fallback: true, data: SauceHanSansJPData },

'NotoSerifJP-Regular': { data: NotoSerifJPRegularData },
'GloriaHallelujah-Regular': { data: GloriaHallelujahRegularData },
'GreatVibes-Regular': { data: GreatVibesRegularData },
'JuliusSansOne-Regular': { data: JuliusSansOneRegularData },
});

@@ -27,0 +33,0 @@ const getPdf = (pdfFilePath) => {

@@ -12,3 +12,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import bwipjs from 'bwip-js';
import { getB64BasePdf, b64toUint8Array, validateBarcodeInput, isTextSchema, isImageSchema, isBarcodeSchema, DEFAULT_FONT_SIZE, DEFAULT_ALIGNMENT, DEFAULT_LINE_HEIGHT, DEFAULT_CHARACTER_SPACING, DEFAULT_FONT_COLOR, calculateDynamicFontSize, heightOfFontAtSize, getFontKitFont, getSplittedLines, mm2pt, widthOfTextAtSize, } from '@pdfme/common';
import { getB64BasePdf, b64toUint8Array, validateBarcodeInput, isTextSchema, isImageSchema, isBarcodeSchema, DEFAULT_ALIGNMENT, DEFAULT_CHARACTER_SPACING, DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE, DEFAULT_LINE_HEIGHT, DEFAULT_VERTICAL_ALIGNMENT, VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_MIDDLE, VERTICAL_ALIGN_BOTTOM, calculateDynamicFontSize, heightOfFontAtSize, getFontDescentInPt, getFontKitFont, getSplittedLines, mm2pt, widthOfTextAtSize, } from '@pdfme/common';
const barCodeType2Bcid = (type) => (type === 'nw7' ? 'rationalizedCodabar' : type);

@@ -97,9 +97,10 @@ export const createBarCode = (arg) => __awaiter(void 0, void 0, void 0, function* () {

const getFontProp = ({ input, font, schema }) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
const size = schema.dynamicFontSize ? yield calculateDynamicFontSize({ textSchema: schema, font, input }) : (_a = schema.fontSize) !== null && _a !== void 0 ? _a : DEFAULT_FONT_SIZE;
var _a, _b, _c, _d, _e, _f;
const fontSize = schema.dynamicFontSize ? yield calculateDynamicFontSize({ textSchema: schema, font, input }) : (_a = schema.fontSize) !== null && _a !== void 0 ? _a : DEFAULT_FONT_SIZE;
const color = hex2RgbColor((_b = schema.fontColor) !== null && _b !== void 0 ? _b : DEFAULT_FONT_COLOR);
const alignment = (_c = schema.alignment) !== null && _c !== void 0 ? _c : DEFAULT_ALIGNMENT;
const lineHeight = (_d = schema.lineHeight) !== null && _d !== void 0 ? _d : DEFAULT_LINE_HEIGHT;
const characterSpacing = (_e = schema.characterSpacing) !== null && _e !== void 0 ? _e : DEFAULT_CHARACTER_SPACING;
return { size, color, alignment, lineHeight, characterSpacing };
const verticalAlignment = (_d = schema.verticalAlignment) !== null && _d !== void 0 ? _d : DEFAULT_VERTICAL_ALIGNMENT;
const lineHeight = (_e = schema.lineHeight) !== null && _e !== void 0 ? _e : DEFAULT_LINE_HEIGHT;
const characterSpacing = (_f = schema.characterSpacing) !== null && _f !== void 0 ? _f : DEFAULT_CHARACTER_SPACING;
return { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing };
});

@@ -116,3 +117,3 @@ const calcX = (x, alignment, boxWidth, textWidth) => {

};
const calcY = (y, height, itemHeight) => height - mm2pt(y) - itemHeight;
const calcY = (y, pageHeight, itemHeight) => pageHeight - mm2pt(y) - itemHeight;
const drawBackgroundColor = (arg) => {

@@ -140,3 +141,3 @@ const { templateSchema, page, pageHeight } = arg;

const { width, height, rotate } = convertSchemaDimensionsToPt(templateSchema);
const { size, color, alignment, lineHeight, characterSpacing } = yield getFontProp({
const { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing } = yield getFontProp({
input,

@@ -147,34 +148,44 @@ font,

page.pushOperators(setCharacterSpacing(characterSpacing));
let beforeLineOver = 0;
const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize);
const descent = getFontDescentInPt(fontKitFont, fontSize);
const halfLineHeightAdjustment = lineHeight === 0 ? 0 : ((lineHeight - 1) * fontSize) / 2;
const fontWidthCalcValues = {
font: fontKitFont,
fontSize: size,
fontSize,
characterSpacing,
boxWidthInPt: width,
};
input.split(/\r|\n|\r\n/g).forEach((inputLine, inputLineIndex) => {
const splitLines = getSplittedLines(inputLine, fontWidthCalcValues);
const drawLine = (line, lineIndex) => {
const textWidth = widthOfTextAtSize(line, fontKitFont, size, characterSpacing);
const textHeight = heightOfFontAtSize(fontKitFont, size);
page.drawText(line, {
x: calcX(templateSchema.position.x, alignment, width, textWidth),
y: calcY(templateSchema.position.y, pageHeight, height) +
height -
textHeight -
lineHeight * size * (inputLineIndex + lineIndex + beforeLineOver) -
(lineHeight === 0 ? 0 : ((lineHeight - 1) * size) / 2),
rotate,
size,
color,
lineHeight: lineHeight * size,
maxWidth: width,
font: pdfFontValue,
wordBreaks: [''],
});
if (splitLines.length === lineIndex + 1)
beforeLineOver += lineIndex;
};
splitLines.forEach(drawLine);
let lines = [];
input.split(/\r|\n|\r\n/g).forEach((inputLine) => {
lines = lines.concat(getSplittedLines(inputLine, fontWidthCalcValues));
});
// Text lines are rendered from the bottom upwards, we need to adjust the position down
let yOffset = 0;
if (verticalAlignment === VERTICAL_ALIGN_TOP) {
yOffset = firstLineTextHeight + halfLineHeightAdjustment;
}
else {
const otherLinesHeight = lineHeight * fontSize * (lines.length - 1);
if (verticalAlignment === VERTICAL_ALIGN_BOTTOM) {
yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment;
}
else if (verticalAlignment === VERTICAL_ALIGN_MIDDLE) {
yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight;
}
}
lines.forEach((line, rowIndex) => {
const textWidth = widthOfTextAtSize(line, fontKitFont, fontSize, characterSpacing);
const rowYOffset = lineHeight * fontSize * rowIndex;
page.drawText(line, {
x: calcX(templateSchema.position.x, alignment, width, textWidth),
y: calcY(templateSchema.position.y, pageHeight, yOffset) - rowYOffset,
rotate,
size: fontSize,
color,
lineHeight: lineHeight * fontSize,
maxWidth: width,
font: pdfFontValue,
wordBreaks: [''],
});
});
});

@@ -181,0 +192,0 @@ const getCacheKey = (templateSchema, input) => `${templateSchema.type}${input}`;

{
"name": "@pdfme/generator",
"version": "2.1.0",
"version": "2.2.0",
"sideEffects": false,

@@ -5,0 +5,0 @@ "author": "hand-dot",

@@ -15,3 +15,3 @@ # PDFME

</a>
<a href="https://pdfme.com/help#contribution">
<a href="https://pdfme.com/development-guide#contribution">
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome!" />

@@ -53,3 +53,3 @@ </a>

As an example, the author's service [https://labelmake.jp/](https://labelmake.jp/) can create more than 100 varieties of PDFs and generates more than 100,000 PDF files per month.
As an example, the author's service [https://labelmake.jp/](https://labelmake.jp/) can create more than 100 varieties of PDFs and generates more than 100,000 PDF files per month. Notably, the monthly server cost, utilizing Cloud Functions For Firebase, remains below $10.

@@ -296,2 +296,3 @@ ## Installation

- [pdf-lib](https://pdf-lib.js.org/): Used in PDF generation.
- [fontkit](https://github.com/foliojs/fontkit): Used in font rendering.
- [PDF.js](https://mozilla.github.io/pdf.js/): Used in PDF viewing.

@@ -305,1 +306,4 @@ - [React](https://reactjs.org/): Used in building the UI.

I definitely could not have created pdfme without these libraries. I am grateful to the developers of these libraries.
If you want to contribute to pdfme, please check the [Development Guide](https://pdfme.com/development-guide) page.
We look forward to your contribution!

@@ -28,9 +28,14 @@ import {

Alignment,
DEFAULT_FONT_SIZE,
DEFAULT_ALIGNMENT,
DEFAULT_LINE_HEIGHT,
DEFAULT_CHARACTER_SPACING,
DEFAULT_FONT_COLOR,
DEFAULT_FONT_SIZE,
DEFAULT_LINE_HEIGHT,
DEFAULT_VERTICAL_ALIGNMENT,
VERTICAL_ALIGN_TOP,
VERTICAL_ALIGN_MIDDLE,
VERTICAL_ALIGN_BOTTOM,
calculateDynamicFontSize,
heightOfFontAtSize,
getFontDescentInPt,
getFontKitFont,

@@ -172,9 +177,10 @@ getSplittedLines,

const getFontProp = async ({ input, font, schema }: { input: string, font: Font, schema: TextSchema }) => {
const size = schema.dynamicFontSize ? await calculateDynamicFontSize({ textSchema: schema, font, input }) : schema.fontSize ?? DEFAULT_FONT_SIZE;
const fontSize = schema.dynamicFontSize ? await calculateDynamicFontSize({ textSchema: schema, font, input }) : schema.fontSize ?? DEFAULT_FONT_SIZE;
const color = hex2RgbColor(schema.fontColor ?? DEFAULT_FONT_COLOR);
const alignment = schema.alignment ?? DEFAULT_ALIGNMENT;
const verticalAlignment = schema.verticalAlignment ?? DEFAULT_VERTICAL_ALIGNMENT;
const lineHeight = schema.lineHeight ?? DEFAULT_LINE_HEIGHT;
const characterSpacing = schema.characterSpacing ?? DEFAULT_CHARACTER_SPACING;
return { size, color, alignment, lineHeight, characterSpacing };
return { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing };
};

@@ -193,3 +199,3 @@

const calcY = (y: number, height: number, itemHeight: number) => height - mm2pt(y) - itemHeight;
const calcY = (y: number, pageHeight: number, itemHeight: number) => pageHeight - mm2pt(y) - itemHeight;

@@ -239,15 +245,18 @@ const drawBackgroundColor = (arg: {

const { width, height, rotate } = convertSchemaDimensionsToPt(templateSchema);
const { size, color, alignment, lineHeight, characterSpacing } = await getFontProp({
input,
font,
schema: templateSchema,
});
const { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing } =
await getFontProp({
input,
font,
schema: templateSchema,
});
page.pushOperators(setCharacterSpacing(characterSpacing));
let beforeLineOver = 0;
const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize);
const descent = getFontDescentInPt(fontKitFont, fontSize);
const halfLineHeightAdjustment = lineHeight === 0 ? 0 : ((lineHeight - 1) * fontSize) / 2;
const fontWidthCalcValues: FontWidthCalcValues = {
font: fontKitFont,
fontSize: size,
fontSize,
characterSpacing,

@@ -257,29 +266,36 @@ boxWidthInPt: width,

input.split(/\r|\n|\r\n/g).forEach((inputLine, inputLineIndex) => {
const splitLines = getSplittedLines(inputLine, fontWidthCalcValues);
let lines: string[] = [];
input.split(/\r|\n|\r\n/g).forEach((inputLine) => {
lines = lines.concat(getSplittedLines(inputLine, fontWidthCalcValues));
});
const drawLine = (line: string, lineIndex: number) => {
const textWidth = widthOfTextAtSize(line, fontKitFont, size, characterSpacing);
const textHeight = heightOfFontAtSize(fontKitFont, size);
// Text lines are rendered from the bottom upwards, we need to adjust the position down
let yOffset = 0;
if (verticalAlignment === VERTICAL_ALIGN_TOP) {
yOffset = firstLineTextHeight + halfLineHeightAdjustment;
} else {
const otherLinesHeight = lineHeight * fontSize * (lines.length - 1);
page.drawText(line, {
x: calcX(templateSchema.position.x, alignment, width, textWidth),
y:
calcY(templateSchema.position.y, pageHeight, height) +
height -
textHeight -
lineHeight * size * (inputLineIndex + lineIndex + beforeLineOver) -
(lineHeight === 0 ? 0 : ((lineHeight - 1) * size) / 2),
rotate,
size,
color,
lineHeight: lineHeight * size,
maxWidth: width,
font: pdfFontValue,
wordBreaks: [''],
});
if (splitLines.length === lineIndex + 1) beforeLineOver += lineIndex;
};
if (verticalAlignment === VERTICAL_ALIGN_BOTTOM) {
yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment;
} else if (verticalAlignment === VERTICAL_ALIGN_MIDDLE) {
yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight;
}
}
splitLines.forEach(drawLine);
lines.forEach((line, rowIndex) => {
const textWidth = widthOfTextAtSize(line, fontKitFont, fontSize, characterSpacing);
const rowYOffset = lineHeight * fontSize * rowIndex;
page.drawText(line, {
x: calcX(templateSchema.position.x, alignment, width, textWidth),
y: calcY(templateSchema.position.y, pageHeight, yOffset) - rowYOffset,
rotate,
size: fontSize,
color,
lineHeight: lineHeight * fontSize,
maxWidth: width,
font: pdfFontValue,
wordBreaks: [''],
});
});

@@ -286,0 +302,0 @@ };

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc