├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── core ├── .gitignore ├── README.md ├── dist │ ├── index.cjs.js │ ├── index.cjs.js.map │ ├── index.d.ts │ ├── index.esm.js │ ├── index.esm.js.map │ ├── index.html │ ├── watermark.js │ ├── watermark.js.map │ ├── watermark.min.js │ └── watermark.min.js.map ├── package.json ├── rollup.config.ts ├── src │ └── index.ts └── tsconfig.json ├── lerna.json ├── package.json ├── react ├── .kktrc.ts ├── README.md ├── package.json ├── src │ └── index.tsx └── tsconfig.json ├── renovate.json ├── test └── index.test.tsx ├── tsconfig.json └── website ├── .kktrc.ts ├── README.md ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── index.tsx └── react-app-env.d.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: jaywcjlove 2 | buy_me_a_coffee: jaywcjlove 3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"] 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | windows: 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 20 15 | 16 | - name: Look Changelog 17 | uses: jaywcjlove/changelog-generator@main 18 | with: 19 | filter-author: (小弟调调™|Renovate Bot|renovate-bot) 20 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 21 | 22 | - run: npm install 23 | - run: npm run build 24 | - run: npm run coverage 25 | - run: npm run doc 26 | 27 | build: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | with: 33 | node-version: 20 34 | registry-url: 'https://registry.npmjs.org' 35 | 36 | - run: npm install 37 | - run: npm run build 38 | - run: npm run coverage 39 | - run: npm run bundle 40 | - run: npm run bundle:min 41 | - run: npm install 42 | - run: npm run doc 43 | 44 | - name: Generate Contributors Images 45 | uses: jaywcjlove/github-action-contributors@main 46 | with: 47 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) 48 | output: website/build/CONTRIBUTORS.svg 49 | avatarSize: 42 50 | 51 | - name: Create Coverage Badges 52 | uses: jaywcjlove/coverage-badges-cli@main 53 | with: 54 | output: website/build/badges.svg 55 | 56 | - run: cp -rp coverage website/build 57 | - run: cp -rp core/dist/*.js website/build 58 | - run: cp -rp core/dist/*.ts website/build 59 | - run: cp -rp core/dist/*.map website/build 60 | - run: cp core/dist/index.html website/build/example.html 61 | 62 | - name: Create watermark-example.svg 63 | working-directory: website/build 64 | run: | 65 | cat > watermark-example.svg << EOF 66 | 67 | EOF 68 | 69 | - name: Is a tag created auto? 70 | id: create_tag 71 | uses: jaywcjlove/create-tag-action@main 72 | with: 73 | token: ${{ secrets.GITHUB_TOKEN }} 74 | package-path: ./react/package.json 75 | 76 | - name: get tag version 77 | id: tag_version 78 | uses: jaywcjlove/changelog-generator@main 79 | 80 | - name: Deploy 81 | uses: peaceiris/actions-gh-pages@v4 82 | with: 83 | commit_message: ${{steps.tag_version.outputs.tag}} ${{ github.event.head_commit.message }} 84 | github_token: ${{ secrets.GITHUB_TOKEN }} 85 | publish_dir: ./website/build 86 | 87 | - name: Generate Changelog 88 | id: changelog 89 | uses: jaywcjlove/changelog-generator@main 90 | if: steps.create_tag.outputs.successful 91 | with: 92 | head-ref: ${{ steps.create_tag.outputs.version }} 93 | filter-author: (小弟调调™|Renovate Bot|renovate-bot) 94 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 95 | 96 | - name: Create Release 97 | uses: ncipollo/release-action@v1 98 | if: steps.create_tag.outputs.successful 99 | with: 100 | allowUpdates: true 101 | token: ${{ secrets.GITHUB_TOKEN }} 102 | name: ${{ steps.changelog.outputs.tag }} 103 | tag: ${{ steps.changelog.outputs.tag }} 104 | body: | 105 | Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/uiwjs/react-watermark/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html 106 | Comparing Changes: ${{ steps.changelog.outputs.compareurl }} 107 | 108 | ${{ steps.changelog.outputs.changelog }} 109 | 110 | # - run: git status 111 | # - run: npm install @jsdevtools/npm-publish -g 112 | # - run: npm-publish --token="${{ secrets.NPM_TOKEN }}" ./react/package.json 113 | 114 | 115 | - run: npm publish --access public 116 | name: 📦 @uiw/react-watermark to NPM 117 | working-directory: react 118 | continue-on-error: true 119 | env: 120 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 121 | 122 | - run: npm publish --access public 123 | name: 📦 @uiw/watermark.js to NPM 124 | working-directory: core 125 | continue-on-error: true 126 | env: 127 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 128 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | build 5 | lib 6 | esm 7 | cjs 8 | 9 | dist.css 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn.lock 14 | yarn-debug.log* 15 | yarn-error.log* 16 | pnpm-debug.log* 17 | package-lock.json 18 | 19 | # local env files 20 | .env.local 21 | .env.*.local 22 | 23 | # Editor directories and files 24 | .DS_Store 25 | .idea 26 | .lerna_backup 27 | .vscode 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.yml 5 | package.json 6 | node_modules 7 | dist 8 | build 9 | lib 10 | esm 11 | cjs 12 | test -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 uiw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react/README.md -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | !dist -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | Watermark.js 2 | === 3 | 4 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) 5 | [![Build & Deploy](https://github.com/uiwjs/react-watermark/actions/workflows/ci.yml/badge.svg)](https://github.com/uiwjs/react-watermark/actions/workflows/ci.yml) 6 | [![Coverage Status](https://uiwjs.github.io/react-watermark/badges.svg)](https://uiwjs.github.io/react-watermark/coverage/lcov-report/) 7 | [![NPM Downloads](https://img.shields.io/npm/dm/@uiw/watermark.js.svg?style=flat)](https://www.npmjs.com/package/@uiw/watermark.js) 8 | [![NPM Version](https://img.shields.io/npm/v/@uiw/watermark.js.svg)](https://www.npmjs.com/package/@uiw/watermark.js) 9 | 10 | JavaScript library for generating image watermarks using canvas. support [`react`](https://www.npmjs.com/package/@uiw/react-watermark). 11 | 12 | ## Install 13 | 14 | ```bash 15 | npm i @uiw/watermark.js 16 | # Or 17 | npm i @uiw/react-watermark 18 | ``` 19 | 20 | ## Using 21 | 22 | ```js 23 | import Watermark from '@uiw/watermark.js'; 24 | 25 | const watermark = new Watermark({ 26 | content: 'Hello Watermark!' 27 | }); 28 | watermark.create().then((mark) => { 29 | console.log('output:', mark) 30 | }) 31 | .catch((err) => { 32 | console.log(err, 'err') 33 | }) 34 | ``` 35 | 36 | Or manually download and link `watermark.js` in your HTML, It can also be downloaded via [UNPKG](https://unpkg.com/browse/@uiw/watermark.js/): 37 | 38 | CDN: [UNPKG](https://unpkg.com/@uiw/watermark.js/dist/) | [jsDelivr](https://cdn.jsdelivr.net/npm/@uiw/watermark.js/) | [Githack](https://raw.githack.com/uiwjs/watermark.js/gh-pages/watermark.min.js) | [Statically](https://cdn.statically.io/gh/uiwjs/watermark.js/gh-pages/watermark.min.js) 39 | 40 | ```html 41 | 54 | 55 |
56 | 57 | 69 | 70 | ``` 71 | 72 | ## Used in React 73 | 74 | ```jsx 75 | import React from "react"; 76 | import Watermark from '@uiw/react-watermark'; 77 | 78 | const style = { width: '100%', maxWidth: '100%', height: 200, display: 'block' }; 79 | const text = `React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. 80 | 81 | Declarative views make your code more predictable and easier to debug.`; 82 | 83 | export default function App() { 84 | return ( 85 | 89 | 31 |
32 | 33 | 47 | 48 | -------------------------------------------------------------------------------- /core/dist/watermark.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * @uiw/watermark.js v1.0.1 3 | * JavaScript library for generating image watermarks using canvas. 4 | * 5 | * Copyright (c) 2024 kenny wang 6 | * https://github.com/uiwjs/react-watermark.git 7 | * 8 | * @website: https://uiwjs.github.io/react-watermark 9 | 10 | * Licensed under the MIT license 11 | */ 12 | 13 | (function (global, factory) { 14 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 15 | typeof define === 'function' && define.amd ? define(factory) : 16 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Watermark = factory()); 17 | })(this, (function () { 'use strict'; 18 | 19 | /** 20 | * Returns the ratio of the current display device's physical pixel resolution to CSS pixel resolution 21 | * @param context 22 | * @returns 23 | */ 24 | const getPixelRatio = (context) => { 25 | if (!context) { 26 | return 1; 27 | } 28 | const backingStore = context.backingStorePixelRatio || 29 | context.webkitBackingStorePixelRatio || 30 | context.mozBackingStorePixelRatio || 31 | context.msBackingStorePixelRatio || 32 | context.oBackingStorePixelRatio || 33 | context.backingStorePixelRatio || 34 | 1; 35 | return (window.devicePixelRatio || 1) / backingStore; 36 | }; 37 | class Watermark { 38 | constructor(options) { 39 | this.option = { 40 | gapX: 212, 41 | gapY: 222, 42 | width: 120, 43 | height: 64, 44 | rotate: -22, 45 | fontStyle: 'normal', 46 | fontWeight: 'normal', 47 | fontColor: 'rgba(0,0,0,.15)', 48 | fontSize: 16, 49 | fontFamily: 'sans-serif', 50 | }; 51 | this.option = Object.assign(Object.assign({}, this.option), options); 52 | } 53 | async create() { 54 | const { image = '', content = '', gapX = 212, gapY = 222, width = 120, height = 64, rotate = -22, fontStyle = 'normal', fontWeight = 'normal', fontColor = 'rgba(0,0,0,.15)', fontSize = 16, fontFamily = 'sans-serif', offsetLeft, offsetTop, } = this.option; 55 | const canvas = document.createElement('canvas'); 56 | const ctx = canvas.getContext('2d'); 57 | const ratio = getPixelRatio(ctx); 58 | const canvasWidth = `${(gapX + width) * ratio}px`; 59 | const canvasHeight = `${(gapY + height) * ratio}px`; 60 | const canvasOffsetLeft = offsetLeft || gapX / 2; 61 | const canvasOffsetTop = offsetTop || gapY / 2; 62 | canvas.setAttribute('width', canvasWidth); 63 | canvas.setAttribute('height', canvasHeight); 64 | return new Promise(async (resolve, reject) => { 65 | if (ctx) { 66 | ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio); 67 | ctx.rotate((Math.PI / 180) * Number(rotate)); 68 | const markWidth = width * ratio; 69 | const markHeight = height * ratio; 70 | if (image) { 71 | const img = new Image(); 72 | img.crossOrigin = 'anonymous'; 73 | img.referrerPolicy = 'no-referrer'; 74 | img.src = image; 75 | img.onload = async () => { 76 | ctx.drawImage(img, 0, 0, markWidth, markHeight); 77 | return resolve(canvas.toDataURL()); 78 | }; 79 | img.onerror = (error) => { 80 | return reject(error); 81 | }; 82 | } 83 | else if (content) { 84 | const markSize = Number(fontSize) * ratio; 85 | ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`; 86 | ctx.fillStyle = fontColor; 87 | if (Array.isArray(content)) { 88 | content === null || content === void 0 ? void 0 : content.forEach((item, index) => ctx.fillText(item, 0, index * 50)); 89 | } 90 | else { 91 | ctx.fillText(content, 0, 0); 92 | } 93 | return resolve(canvas.toDataURL()); 94 | } 95 | } 96 | else { 97 | return reject('Error: Canvas is not supported in the current environment'); 98 | } 99 | }); 100 | } 101 | } 102 | 103 | return Watermark; 104 | 105 | })); 106 | //# sourceMappingURL=watermark.js.map 107 | -------------------------------------------------------------------------------- /core/dist/watermark.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"watermark.js","sources":["../src/index.ts"],"sourcesContent":["export interface WatermarkOptions {\n /** watermark text content */\n content?: string | string[];\n /**\n * When the watermark is drawn, the rotation angle, in `°`. @default `-22`\n */\n rotate?: number;\n /**\n * High-definition print image source, for high-definition screen display,\n * it is recommended to use 2x or 3x image, and priority to use image rendering watermark.\n */\n image?: string;\n /** Horizontal spacing between watermarks. @default `212` */\n gapX?: number;\n /** vertical spacing between watermarks. @default `222` */\n gapY?: number;\n /** width of watermark. @default `120` */\n width?: number;\n /** height of watermark @default `64` */\n height?: number;\n /**\n * The vertical offset of the watermark drawn on the canvas.\n * Normally, the watermark is drawn in the middle position, ie `offsetTop = gapY / 2`\n */\n offsetLeft?: number;\n /**\n * The horizontal offset of the watermark drawn on the canvas, under normal circumstances,\n * the watermark is drawn in the middle position, ie `offsetTop = gapX / 2`\n */\n offsetTop?: number;\n /** text size @default `16` */\n fontSize?: number;\n /** text family @default `sans-serif` */\n fontFamily?: string;\n /** text weight @default `normal` */\n fontWeight?: 'normal' | 'light' | 'weight' | number;\n /** text color @default `rgba(0,0,0,.15)` */\n fontColor?: string;\n /** text style */\n fontStyle?: CanvasFillStrokeStyles['fillStyle'];\n}\n\n/**\n * Returns the ratio of the current display device's physical pixel resolution to CSS pixel resolution\n * @param context\n * @returns\n */\nconst getPixelRatio = (context: any) => {\n if (!context) {\n return 1;\n }\n const backingStore =\n context.backingStorePixelRatio ||\n context.webkitBackingStorePixelRatio ||\n context.mozBackingStorePixelRatio ||\n context.msBackingStorePixelRatio ||\n context.oBackingStorePixelRatio ||\n context.backingStorePixelRatio ||\n 1;\n return (window.devicePixelRatio || 1) / backingStore;\n};\n\nexport default class Watermark {\n option: WatermarkOptions = {\n gapX: 212,\n gapY: 222,\n width: 120,\n height: 64,\n rotate: -22,\n fontStyle: 'normal',\n fontWeight: 'normal',\n fontColor: 'rgba(0,0,0,.15)',\n fontSize: 16,\n fontFamily: 'sans-serif',\n };\n constructor(options: WatermarkOptions) {\n this.option = { ...this.option, ...options };\n }\n async create(): Promise {\n const {\n image = '',\n content = '',\n gapX = 212,\n gapY = 222,\n width = 120,\n height = 64,\n rotate = -22,\n fontStyle = 'normal',\n fontWeight = 'normal',\n fontColor = 'rgba(0,0,0,.15)',\n fontSize = 16,\n fontFamily = 'sans-serif',\n offsetLeft,\n offsetTop,\n } = this.option;\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n const ratio = getPixelRatio(ctx);\n const canvasWidth = `${(gapX + width) * ratio}px`;\n const canvasHeight = `${(gapY + height) * ratio}px`;\n const canvasOffsetLeft = offsetLeft || gapX / 2;\n const canvasOffsetTop = offsetTop || gapY / 2;\n canvas.setAttribute('width', canvasWidth);\n canvas.setAttribute('height', canvasHeight);\n return new Promise(async (resolve, reject) => {\n if (ctx) {\n ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio);\n ctx.rotate((Math.PI / 180) * Number(rotate));\n const markWidth = width * ratio;\n const markHeight = height * ratio;\n if (image) {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.referrerPolicy = 'no-referrer';\n img.src = image;\n img.onload = async () => {\n ctx.drawImage(img, 0, 0, markWidth, markHeight);\n return resolve(canvas.toDataURL());\n };\n img.onerror = (error) => {\n return reject(error);\n };\n } else if (content) {\n const markSize = Number(fontSize) * ratio;\n ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`;\n ctx.fillStyle = fontColor;\n if (Array.isArray(content)) {\n content?.forEach((item: string, index: number) => ctx.fillText(item, 0, index * 50));\n } else {\n ctx.fillText(content, 0, 0);\n }\n return resolve(canvas.toDataURL());\n }\n } else {\n return reject('Error: Canvas is not supported in the current environment');\n }\n });\n }\n}\n"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /core/dist/watermark.min.js: -------------------------------------------------------------------------------- 1 | /*! @uiw/watermark.js v1.0.1 | MIT © 2024 kenny wang https://uiwjs.github.io/react-watermark */ 2 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Watermark=e()}(this,(function(){"use strict";return class{constructor(t){this.option={gapX:212,gapY:222,width:120,height:64,rotate:-22,fontStyle:"normal",fontWeight:"normal",fontColor:"rgba(0,0,0,.15)",fontSize:16,fontFamily:"sans-serif"},this.option=Object.assign(Object.assign({},this.option),t)}async create(){const{image:t="",content:e="",gapX:o=212,gapY:n=222,width:i=120,height:r=64,rotate:a=-22,fontStyle:s="normal",fontWeight:f="normal",fontColor:l="rgba(0,0,0,.15)",fontSize:c=16,fontFamily:g="sans-serif",offsetLeft:u,offsetTop:m}=this.option,h=document.createElement("canvas"),p=h.getContext("2d"),d=(t=>{if(!t)return 1;const e=t.backingStorePixelRatio||t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1;return(window.devicePixelRatio||1)/e})(p),x=(o+i)*d+"px",y=(n+r)*d+"px",b=u||o/2,S=m||n/2;return h.setAttribute("width",x),h.setAttribute("height",y),new Promise((async(o,n)=>{if(!p)return n("Error: Canvas is not supported in the current environment");{p.translate(b*d,S*d),p.rotate(Math.PI/180*Number(a));const u=i*d,m=r*d;if(t){const e=new Image;e.crossOrigin="anonymous",e.referrerPolicy="no-referrer",e.src=t,e.onload=async()=>(p.drawImage(e,0,0,u,m),o(h.toDataURL())),e.onerror=t=>n(t)}else if(e){const t=Number(c)*d;return p.font=`${s} normal ${f} ${t}px/${m}px ${g}`,p.fillStyle=l,Array.isArray(e)?null==e||e.forEach(((t,e)=>p.fillText(t,0,50*e))):p.fillText(e,0,0),o(h.toDataURL())}}}))}}})); 3 | //# sourceMappingURL=watermark.min.js.map 4 | -------------------------------------------------------------------------------- /core/dist/watermark.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"watermark.min.js","sources":["../src/index.ts"],"sourcesContent":["export interface WatermarkOptions {\n /** watermark text content */\n content?: string | string[];\n /**\n * When the watermark is drawn, the rotation angle, in `°`. @default `-22`\n */\n rotate?: number;\n /**\n * High-definition print image source, for high-definition screen display,\n * it is recommended to use 2x or 3x image, and priority to use image rendering watermark.\n */\n image?: string;\n /** Horizontal spacing between watermarks. @default `212` */\n gapX?: number;\n /** vertical spacing between watermarks. @default `222` */\n gapY?: number;\n /** width of watermark. @default `120` */\n width?: number;\n /** height of watermark @default `64` */\n height?: number;\n /**\n * The vertical offset of the watermark drawn on the canvas.\n * Normally, the watermark is drawn in the middle position, ie `offsetTop = gapY / 2`\n */\n offsetLeft?: number;\n /**\n * The horizontal offset of the watermark drawn on the canvas, under normal circumstances,\n * the watermark is drawn in the middle position, ie `offsetTop = gapX / 2`\n */\n offsetTop?: number;\n /** text size @default `16` */\n fontSize?: number;\n /** text family @default `sans-serif` */\n fontFamily?: string;\n /** text weight @default `normal` */\n fontWeight?: 'normal' | 'light' | 'weight' | number;\n /** text color @default `rgba(0,0,0,.15)` */\n fontColor?: string;\n /** text style */\n fontStyle?: CanvasFillStrokeStyles['fillStyle'];\n}\n\n/**\n * Returns the ratio of the current display device's physical pixel resolution to CSS pixel resolution\n * @param context\n * @returns\n */\nconst getPixelRatio = (context: any) => {\n if (!context) {\n return 1;\n }\n const backingStore =\n context.backingStorePixelRatio ||\n context.webkitBackingStorePixelRatio ||\n context.mozBackingStorePixelRatio ||\n context.msBackingStorePixelRatio ||\n context.oBackingStorePixelRatio ||\n context.backingStorePixelRatio ||\n 1;\n return (window.devicePixelRatio || 1) / backingStore;\n};\n\nexport default class Watermark {\n option: WatermarkOptions = {\n gapX: 212,\n gapY: 222,\n width: 120,\n height: 64,\n rotate: -22,\n fontStyle: 'normal',\n fontWeight: 'normal',\n fontColor: 'rgba(0,0,0,.15)',\n fontSize: 16,\n fontFamily: 'sans-serif',\n };\n constructor(options: WatermarkOptions) {\n this.option = { ...this.option, ...options };\n }\n async create(): Promise {\n const {\n image = '',\n content = '',\n gapX = 212,\n gapY = 222,\n width = 120,\n height = 64,\n rotate = -22,\n fontStyle = 'normal',\n fontWeight = 'normal',\n fontColor = 'rgba(0,0,0,.15)',\n fontSize = 16,\n fontFamily = 'sans-serif',\n offsetLeft,\n offsetTop,\n } = this.option;\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n const ratio = getPixelRatio(ctx);\n const canvasWidth = `${(gapX + width) * ratio}px`;\n const canvasHeight = `${(gapY + height) * ratio}px`;\n const canvasOffsetLeft = offsetLeft || gapX / 2;\n const canvasOffsetTop = offsetTop || gapY / 2;\n canvas.setAttribute('width', canvasWidth);\n canvas.setAttribute('height', canvasHeight);\n return new Promise(async (resolve, reject) => {\n if (ctx) {\n ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio);\n ctx.rotate((Math.PI / 180) * Number(rotate));\n const markWidth = width * ratio;\n const markHeight = height * ratio;\n if (image) {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.referrerPolicy = 'no-referrer';\n img.src = image;\n img.onload = async () => {\n ctx.drawImage(img, 0, 0, markWidth, markHeight);\n return resolve(canvas.toDataURL());\n };\n img.onerror = (error) => {\n return reject(error);\n };\n } else if (content) {\n const markSize = Number(fontSize) * ratio;\n ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`;\n ctx.fillStyle = fontColor;\n if (Array.isArray(content)) {\n content?.forEach((item: string, index: number) => ctx.fillText(item, 0, index * 50));\n } else {\n ctx.fillText(content, 0, 0);\n }\n return resolve(canvas.toDataURL());\n }\n } else {\n return reject('Error: Canvas is not supported in the current environment');\n }\n });\n }\n}\n"],"names":["constructor","options","this","option","gapX","gapY","width","height","rotate","fontStyle","fontWeight","fontColor","fontSize","fontFamily","Object","assign","create","image","content","offsetLeft","offsetTop","canvas","document","createElement","ctx","getContext","ratio","context","backingStore","backingStorePixelRatio","webkitBackingStorePixelRatio","mozBackingStorePixelRatio","msBackingStorePixelRatio","oBackingStorePixelRatio","window","devicePixelRatio","getPixelRatio","canvasWidth","canvasHeight","canvasOffsetLeft","canvasOffsetTop","setAttribute","Promise","async","resolve","reject","translate","Math","PI","Number","markWidth","markHeight","img","Image","crossOrigin","referrerPolicy","src","onload","drawImage","toDataURL","onerror","error","markSize","font","fillStyle","Array","isArray","forEach","item","index","fillText"],"mappings":";iPA8Dc,MAaZ,WAAAA,CAAYC,GAZZC,KAAAC,OAA2B,CACzBC,KAAM,IACNC,KAAM,IACNC,MAAO,IACPC,OAAQ,GACRC,QAAS,GACTC,UAAW,SACXC,WAAY,SACZC,UAAW,kBACXC,SAAU,GACVC,WAAY,cAGZX,KAAKC,OAAcW,OAAAC,OAAAD,OAAAC,OAAA,CAAA,EAAAb,KAAKC,QAAWF,EACpC,CACD,YAAMe,GACJ,MAAMC,MACJA,EAAQ,GAAEC,QACVA,EAAU,GAAEd,KACZA,EAAO,IAAGC,KACVA,EAAO,IAAGC,MACVA,EAAQ,IAAGC,OACXA,EAAS,GAAEC,OACXA,GAAS,GAAGC,UACZA,EAAY,SAAQC,WACpBA,EAAa,SAAQC,UACrBA,EAAY,kBAAiBC,SAC7BA,EAAW,GAAEC,WACbA,EAAa,aAAYM,WACzBA,EAAUC,UACVA,GACElB,KAAKC,OACHkB,EAASC,SAASC,cAAc,UAChCC,EAAMH,EAAOI,WAAW,MACxBC,EAlDY,CAACC,IACrB,IAAKA,EACH,OAAO,EAET,MAAMC,EACJD,EAAQE,wBACRF,EAAQG,8BACRH,EAAQI,2BACRJ,EAAQK,0BACRL,EAAQM,yBACRN,EAAQE,wBACR,EACF,OAAQK,OAAOC,kBAAoB,GAAKP,CAAY,EAsCpCQ,CAAcZ,GACtBa,GAAkBjC,EAAOE,GAASoB,EAApB,KACdY,GAAmBjC,EAAOE,GAAUmB,EAArB,KACfa,EAAmBpB,GAAcf,EAAO,EACxCoC,EAAkBpB,GAAaf,EAAO,EAG5C,OAFAgB,EAAOoB,aAAa,QAASJ,GAC7BhB,EAAOoB,aAAa,SAAUH,GACvB,IAAII,SAAQC,MAAOC,EAASC,KACjC,IAAIrB,EA6BF,OAAOqB,EAAO,6DA7BP,CACPrB,EAAIsB,UAAUP,EAAmBb,EAAOc,EAAkBd,GAC1DF,EAAIhB,OAAQuC,KAAKC,GAAK,IAAOC,OAAOzC,IACpC,MAAM0C,EAAY5C,EAAQoB,EACpByB,EAAa5C,EAASmB,EAC5B,GAAIT,EAAO,CACT,MAAMmC,EAAM,IAAIC,MAChBD,EAAIE,YAAc,YAClBF,EAAIG,eAAiB,cACrBH,EAAII,IAAMvC,EACVmC,EAAIK,OAASd,UACXnB,EAAIkC,UAAUN,EAAK,EAAG,EAAGF,EAAWC,GAC7BP,EAAQvB,EAAOsC,cAExBP,EAAIQ,QAAWC,GACNhB,EAAOgB,EAEjB,MAAM,GAAI3C,EAAS,CAClB,MAAM4C,EAAWb,OAAOrC,GAAYc,EAQpC,OAPAF,EAAIuC,KAAO,GAAGtD,YAAoBC,KAAcoD,OAAcX,OAAgBtC,IAC9EW,EAAIwC,UAAYrD,EACZsD,MAAMC,QAAQhD,GAChBA,SAAAA,EAASiD,SAAQ,CAACC,EAAcC,IAAkB7C,EAAI8C,SAASF,EAAM,EAAW,GAARC,KAExE7C,EAAI8C,SAASpD,EAAS,EAAG,GAEpB0B,EAAQvB,EAAOsC,YACvB,CACF,CAEA,GAEJ"} -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uiw/watermark.js", 3 | "version": "1.0.1", 4 | "homepage": "https://uiwjs.github.io/react-watermark", 5 | "funding": "https://jaywcjlove.github.io/#/sponsor", 6 | "author": "kenny wang ", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/uiwjs/react-watermark.git" 10 | }, 11 | "license": "MIT", 12 | "description": "JavaScript library for generating image watermarks using canvas.", 13 | "main": "./dist/index.cjs.js", 14 | "module": "./dist/index.esm.js", 15 | "typings": "./dist/index.d.ts", 16 | "unpkg": "./dist/watermark.js", 17 | "jsdelivr": "./dist/watermark.js", 18 | "scripts": { 19 | "build": "rollup -c rollup.config.ts --configPlugin typescript", 20 | "start": "rollup -c rollup.config.ts --configPlugin typescript -w" 21 | }, 22 | "keywords": [], 23 | "files": [ 24 | "src", 25 | "dist", 26 | "!dist/index.html" 27 | ], 28 | "devDependencies": { 29 | "@rollup/plugin-commonjs": "^25.0.2", 30 | "@rollup/plugin-json": "^6.0.0", 31 | "@rollup/plugin-node-resolve": "^15.1.0", 32 | "@rollup/plugin-terser": "^0.4.3", 33 | "@rollup/plugin-typescript": "^11.1.2", 34 | "bannerjs": "^3.0.1", 35 | "rollup": "^3.26.1", 36 | "tslib": "^2.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/rollup.config.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module'; 2 | import typescript from '@rollup/plugin-typescript'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 5 | import terser from '@rollup/plugin-terser'; 6 | import { multibanner, onebanner } from 'bannerjs'; 7 | 8 | const require = createRequire(import.meta.url); 9 | const pkg = require('./package.json'); 10 | 11 | export default [ 12 | { 13 | input: 'src/index.ts', 14 | output: [ 15 | { 16 | file: pkg.unpkg, 17 | format: 'umd', 18 | exports: 'default', 19 | name: 'Watermark', 20 | banner: multibanner(), 21 | sourcemap: true, 22 | }, 23 | { 24 | file: pkg.main, 25 | format: 'cjs', 26 | exports: 'default', 27 | name: 'Watermark', 28 | banner: onebanner(), 29 | sourcemap: true, 30 | }, 31 | { 32 | file: pkg.module, 33 | format: 'esm', 34 | exports: 'default', 35 | name: 'Watermark', 36 | banner: onebanner(), 37 | sourcemap: true, 38 | }, 39 | ], 40 | plugins: [ 41 | nodeResolve({ 42 | browser: true, 43 | }), 44 | commonjs(), 45 | typescript({ 46 | tsconfig: './tsconfig.json', 47 | compilerOptions: { 48 | outDir: 'dist', 49 | declarationDir: '.', 50 | }, 51 | }), 52 | ], 53 | }, 54 | { 55 | input: 'src/index.ts', 56 | output: [ 57 | { 58 | file: pkg.unpkg.replace(/.js$/, '.min.js'), 59 | format: 'umd', 60 | exports: 'default', 61 | name: 'Watermark', 62 | banner: onebanner(), 63 | sourcemap: true, 64 | }, 65 | ], 66 | plugins: [ 67 | nodeResolve({ 68 | browser: true, 69 | }), 70 | commonjs(), 71 | typescript({ 72 | tsconfig: './tsconfig.json', 73 | compilerOptions: { 74 | outDir: 'dist', 75 | declarationDir: '.', 76 | }, 77 | }), 78 | terser({}), 79 | ], 80 | }, 81 | ]; 82 | -------------------------------------------------------------------------------- /core/src/index.ts: -------------------------------------------------------------------------------- 1 | export interface WatermarkOptions { 2 | /** watermark text content */ 3 | content?: string | string[]; 4 | /** 5 | * When the watermark is drawn, the rotation angle, in `°`. @default `-22` 6 | */ 7 | rotate?: number; 8 | /** 9 | * High-definition print image source, for high-definition screen display, 10 | * it is recommended to use 2x or 3x image, and priority to use image rendering watermark. 11 | */ 12 | image?: string; 13 | /** Horizontal spacing between watermarks. @default `212` */ 14 | gapX?: number; 15 | /** vertical spacing between watermarks. @default `222` */ 16 | gapY?: number; 17 | /** width of watermark. @default `120` */ 18 | width?: number; 19 | /** height of watermark @default `64` */ 20 | height?: number; 21 | /** 22 | * The vertical offset of the watermark drawn on the canvas. 23 | * Normally, the watermark is drawn in the middle position, ie `offsetTop = gapY / 2` 24 | */ 25 | offsetLeft?: number; 26 | /** 27 | * The horizontal offset of the watermark drawn on the canvas, under normal circumstances, 28 | * the watermark is drawn in the middle position, ie `offsetTop = gapX / 2` 29 | */ 30 | offsetTop?: number; 31 | /** text size @default `16` */ 32 | fontSize?: number; 33 | /** text family @default `sans-serif` */ 34 | fontFamily?: string; 35 | /** text weight @default `normal` */ 36 | fontWeight?: 'normal' | 'light' | 'weight' | number; 37 | /** text color @default `rgba(0,0,0,.15)` */ 38 | fontColor?: string; 39 | /** text style */ 40 | fontStyle?: CanvasFillStrokeStyles['fillStyle']; 41 | } 42 | 43 | /** 44 | * Returns the ratio of the current display device's physical pixel resolution to CSS pixel resolution 45 | * @param context 46 | * @returns 47 | */ 48 | const getPixelRatio = (context: any) => { 49 | if (!context) { 50 | return 1; 51 | } 52 | const backingStore = 53 | context.backingStorePixelRatio || 54 | context.webkitBackingStorePixelRatio || 55 | context.mozBackingStorePixelRatio || 56 | context.msBackingStorePixelRatio || 57 | context.oBackingStorePixelRatio || 58 | context.backingStorePixelRatio || 59 | 1; 60 | return (window.devicePixelRatio || 1) / backingStore; 61 | }; 62 | 63 | export default class Watermark { 64 | option: WatermarkOptions = { 65 | gapX: 212, 66 | gapY: 222, 67 | width: 120, 68 | height: 64, 69 | rotate: -22, 70 | fontStyle: 'normal', 71 | fontWeight: 'normal', 72 | fontColor: 'rgba(0,0,0,.15)', 73 | fontSize: 16, 74 | fontFamily: 'sans-serif', 75 | }; 76 | constructor(options: WatermarkOptions) { 77 | this.option = { ...this.option, ...options }; 78 | } 79 | async create(): Promise { 80 | const { 81 | image = '', 82 | content = '', 83 | gapX = 212, 84 | gapY = 222, 85 | width = 120, 86 | height = 64, 87 | rotate = -22, 88 | fontStyle = 'normal', 89 | fontWeight = 'normal', 90 | fontColor = 'rgba(0,0,0,.15)', 91 | fontSize = 16, 92 | fontFamily = 'sans-serif', 93 | offsetLeft, 94 | offsetTop, 95 | } = this.option; 96 | const canvas = document.createElement('canvas'); 97 | const ctx = canvas.getContext('2d'); 98 | const ratio = getPixelRatio(ctx); 99 | const canvasWidth = `${(gapX + width) * ratio}px`; 100 | const canvasHeight = `${(gapY + height) * ratio}px`; 101 | const canvasOffsetLeft = offsetLeft || gapX / 2; 102 | const canvasOffsetTop = offsetTop || gapY / 2; 103 | canvas.setAttribute('width', canvasWidth); 104 | canvas.setAttribute('height', canvasHeight); 105 | return new Promise(async (resolve, reject) => { 106 | if (ctx) { 107 | ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio); 108 | ctx.rotate((Math.PI / 180) * Number(rotate)); 109 | const markWidth = width * ratio; 110 | const markHeight = height * ratio; 111 | if (image) { 112 | const img = new Image(); 113 | img.crossOrigin = 'anonymous'; 114 | img.referrerPolicy = 'no-referrer'; 115 | img.src = image; 116 | img.onload = async () => { 117 | ctx.drawImage(img, 0, 0, markWidth, markHeight); 118 | return resolve(canvas.toDataURL()); 119 | }; 120 | img.onerror = (error) => { 121 | return reject(error); 122 | }; 123 | } else if (content) { 124 | const markSize = Number(fontSize) * ratio; 125 | ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`; 126 | ctx.fillStyle = fontColor; 127 | if (Array.isArray(content)) { 128 | content?.forEach((item: string, index: number) => ctx.fillText(item, 0, index * 50)); 129 | } else { 130 | ctx.fillText(content, 0, 0); 131 | } 132 | return resolve(canvas.toDataURL()); 133 | } 134 | } else { 135 | return reject('Error: Canvas is not supported in the current environment'); 136 | } 137 | }); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "module": "es2015", 5 | "target": "es2017", 6 | "esModuleInterop": true, 7 | "declaration": true, 8 | "noImplicitAny": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "strict": false, 14 | "skipLibCheck": true, 15 | "outDir": "lib", 16 | "baseUrl": "." 17 | }, 18 | "include": ["src"], 19 | "exclude": ["dist"] 20 | } 21 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.1", 3 | "packages": ["website", "core", "react"] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "⬇️⬇️⬇️⬇️⬇️ package ⬇️⬇️⬇️⬇️⬇️": "▼▼▼▼▼ package ▼▼▼▼▼", 5 | "build": "lerna exec --scope @uiw/* -- npm run build", 6 | "watch": "lerna exec --scope @uiw/react-watermark -- npm run start", 7 | "watch:core": "lerna exec --scope @uiw/watermark.js -- npm start", 8 | "bundle": "lerna exec --scope @uiw/* -- ncc build src/index.tsx --target web --filename dist", 9 | "bundle:min": "lerna exec --scope @uiw/* -- ncc build src/index.tsx --target web --filename dist --minify", 10 | "⬆️⬆️⬆️⬆️⬆️ package ⬆️⬆️⬆️⬆️⬆️": "▲▲▲▲▲ package ▲▲▲▲▲", 11 | "start": "lerna exec --scope website -- npm run start", 12 | "doc": "lerna exec --scope website -- npm run build", 13 | "bootstrap": "lerna bootstrap", 14 | "hoist": "lerna bootstrap --hoist", 15 | "test": "tsbb test", 16 | "coverage": "tsbb test --coverage --bail", 17 | "prepare": "husky install", 18 | "version": "lerna version --exact --force-publish --no-push --no-git-tag-version", 19 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 20 | "remove": "npm run clean && lerna exec \"rm -rf package-lock.json\" --scope @uiw/* --scope website", 21 | "clean": "lerna clean --yes" 22 | }, 23 | "lint-staged": { 24 | "*.{js,jsx,ts,tsx,less,md,json}": [ 25 | "prettier --write" 26 | ] 27 | }, 28 | "jest": { 29 | "collectCoverageFrom": [ 30 | "/react/src/*.{tsx,ts}", 31 | "!**/*.{js,d.ts}" 32 | ], 33 | "testMatch": [ 34 | "/test/*.{ts,tsx}", 35 | "/packages/**/__tests__/*.{ts,tsx}" 36 | ], 37 | "transformIgnorePatterns": [ 38 | "/node_modules/?!(.*)" 39 | ] 40 | }, 41 | "workspaces": [ 42 | "website", 43 | "react", 44 | "core" 45 | ], 46 | "devDependencies": { 47 | "@kkt/ncc": "^1.0.14", 48 | "@types/react-test-renderer": "^18.0.0", 49 | "compile-less-cli": "^1.8.13", 50 | "husky": "^8.0.1", 51 | "jest": "^29.5.0", 52 | "jest-canvas-mock": "^2.5.0", 53 | "jest-watch-typeahead": "^2.2.2", 54 | "jest-environment-jsdom": "^29.5.0", 55 | "lerna": "^7.1.1", 56 | "lint-staged": "^13.0.3", 57 | "prettier": "^2.7.1", 58 | "pretty-quick": "^3.1.3", 59 | "react-test-renderer": "^18.2.0", 60 | "tsbb": "^4.1.14" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /react/.kktrc.ts: -------------------------------------------------------------------------------- 1 | import { LoaderConfOptions, WebpackConfiguration } from 'kkt'; 2 | import lessModules from '@kkt/less-modules'; 3 | 4 | export default (conf: WebpackConfiguration, env: 'development' | 'production', options: LoaderConfOptions) => { 5 | conf = lessModules(conf, env, options); 6 | if (options.bundle) { 7 | conf.output!.library = '@uiw/react-watermark'; 8 | conf.externals = { 9 | react: { 10 | root: 'React', 11 | commonjs2: 'react', 12 | commonjs: 'react', 13 | amd: 'react', 14 | }, 15 | }; 16 | } 17 | return conf; 18 | }; 19 | -------------------------------------------------------------------------------- /react/README.md: -------------------------------------------------------------------------------- 1 | 2 | react-watermark 3 | === 4 | 5 | 6 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) 7 | [![Build & Deploy](https://github.com/uiwjs/react-watermark/actions/workflows/ci.yml/badge.svg)](https://github.com/uiwjs/react-watermark/actions/workflows/ci.yml) 8 | [![Coverage Status](https://uiwjs.github.io/react-watermark/badges.svg)](https://uiwjs.github.io/react-watermark/coverage/lcov-report/) 9 | [![NPM Downloads](https://img.shields.io/npm/dm/@uiw/react-watermark.svg?style=flat)](https://www.npmjs.com/package/@uiw/react-watermark) 10 | [![NPM Version](https://img.shields.io/npm/v/@uiw/react-watermark.svg)](https://www.npmjs.com/package/@uiw/react-watermark) 11 | 12 | A react component that adds a watermark to an area of a web page. Example Preview: https://uiwjs.github.io/react-watermark 13 | 14 | ## Install 15 | 16 | Not dependent on **uiw**. 17 | 18 | ```bash 19 | npm i @uiw/react-watermark 20 | # Or 21 | npm i @uiw/watermark.js 22 | ``` 23 | 24 | ### Using 25 | 26 | ```jsx mdx:preview 27 | import React from "react"; 28 | import Watermark from '@uiw/react-watermark'; 29 | 30 | const style = { width: '100%', maxWidth: '100%', height: 200, display: 'block' }; 31 | const text = `React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. 32 | 33 | Declarative views make your code more predictable and easier to debug.`; 34 | 35 | export default function App() { 36 | return ( 37 | 41 |