├── .gitignore ├── banner.jpg ├── icons ├── dark@1x.png ├── dark@2x.png ├── light@1x.png ├── light@2x.png ├── plugin@1x.png └── plugin@2x.png ├── img ├── pan.svg ├── bisexual.svg ├── inter.svg ├── genderqueer.svg ├── asexual.svg ├── non-binary.svg ├── aromantic.svg ├── lesbian.svg ├── trans.svg ├── rainbow.svg ├── genderfluid.svg ├── agender.svg └── gay-men.svg ├── tsconfig.json ├── logos ├── dark.svg ├── light.svg ├── plugin.svg └── marketing.svg ├── lib ├── text-handling.cjs └── colors.cjs ├── manifest.json ├── .github └── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug.yml ├── README.md ├── index.html ├── LICENSE └── index.cjs /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pklaschka/indesign-pride-flagger/HEAD/banner.jpg -------------------------------------------------------------------------------- /icons/dark@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pklaschka/indesign-pride-flagger/HEAD/icons/dark@1x.png -------------------------------------------------------------------------------- /icons/dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pklaschka/indesign-pride-flagger/HEAD/icons/dark@2x.png -------------------------------------------------------------------------------- /icons/light@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pklaschka/indesign-pride-flagger/HEAD/icons/light@1x.png -------------------------------------------------------------------------------- /icons/light@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pklaschka/indesign-pride-flagger/HEAD/icons/light@2x.png -------------------------------------------------------------------------------- /icons/plugin@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pklaschka/indesign-pride-flagger/HEAD/icons/plugin@1x.png -------------------------------------------------------------------------------- /icons/plugin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pklaschka/indesign-pride-flagger/HEAD/icons/plugin@2x.png -------------------------------------------------------------------------------- /img/pan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noEmit": true, 5 | "target": "es5", 6 | "allowJs": true, 7 | "sourceMap": true 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ], 12 | "files": [ 13 | "index.cjs", 14 | "types.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /img/bisexual.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /img/inter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /img/genderqueer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /img/asexual.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /img/non-binary.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /img/aromantic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /img/lesbian.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /img/trans.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /img/rainbow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /img/genderfluid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /img/agender.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /img/gay-men.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /logos/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /logos/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logos/plugin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logos/marketing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /lib/text-handling.cjs: -------------------------------------------------------------------------------- 1 | const cbPadding = document.querySelector('#cb-padding'); 2 | const txtTextfield = document.querySelector('#txt-message'); 3 | 4 | function getTextMessage() { 5 | if (!txtTextfield || txtTextfield.value === '') { 6 | return undefined; 7 | } 8 | return txtTextfield.value.toString(); 9 | } 10 | 11 | /** 12 | * 13 | * @param {{parent: import('indesign').Page, bounds: [number, number, number, number]}} newPage 14 | */ 15 | function createText({ parent, bounds }) { 16 | let textFrame = parent.textFrames.add(); 17 | textFrame.textFramePreferences.verticalJustification = VerticalJustification.CENTER_ALIGN; 18 | 19 | textFrame.contents = getTextMessage() || ''; 20 | textFrame.paragraphs.firstItem().justification = Justification.CENTER_ALIGN; 21 | textFrame.paragraphs.firstItem().pointSize = 32; 22 | textFrame.paragraphs.firstItem().appliedFont = app.fonts.itemByName('Arial'); 23 | 24 | const padding = cbPadding.checked ? parent.marginPreferences.columnGutter : 0; 25 | textFrame.geometricBounds = [ 26 | bounds[0] + bounds[2] / 5 * 2 + padding, 27 | bounds[1] + parent.marginPreferences.left + padding, 28 | bounds[2] / 5 * 3 - padding, 29 | bounds[3] - parent.marginPreferences.right - padding 30 | ]; 31 | textFrame.fillColor = getOrCreateColor('White', [0, 0, 0, 0]); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ae7b0450", 3 | "name": "Pride Flagger", 4 | "version": "1.1.0", 5 | "main": "index.html", 6 | "host": [ 7 | { 8 | "app": "ID", 9 | "minVersion": "18.5.0" 10 | } 11 | ], 12 | "manifestVersion": 5, 13 | "entrypoints": [ 14 | { 15 | "type": "panel", 16 | "id": "showPanel", 17 | "minimumSize": { 18 | "width": 230, 19 | "height": 200 20 | }, 21 | "maximumSize": { 22 | "width": 720, 23 | "height": 2000 24 | }, 25 | "preferredDockedSize": { 26 | "width": 360, 27 | "height": 580 28 | }, 29 | "preferredFloatingSize": { 30 | "width": 360, 31 | "height": 580 32 | }, 33 | "label": { 34 | "default": "Pride Flagger" 35 | }, 36 | "icons": [ 37 | { 38 | "width": 23, 39 | "height": 23, 40 | "path": "icons/dark.png", 41 | "scale": [ 42 | 1, 43 | 2 44 | ], 45 | "theme": [ 46 | "darkest", 47 | "dark", 48 | "medium" 49 | ] 50 | }, 51 | { 52 | "width": 23, 53 | "height": 23, 54 | "path": "icons/light.png", 55 | "scale": [ 56 | 1, 57 | 2 58 | ], 59 | "theme": [ 60 | "lightest", 61 | "light" 62 | ] 63 | } 64 | ] 65 | } 66 | ], 67 | "icons": [ 68 | { 69 | "width": 24, 70 | "height": 24, 71 | "path": "icons/plugin.png", 72 | "scale": [ 73 | 1, 74 | 2 75 | ], 76 | "theme": [ 77 | "darkest", 78 | "dark", 79 | "medium", 80 | "lightest", 81 | "light", 82 | "all" 83 | ], 84 | "species": [ 85 | "pluginList" 86 | ] 87 | } 88 | ], 89 | "requiredPermissions": { 90 | "launchProcess": { 91 | "schemes": ["https"] 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 'Feature Request (user story)' 2 | description: 'Suggest a new feature or improvement to help us improve' 3 | 4 | assignees: 5 | - 'pklaschka' 6 | 7 | labels: 8 | - 'enhancement' 9 | 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Thank you for suggesting a new feature. Please fill out the following form to help us implement it. 15 | 16 | Please describe the feature in as much detail as possible. If you have a screenshot or video of the feature, please attach it to this issue. 17 | - type: input 18 | id: role 19 | attributes: 20 | label: 'Role' 21 | description: 'Please describe your role (e.g., "graphic designer", "editor", "project manager", ...).' 22 | placeholder: 'graphic designer' 23 | - type: textarea 24 | id: feature-description 25 | validations: 26 | required: true 27 | attributes: 28 | label: 'Feature Description' 29 | description: 'Please describe the feature you would like to see implemented.' 30 | placeholder: 'I would like to be able to insert a flag that ...' 31 | - type: textarea 32 | id: motivation 33 | validations: 34 | required: true 35 | attributes: 36 | label: 'Motivation' 37 | description: 'Please describe why you would like to see this feature implemented or how it would help you.' 38 | placeholder: 'Because it would help me to ...' 39 | - type: markdown 40 | attributes: 41 | value: | 42 | ## Additional Information 43 | 44 | Please fill out the following information to help us implement the feature. 45 | - type: textarea 46 | id: additional-information 47 | attributes: 48 | label: 'Additional Information' 49 | description: 'Please provide any additional information that might help us implement this feature.' 50 | placeholder: 'I could also imagine this as a bigger feature to ...' 51 | -------------------------------------------------------------------------------- /lib/colors.cjs: -------------------------------------------------------------------------------- 1 | const { app, ColorSpace } = require("indesign"); 2 | 3 | module.exports = { 4 | getOrCreateColor 5 | } 6 | 7 | function getOrCreateColor(name, colorValue) { 8 | let color = app.activeDocument.colors.itemByName(name); 9 | colorValue = toColorArray(colorValue); 10 | if (!color.isValid) { 11 | console.log(`Creating color ${name} because it doesn't exist`); 12 | color = app.activeDocument.colors.add({ 13 | name: name, 14 | space: colorValue.length > 3 ? ColorSpace.CMYK : ColorSpace.RGB, 15 | colorValue: colorValue, 16 | parentColorGroup: getOrCreateColorGroup() 17 | }); 18 | color.space = ColorSpace.CMYK; 19 | } 20 | return color; 21 | } 22 | 23 | /** 24 | * Converts a color to an InDesign color array 25 | * @param {string | number[]} color The color to convert. Can be a hex string or an array of (3) RGB or (4) CMYK values 26 | * @returns {number[]} The color as an array of either (3) RGB (for HEX and RGB input) or (4) CMYK (for CMYK input) values 27 | * @throws {Error} If the color is not a valid color 28 | */ 29 | function toColorArray(color) { 30 | if (Array.isArray(color)) 31 | return color; 32 | if (typeof color !== 'string') 33 | throw new Error(`Invalid color ${color}`); 34 | if (color.startsWith('#')) 35 | color = color.substring(1); 36 | if (color.length === 3) 37 | color = color.split('').map(c => c + c).join(''); 38 | if (color.length !== 6) 39 | throw new Error(`Invalid color ${color}. Must be 3 or 6 characters long`); 40 | return [ 41 | parseInt(color.substring(0, 2), 16), 42 | parseInt(color.substring(2, 4), 16), 43 | parseInt(color.substring(4, 6), 16) 44 | ]; 45 | } 46 | 47 | function getOrCreateColorGroup() { 48 | let colorGroup = app.activeDocument.colorGroups.itemByName('Pride Colors'); 49 | if (!colorGroup.isValid) { 50 | console.log(`Creating color group Pride Colors because it doesn't exist`); 51 | colorGroup = app.activeDocument.colorGroups.add('Pride Colors', undefined, {}); 52 | } 53 | return colorGroup; 54 | } 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 'Bug Report' 2 | description: 'Report a bug to help us improve' 3 | 4 | assignees: 5 | - 'pklaschka' 6 | 7 | labels: 8 | - 'bug' 9 | 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Thank you for reporting a bug. Please fill out the following form to help us fix it. 15 | 16 | Please describe the bug in as much detail as possible. If you have a screenshot or a video of the bug, please attach it to this issue. 17 | - type: textarea 18 | id: bug-description 19 | validations: 20 | required: true 21 | attributes: 22 | label: 'Bug Description' 23 | description: 'Please described what happened.' 24 | placeholder: 'InDesign crashed after inserting a flag / I got an error message when trying to insert a flag / ...' 25 | - type: textarea 26 | id: bug-expected 27 | attributes: 28 | label: 'Expected behavior' 29 | description: 'Please describe what you expected to happen.' 30 | placeholder: 'The flag should have been inserted / The flag should have been inserted and the text should have been formatted / ...' 31 | - type: textarea 32 | id: bug-steps 33 | attributes: 34 | label: 'Steps to reproduce' 35 | description: 'Please describe the steps to reproduce the bug.' 36 | placeholder: '1. Open InDesign / 2. Insert a flag / 3. ...' 37 | - type: markdown 38 | attributes: 39 | value: | 40 | ## Additional Information 41 | Please fill out the following information to help us fix the bug. 42 | - type: dropdown 43 | id: os 44 | attributes: 45 | label: 'Operating System' 46 | description: 'Please select your operating system.' 47 | options: 48 | - 'Windows' 49 | - 'macOS' 50 | - type: input 51 | id: indesign-version 52 | attributes: 53 | label: 'InDesign Version' 54 | description: 'Please enter your InDesign version. You can find it in the "About InDesign" dialog (Help > About InDesign).' 55 | placeholder: '18.5 64 Bit' 56 | - type: markdown 57 | attributes: 58 | value: | 59 | Thank you for reporting this bug. We will try to fix it as soon as possible. 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pride Flagger Plugin Logo 2 | 3 | # Pride Flagger for Adobe InDesign 4 | 5 | This is a UXP plugin for Adobe InDesign that allows you to add pride flags to your documents. It was created by [Zuri Klaschka (they/them)](https://github.com/pklaschka). 6 | 7 | ![Banner with the rainbow flag in the background and a screenshot of a panel with buttons to add pride flags in the foreground. Text: Show Pride! LGBTQIA+ Flags at your Fingertips. by Zuri Klaschka (they/them)](banner.jpg) 8 | 9 | ## Installation 10 | 11 | ### From the Adobe Exchange 12 | 13 | (Coming soon) 14 | 15 | ### From the Releases 16 | 17 | 1. Download the latest release from the [releases page](https://github.com/pklaschka/indesign-pride-flagger/releases). 18 | 2. Double-click the `.ccx` file to install it. 19 | 3. Follow the instructions in the installer. 20 | 4. Restart Adobe InDesign. 21 | 22 | ### From Source 23 | 24 | 1. Clone this repository. 25 | 2. Run the UXP Developer Tool (installed with Adobe Creative Cloud Desktop). 26 | 3. Click "Add Plugin" and select the `manifest.json` file in the repository. 27 | 4. Run InDesign (v18.5 or above). 28 | 5. In the UXP Developer Tool, click "Load" und the Pride Flagger plugin's menu. 29 | 30 | ## Usage 31 | 32 | ### Adding a Pride Flag page 33 | 34 | 1. Open the Pride Flagger plugin from the "Plugins" menu. 35 | 2. Make sure you haven't selected any objects in your document (in the Pride Flagger panel, there should be a blue bar saying _"Creating a new page (nothing selected)"_). 36 | 3. Click the button corresponding to the flag you want to add. 37 | 4. A new page will be added to your document with the flag on it. 38 | 39 | ### Adding a Pride Flag to an existing page 40 | 41 | 1. Open the Pride Flagger plugin from the "Plugins" menu. 42 | 2. Select the object you want to add the flag to (you can only select rectangles and groups at the moment). 43 | 3. Click the button corresponding to the flag you want to add. 44 | 4. The flag will be added in the place of (and replacing) the selected frame. 45 | 46 | ## FAQ 47 | 48 | ### Why is the plugin called "Pride Flagger"? 49 | 50 | Because it adds pride flags to your document. 51 | 52 | ### Should you use pride flags in commercial settings? 53 | 54 | Yes, you should. Pride flags are a symbol of acceptance and inclusion, and should be used in all settings, including commercial ones. 55 | 56 | However, you should make sure that you don't just add pride flags to your documents, but also make sure that you are actively supporting the LGBTQIA+ community. Otherwise, it might get seen as "rainbow-washing" (similar to "green-washing"), which the community doesn't usually appreciate. 57 | 58 | ### Don't you also do a kind of rainbow washing by building this plugin and thus creating a product from it? 59 | 60 | No. First of all, I myself am part of the LGBTQIA+ community (to be more precise, of the 🏳️‍⚧️ _T_ and the _A_ in the acronym), so I'm not doing this for money. I'm not selling this plugin (and instead provide it as free and open-source software). I'm just building it because I think it's fun, and because I myself want to use it. 61 | 62 | ### Why would you want to add pride flags to your documents? 63 | 64 | Because it's fun, and because it shows that you support the LGBTQIA+ community. 65 | 66 | ### Why are there only a few flags? 67 | 68 | I'm adding more flags as I go. If you want to add a flag, feel free to open a pull request. 69 | 70 | ### Is this the first (complete) open-source UXP-based plugin for Adobe InDesign ever published? 71 | 72 | I ... think so 😅. 73 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pride Flags 5 | 6 | 7 | 51 | 52 | 53 | 54 |
55 | Pride Flags 56 | Add pride flags to your document and show support for the LGBTQIA+ community 🏳️‍⚧️🏳️‍🌈! 57 |
58 | 66 |
67 | 68 | Mode: Loading... 69 | 70 |
71 | General pride flags 72 | 73 | Rainbow flag 74 | Add a rainbow flag 75 | 76 | Sexual and Romantic Orientation 77 | 78 | Asexual flag 79 | Add an asexual flag 80 | 81 | 82 | Aromantic flag 83 | Add an aromantic flag 84 | 85 | 86 | Bisexual flag 87 | Add a bisexual flag 88 | 89 | 90 | Pansexual flag 91 | Add a pansexual flag 92 | 93 | 94 | Lesbian flag 95 | Add a lesbian flag 96 | 97 | 98 | Gay Men's Pride flag 99 | Add a gay men's flag 100 | 101 | Gender & Sex 102 | 103 | Trans flag 104 | Add a trans flag 105 | 106 | 107 | Non-binary flag 108 | Add a non-binary flag 109 | 110 | 111 | Inter flag 112 | Add a inter flag 113 | 114 | 115 | Genderqueer flag 116 | Add a genderqueer flag 117 | 118 | 119 | Genderfluid flag 120 | Add a genderfluid flag 121 | 122 | 123 | Agender flag 124 | Add a agender flag 125 | 126 | 134 |
135 | 136 | Invalid selection 137 | 138 | Please select a rectangle or group to replace, or nothing to create a new page. 139 | 140 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2023 Zuri Klaschka 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /index.cjs: -------------------------------------------------------------------------------- 1 | const { 2 | app, 3 | ScriptLanguage, 4 | UndoModes, 5 | Rectangle, 6 | Group, 7 | Application, StrokeAlignment, MeasurementUnits 8 | } = require("indesign"); 9 | const {getOrCreateColor} = require("./lib/colors.cjs"); 10 | 11 | // more to get type definitions for the InDesign DOM than anything else: 12 | if (!(app instanceof Application)) { 13 | throw new Error('This script must be run from InDesign'); 14 | } 15 | 16 | const buttons = { 17 | '#btn-rainbow': createRainbowFlag, 18 | '#btn-asexual': createAsexualFlag, 19 | '#btn-aromantic': createAromanticFlag, 20 | '#btn-bisexual': createBisexualFlag, 21 | '#btn-pan': createPanFlag, 22 | '#btn-lesbian': createLesbianFlag, 23 | '#btn-gay-men': createGayMenFlag, 24 | '#btn-trans': createTransFlag, 25 | '#btn-non-binary': createNonBinaryFlag, 26 | '#btn-inter': createInterFlag, 27 | '#btn-genderqueer': createGenderqueerFlag, 28 | '#btn-genderfluid': createGenderfluidFlag, 29 | '#btn-agender': createAgenderFlag, 30 | } 31 | 32 | Object.entries(buttons).forEach(([selector, flagFunction]) => { 33 | const button = document.querySelector(selector); 34 | button.addEventListener('click', () => { 35 | createPrideFlag(flagFunction, `${button.textContent.trim()}`) 36 | }); 37 | }); 38 | 39 | function createPrideFlag(flagFunction, historyName = 'Add pride flag') { 40 | if (checkSelection() === 'invalid') 41 | return document.querySelector('#selection-error-dialog').showModal(); 42 | app.doScript(() => { 43 | const newPage = getOrCreateFlagLocation(); 44 | flagFunction(newPage); 45 | }, ScriptLanguage.UXPSCRIPT, [], UndoModes.ENTIRE_SCRIPT, historyName); 46 | } 47 | 48 | /** 49 | * Create a stripe in the given parent 50 | * 51 | * @param {import('indesign').Graphic} parent 52 | * @param {[number, number, number, number]} bounds 53 | * @param color 54 | * @param index 55 | * @param total 56 | * @returns {Rectangle} 57 | */ 58 | function createStripe({parent, bounds}, color, index = 0, total = 1) { 59 | const rect = parent.rectangles.add(); 60 | rect.strokeWeight = 0; 61 | rect.fillColor = color; 62 | rect.geometricBounds = [ 63 | bounds[0] + (bounds[2] - bounds[0]) / total * index, 64 | bounds[1], 65 | bounds[0] + (bounds[2] - bounds[0]) / total * (index + 1), 66 | bounds[3] 67 | ]; 68 | 69 | return rect; 70 | } 71 | 72 | /** 73 | * A list of transforms that can be restored, mapped to their "base" / reset values 74 | * @type {Omit} 75 | */ 76 | const restorableTransforms = { 77 | rotationAngle: 0, 78 | shearAngle: 0, 79 | horizontalScale: 100, 80 | verticalScale: 100 81 | } 82 | 83 | /** 84 | * @typedef {{ 85 | * parent: import('indesign').Graphic | import('indesign').Page, 86 | * bounds: [number, number, number, number], 87 | * rotationAngle: number, 88 | * shearAngle: number, 89 | * horizontalScale: number, 90 | * verticalScale: number 91 | * }} FlagLocation 92 | */ 93 | 94 | /** 95 | * Create a group of stripes (and optionally other elements) and create a group from them 96 | * @param {FlagLocation} flagLocation 97 | * @param colors 98 | * @param {(FlagLocation) => any[]} additionalElements 99 | * @returns {*} 100 | */ 101 | function createStripes(flagLocation, colors, additionalElements = () => []) { 102 | const total = colors.length; 103 | 104 | // create a sprite for each color 105 | const mapColorsToSprites = (color, index) => { 106 | if (color === undefined) return; 107 | return createStripe(flagLocation, color, index, total) 108 | }; 109 | 110 | // filter out empty values 111 | const stripes = colors.map(mapColorsToSprites).filter(Boolean); 112 | 113 | // if we have no stripes, return 114 | if (stripes.length === 0) return; 115 | 116 | // create a group from the stripes 117 | const group = flagLocation.parent.groups.add([...stripes, ...additionalElements( 118 | flagLocation, 119 | )]); 120 | 121 | // Apply transforms 122 | for (const key in restorableTransforms) { 123 | if (!flagLocation.hasOwnProperty(key)) continue; 124 | group[key] = flagLocation[key]; 125 | } 126 | 127 | // select the group 128 | app.selection = [group]; 129 | 130 | // return the group 131 | return group; 132 | } 133 | 134 | const modeAlertContainer = document.querySelector('#selection-status-alert'); 135 | const mode = document.querySelector('#mode'); 136 | 137 | setInterval(checkSelection, 500); 138 | 139 | function checkSelection() { 140 | if (app.documents.length < 1) { 141 | console.debug('No documents open, aborting') 142 | modeAlertContainer.classList.add('alert__invalid'); 143 | mode.textContent = 'Please open a document'; 144 | 145 | return 'invalid'; 146 | } 147 | 148 | if (app.selection.length > 1) { 149 | modeAlertContainer.classList.add('alert__invalid'); 150 | mode.textContent = 'Please select only one layer (or nothing)'; 151 | 152 | return 'invalid'; 153 | } 154 | if (app.selection.length === 0) { 155 | modeAlertContainer.classList.remove('alert__invalid'); 156 | mode.textContent = 'Creating a new page (nothing selected)'; 157 | 158 | return 'new'; 159 | } 160 | const selectionElement = app.selection[0]; 161 | if (!(selectionElement instanceof Rectangle) && !(selectionElement instanceof Group)) { 162 | modeAlertContainer.classList.add('alert__invalid'); 163 | mode.textContent = 'Please select a rectangle or group'; 164 | 165 | return 'invalid'; 166 | } 167 | 168 | modeAlertContainer.classList.remove('alert__invalid'); 169 | mode.textContent = 'Replacing selection'; 170 | return 'replace'; 171 | } 172 | 173 | /** 174 | * Get the current selection's parent page or create a new page if there is no selection 175 | * @returns {FlagLocation} 176 | */ 177 | function getOrCreateFlagLocation() { 178 | if (app.selection.length > 1) throw new Error('Please select only one layer'); 179 | if (app.selection.length === 0) { 180 | const doc = app.activeDocument; 181 | const newPage = doc.pages.add(); 182 | return { 183 | parent: newPage, 184 | bounds: newPage.bounds, 185 | ...restorableTransforms 186 | }; 187 | } 188 | const selectionElement = app.selection[0]; 189 | if (!(selectionElement instanceof Rectangle) && !(selectionElement instanceof Group)) throw new Error('Please select a rectangle'); 190 | 191 | console.debug('Selection is', selectionElement) 192 | 193 | // store the current transforms 194 | /** 195 | * @type {Omit} 196 | */ 197 | const transforms = Object.keys(restorableTransforms).reduce((acc, key) => { 198 | acc[key] = selectionElement[key]; 199 | return acc; 200 | }, {}); 201 | 202 | // reset the transforms to their defaults, so we can get the bounds 203 | Object.assign(selectionElement, restorableTransforms); 204 | 205 | const flagLocation = { 206 | parent: selectionElement.parent, 207 | bounds: selectionElement.geometricBounds, 208 | ...transforms, 209 | }; 210 | selectionElement.remove(); 211 | return flagLocation; 212 | } 213 | 214 | function createTransFlag(newPage) { 215 | createStripes(newPage, [ 216 | getOrCreateColor('Trans Flag Blue', '#5BCFFB'), 217 | getOrCreateColor('Trans Flag Pink', '#F5A9B8'), 218 | getOrCreateColor('Trans Flag White', '#FFFFFF'), 219 | getOrCreateColor('Trans Flag Pink', '#F5A9B8'), 220 | getOrCreateColor('Trans Flag Blue', '#5BCFFB'), 221 | ]) 222 | } 223 | 224 | function createInterFlag(newPage) { 225 | createStripes(newPage, [ 226 | getOrCreateColor('Inter Yellow', '#FFD500'), 227 | ], flagLocation => { 228 | const oval = flagLocation.parent.ovals.add(); 229 | 230 | const smallerDimension = Math.min(flagLocation.bounds[2] - flagLocation.bounds[0], flagLocation.bounds[3] - flagLocation.bounds[1]); 231 | const innerDimension = smallerDimension * 0.4; 232 | oval.geometricBounds = [ 233 | flagLocation.bounds[0] + (flagLocation.bounds[2] - flagLocation.bounds[0]) / 2 - innerDimension / 2, 234 | flagLocation.bounds[1] + (flagLocation.bounds[3] - flagLocation.bounds[1]) / 2 - innerDimension / 2, 235 | flagLocation.bounds[0] + (flagLocation.bounds[2] - flagLocation.bounds[0]) / 2 + innerDimension / 2, 236 | flagLocation.bounds[1] + (flagLocation.bounds[3] - flagLocation.bounds[1]) / 2 + innerDimension / 2, 237 | ]; 238 | 239 | const originalVerticalMeasurementUnits = app.activeDocument.viewPreferences.verticalMeasurementUnits; 240 | app.activeDocument.viewPreferences.verticalMeasurementUnits = MeasurementUnits.MILLIMETERS; 241 | 242 | oval.strokeWeight = ((oval.geometricBounds[2] - oval.geometricBounds[0]) * 0.225) + 'mm'; 243 | app.activeDocument.viewPreferences.verticalMeasurementUnits = originalVerticalMeasurementUnits; 244 | 245 | oval.strokeAlignment = StrokeAlignment.OUTSIDE_ALIGNMENT; 246 | 247 | 248 | console.log(oval.strokeWeight) 249 | oval.strokeColor = getOrCreateColor('Intersex Purple', '#7A02AA'); 250 | return [oval]; 251 | }) 252 | } 253 | 254 | function createRainbowFlag(newPage) { 255 | createStripes(newPage, [ 256 | getOrCreateColor('Pride Red', '#FF0018'), 257 | getOrCreateColor('Pride Orange', '#FFA52C'), 258 | getOrCreateColor('Pride Yellow', '#FFFF41'), 259 | getOrCreateColor('Pride Green', '#008018'), 260 | getOrCreateColor('Pride Blue', '#0000F9'), 261 | getOrCreateColor('Pride Purple', '#86007D') 262 | ]) 263 | } 264 | 265 | function createNonBinaryFlag(newPage) { 266 | createStripes(newPage, [ 267 | getOrCreateColor('Non-Binary Yellow', '#FCF431'), 268 | getOrCreateColor('Non-Binary White', '#FFFFFF'), 269 | getOrCreateColor('Non-Binary Purple', '#9C59D1'), 270 | getOrCreateColor('Non-Binary Black', '#000000') 271 | ]) 272 | } 273 | 274 | function createPanFlag(newPage) { 275 | createStripes(newPage, [ 276 | getOrCreateColor('Pansexual Pink', '#FF1B8D'), 277 | getOrCreateColor('Pansexual Yellow', '#FFDA00'), 278 | getOrCreateColor('Pansexual Blue', '#1BB3FF') 279 | ]) 280 | } 281 | 282 | function createBisexualFlag(newPage) { 283 | createStripes(newPage, [ 284 | getOrCreateColor('Bisexual Pink', '#D60270'), 285 | getOrCreateColor('Bisexual Purple', '#9B4F96'), 286 | getOrCreateColor('Bisexual Blue', '#0038A8') 287 | ]) 288 | } 289 | 290 | function createAsexualFlag(newPage) { 291 | createStripes(newPage, [ 292 | getOrCreateColor('Asexual Black', '#000000'), 293 | getOrCreateColor('Asexual Grey', '#a4a4a4'), 294 | getOrCreateColor('Asexual White', '#ffffff'), 295 | getOrCreateColor('Asexual Purple', '#810081') 296 | ]) 297 | } 298 | 299 | function createAromanticFlag(newPage) { 300 | createStripes(newPage, [ 301 | getOrCreateColor('Aromantic Green 1', '#3ba740'), 302 | getOrCreateColor('Aromantic Green 2', '#a8d47a'), 303 | getOrCreateColor('Aromantic White', '#ffffff'), 304 | getOrCreateColor('Aromantic Grey', '#ababab'), 305 | getOrCreateColor('Aromantic Black', '#000000'), 306 | ]) 307 | } 308 | 309 | function createLesbianFlag(newPage) { 310 | createStripes(newPage, [ 311 | getOrCreateColor('Lesbian Red', '#D52D00'), 312 | getOrCreateColor('Lesbian Orange', '#FF9A56'), 313 | getOrCreateColor('Lesbian White', '#FFFFFF'), 314 | getOrCreateColor('Lesbian Light Pink', '#D462A6'), 315 | getOrCreateColor('Lesbian Pink', '#A50062'), 316 | ]) 317 | } 318 | 319 | function createGenderfluidFlag(newPage) { 320 | createStripes(newPage, [ 321 | getOrCreateColor('Genderfluid Pink', '#FE76A2'), 322 | getOrCreateColor('Genderfluid White', '#FFFFFF'), 323 | getOrCreateColor('Genderfluid Violet', '#BF12d7'), 324 | getOrCreateColor('Genderfluid Black', '#000000'), 325 | getOrCreateColor('Genderfluid Blue', '#303CBE') 326 | ]); 327 | } 328 | 329 | function createAgenderFlag(newPage) { 330 | createStripes(newPage, [ 331 | getOrCreateColor('Agender Black', '#000'), 332 | getOrCreateColor('Agender Gray', '#bababa'), 333 | getOrCreateColor('Agender White', '#fff'), 334 | getOrCreateColor('Agender Green', '#baf484'), 335 | getOrCreateColor('Agender White', '#fff'), 336 | getOrCreateColor('Agender Gray', '#bababa'), 337 | getOrCreateColor('Agender Black', '#000'), 338 | ]); 339 | } 340 | 341 | function createGayMenFlag(newPage) { 342 | createStripes(newPage, [ 343 | getOrCreateColor('Gay Men Green 1', '#068E70'), 344 | getOrCreateColor('Gay Men Green 2', '#27CFAA'), 345 | getOrCreateColor('Gay Men Green 3', '#98e9c1'), 346 | getOrCreateColor('Gay Men White', '#FFFFFF'), 347 | getOrCreateColor('Gay Men Blue 1', '#7bade2'), 348 | getOrCreateColor('Gay Men Blue 2', "#5049cb"), 349 | getOrCreateColor('Gay Men Blue 3', "#3c1a77"), 350 | ]); 351 | } 352 | 353 | function createGenderqueerFlag(newPage) { 354 | createStripes(newPage, [ 355 | getOrCreateColor('Genderqueer Purple', "#b57fdd"), 356 | getOrCreateColor('Genderqueer White', '#fff'), 357 | getOrCreateColor('Genderqueer Green', "#49821e") 358 | ]); 359 | } 360 | --------------------------------------------------------------------------------