purgetss 7.7.1 → 7.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/bin/purgetss +23 -0
- package/dist/purgetss.ui.js +1 -1
- package/lib/templates/create/index.xml +1 -1
- package/lib/templates/purgetss.config.js.cjs +3 -1
- package/package.json +2 -2
- package/src/cli/commands/build.js +9 -4
- package/src/cli/commands/images.js +49 -2
- package/src/cli/commands/purge.js +31 -4
- package/src/cli/commands/shades.js +2 -2
- package/src/cli/utils/cli-helpers.js +15 -5
- package/src/cli/utils/unsupported-class-reporter.js +209 -0
- package/src/core/analyzers/class-extractor.js +54 -0
- package/src/core/analyzers/controller-svg-refs.js +154 -0
- package/src/core/branding/brand-config.js +7 -0
- package/src/core/branding/ensure-brand-section.js +4 -3
- package/src/core/branding/gen-feature-graphic.js +57 -0
- package/src/core/branding/index.js +28 -4
- package/src/core/branding/post-gen-notes.js +2 -2
- package/{experimental/completions2.js → src/core/builders/auto-utilities-builder.js} +74 -40
- package/src/core/builders/tailwind-builder.js +2 -2
- package/src/core/builders/tailwind-helpers.js +0 -444
- package/src/core/images/ensure-images-section.js +6 -4
- package/src/core/images/gen-scales.js +96 -13
- package/src/core/images/index.js +121 -9
- package/src/core/purger/icon-purger.js +7 -3
- package/src/core/purger/tailwind-purger.js +43 -5
- package/src/core/svg/cache.js +96 -0
- package/src/core/svg/derive-dimensions.js +120 -0
- package/src/core/svg/index.js +215 -0
- package/src/core/svg/resolve-classes.js +46 -0
- package/src/core/svg/sync-images.js +278 -0
- package/src/core/svg/tss-reader.js +134 -0
- package/src/dev/builders/tailwind-builder.js +3 -11
- package/src/shared/config-manager.js +72 -3
- package/src/shared/error-reporter.js +117 -0
- package/src/shared/helpers/colors.js +57 -13
- package/src/shared/helpers/core.js +0 -19
- package/src/shared/helpers/utils.js +146 -36
- package/src/shared/logger.js +12 -0
- package/src/shared/semantic-helpers.js +143 -0
- package/src/shared/validation/config-validator.js +167 -0
|
@@ -1,39 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PurgeTSS v7.1.0 - Core Builder: Tailwind Helpers
|
|
3
|
-
* Helper functions for building Tailwind CSS
|
|
4
|
-
*
|
|
5
|
-
* COPIED from src/index.js during refactorization - NO CHANGES to logic.
|
|
6
3
|
*
|
|
7
4
|
* @since 7.1.0
|
|
8
5
|
* @author César Estrada
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
|
-
import _ from 'lodash'
|
|
12
|
-
import defaultColors from 'tailwindcss/colors.js'
|
|
13
|
-
|
|
14
|
-
// Import functions from their new modular locations
|
|
15
|
-
import { getConfigFile } from '../../shared/config-manager.js'
|
|
16
|
-
import { removeDeprecatedColors, fixPercentages } from '../../shared/helpers.js'
|
|
17
|
-
|
|
18
|
-
// Get config once for this module
|
|
19
|
-
const configFile = getConfigFile()
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Remove fit, max, min values from width, height and spacing objects
|
|
23
|
-
* @param {Object} theObject - Object with width, height, spacing properties
|
|
24
|
-
*/
|
|
25
|
-
export function removeFitMaxMin(theObject) {
|
|
26
|
-
delete theObject.width.fit
|
|
27
|
-
delete theObject.width.max
|
|
28
|
-
delete theObject.width.min
|
|
29
|
-
delete theObject.height.fit
|
|
30
|
-
delete theObject.height.max
|
|
31
|
-
delete theObject.height.min
|
|
32
|
-
delete theObject.spacing.fit
|
|
33
|
-
delete theObject.spacing.max
|
|
34
|
-
delete theObject.spacing.min
|
|
35
|
-
}
|
|
36
|
-
|
|
37
8
|
/**
|
|
38
9
|
* Combine keys from theme and extend, with fallback to base values
|
|
39
10
|
* @param {Object} values - Theme values object
|
|
@@ -44,418 +15,3 @@ export function removeFitMaxMin(theObject) {
|
|
|
44
15
|
export function combineKeys(values, base, key) {
|
|
45
16
|
return (values[key]) ? { ...values[key], ...values.extend[key] } : { ...base, ...values.extend[key] }
|
|
46
17
|
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Get base values for Tailwind building
|
|
50
|
-
* This function prepares all the base theme values needed for building
|
|
51
|
-
* @param {Object} defaultTheme - Default Tailwind theme
|
|
52
|
-
* @returns {Object} Base values object
|
|
53
|
-
*/
|
|
54
|
-
export function getBaseValues(defaultTheme) {
|
|
55
|
-
const defaultThemeWidth = defaultTheme.width({ theme: () => (defaultTheme.spacing) })
|
|
56
|
-
const defaultThemeHeight = defaultTheme.height({ theme: () => (defaultTheme.spacing) })
|
|
57
|
-
|
|
58
|
-
removeDeprecatedColors(defaultColors)
|
|
59
|
-
|
|
60
|
-
// !Prepare values
|
|
61
|
-
const tiResets = { full: '100%' }
|
|
62
|
-
const allWidthsCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeWidth }
|
|
63
|
-
const allHeightsCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeHeight }
|
|
64
|
-
const allSpacingCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeWidth, ...defaultThemeHeight }
|
|
65
|
-
|
|
66
|
-
const themeOrDefaultValues = {
|
|
67
|
-
width: configFile.theme.width ?? allWidthsCombined,
|
|
68
|
-
height: configFile.theme.height ?? allHeightsCombined,
|
|
69
|
-
spacing: configFile.theme.spacing ?? allSpacingCombined,
|
|
70
|
-
fontSize: configFile.theme.spacing ?? defaultTheme.fontSize,
|
|
71
|
-
colors: configFile.theme.colors ?? { transparent: 'transparent', ...defaultColors }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ! Remove unnecessary values
|
|
75
|
-
removeFitMaxMin(themeOrDefaultValues)
|
|
76
|
-
|
|
77
|
-
// ! Merge with extend values
|
|
78
|
-
const base = {
|
|
79
|
-
width: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing, ...themeOrDefaultValues.width, ...configFile.theme.extend.width },
|
|
80
|
-
height: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing, ...themeOrDefaultValues.height, ...configFile.theme.extend.height },
|
|
81
|
-
colors: { ...themeOrDefaultValues.colors, ...configFile.theme.extend.colors },
|
|
82
|
-
spacing: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing },
|
|
83
|
-
fontSize: { ...themeOrDefaultValues.fontSize, ...configFile.theme.extend.spacing, ...configFile.theme.extend.fontSize },
|
|
84
|
-
columns: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12 },
|
|
85
|
-
delay: { 0: '0ms', 25: '25ms', 50: '50ms', 250: '250ms', 350: '350ms', 400: '400ms', 450: '450ms', 600: '600ms', 800: '800ms', 900: '900ms', 2000: '2000ms', 3000: '3000ms', 4000: '4000ms', 5000: '5000ms' }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
fixPercentages(base.width)
|
|
89
|
-
fixPercentages(base.height)
|
|
90
|
-
fixPercentages(base.spacing)
|
|
91
|
-
|
|
92
|
-
return base
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Combine all values for Tailwind building - MASSIVE function with all Titanium properties
|
|
97
|
-
* This function builds the complete values object used in legacy Tailwind building
|
|
98
|
-
* @param {Object} base - Base values from getBaseValues()
|
|
99
|
-
* @param {Object} defaultTheme - Default Tailwind theme
|
|
100
|
-
* @returns {Object} Complete values object for building
|
|
101
|
-
*/
|
|
102
|
-
export function combineAllValues(base, defaultTheme) {
|
|
103
|
-
const allValues = {}
|
|
104
|
-
|
|
105
|
-
// ! Custom Window, View and ImageView
|
|
106
|
-
// Merge extend values into theme (same as colors, spacing, etc.)
|
|
107
|
-
_.each(['Window', 'View', 'ImageView'], comp => {
|
|
108
|
-
if (configFile.theme.extend[comp]) {
|
|
109
|
-
configFile.theme[comp] = _.merge({}, configFile.theme[comp], configFile.theme.extend[comp])
|
|
110
|
-
delete configFile.theme.extend[comp]
|
|
111
|
-
}
|
|
112
|
-
// Normalize shorthand: { apply: '...' } → { default: { apply: '...' } }
|
|
113
|
-
if (configFile.theme[comp] && configFile.theme[comp].apply && !configFile.theme[comp].default) {
|
|
114
|
-
configFile.theme[comp] = { default: configFile.theme[comp] }
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// Merge user config WITH defaults, then write back to configFile.theme
|
|
119
|
-
// so that downstream functions pick up the full merged object
|
|
120
|
-
configFile.theme.Window = _.merge({ default: { backgroundColor: '#FFFFFF' } }, configFile.theme.Window)
|
|
121
|
-
configFile.theme.ImageView = _.merge({ ios: { hires: true } }, configFile.theme.ImageView)
|
|
122
|
-
configFile.theme.View = _.merge({ default: { width: 'Ti.UI.SIZE', height: 'Ti.UI.SIZE' } }, configFile.theme.View)
|
|
123
|
-
|
|
124
|
-
allValues.Window = configFile.theme.Window
|
|
125
|
-
allValues.ImageView = configFile.theme.ImageView
|
|
126
|
-
allValues.View = configFile.theme.View
|
|
127
|
-
|
|
128
|
-
// ! Width, height and margin properties
|
|
129
|
-
// INFO: sizingProperties: For glossary generator only... Do not move from this position.
|
|
130
|
-
allValues.sizingProperties = {}
|
|
131
|
-
|
|
132
|
-
allValues.height = base.height
|
|
133
|
-
allValues.width = base.width
|
|
134
|
-
allValues.margin = combineKeys(configFile.theme, base.spacing, 'margin')
|
|
135
|
-
allValues.marginAlternate = combineKeys(configFile.theme, base.spacing, 'margin')
|
|
136
|
-
|
|
137
|
-
// ! Properties with constant values
|
|
138
|
-
// INFO: constantProperties: For glossary generator only... Do not move from this position.
|
|
139
|
-
allValues.constantProperties = {}
|
|
140
|
-
|
|
141
|
-
// allValues.audioStreamType = {};
|
|
142
|
-
// allValues.category = {};
|
|
143
|
-
allValues.accessibilityHidden = {}
|
|
144
|
-
allValues.accessoryType = {}
|
|
145
|
-
allValues.activeIconIsMask = {}
|
|
146
|
-
allValues.activityEnterTransition = {}
|
|
147
|
-
allValues.activityExitTransition = {}
|
|
148
|
-
allValues.activityIndicatorStyle = {}
|
|
149
|
-
allValues.activityReenterTransition = {}
|
|
150
|
-
allValues.activityReturnTransition = {}
|
|
151
|
-
allValues.activitySharedElementEnterTransition = {}
|
|
152
|
-
allValues.activitySharedElementExitTransition = {}
|
|
153
|
-
allValues.activitySharedElementReenterTransition = {}
|
|
154
|
-
allValues.activitySharedElementReturnTransition = {}
|
|
155
|
-
allValues.alertDialogStyle = {}
|
|
156
|
-
allValues.allowsBackForwardNavigationGestures = {}
|
|
157
|
-
allValues.allowsLinkPreview = {}
|
|
158
|
-
allValues.allowsMultipleSelectionDuringEditing = {}
|
|
159
|
-
allValues.allowsMultipleSelectionInteraction = {}
|
|
160
|
-
allValues.allowsSelection = {}
|
|
161
|
-
allValues.allowsSelectionDuringEditing = {}
|
|
162
|
-
allValues.allowUserCustomization = {}
|
|
163
|
-
allValues.anchorPoint = {}
|
|
164
|
-
allValues.autoAdjustScrollViewInsets = {}
|
|
165
|
-
allValues.autocapitalization = {}
|
|
166
|
-
allValues.autocorrect = {}
|
|
167
|
-
allValues.autofillType = {}
|
|
168
|
-
allValues.autoLink = {}
|
|
169
|
-
allValues.autoreverse = {}
|
|
170
|
-
allValues.autorotate = {}
|
|
171
|
-
allValues.backgroundBlendMode = {}
|
|
172
|
-
allValues.backgroundLinearGradient = {}
|
|
173
|
-
allValues.backgroundRadialGradient = {}
|
|
174
|
-
allValues.backgroundRepeat = {}
|
|
175
|
-
allValues.borderStyle = {}
|
|
176
|
-
allValues.bubbleParent = {}
|
|
177
|
-
allValues.buttonStyle = {}
|
|
178
|
-
allValues.cacheMode = {}
|
|
179
|
-
allValues.cachePolicy = {}
|
|
180
|
-
allValues.calendarViewShown = {}
|
|
181
|
-
allValues.canCancelEvents = {}
|
|
182
|
-
allValues.cancelable = {}
|
|
183
|
-
allValues.canceledOnTouchOutside = {}
|
|
184
|
-
allValues.canDelete = {}
|
|
185
|
-
allValues.canEdit = {}
|
|
186
|
-
allValues.canInsert = {}
|
|
187
|
-
allValues.canMove = {}
|
|
188
|
-
allValues.canScroll = {}
|
|
189
|
-
allValues.caseInsensitiveSearch = {}
|
|
190
|
-
allValues.checkable = {}
|
|
191
|
-
allValues.clearButtonMode = {}
|
|
192
|
-
allValues.clearOnEdit = {}
|
|
193
|
-
allValues.clipMode = {}
|
|
194
|
-
allValues.constraint = {}
|
|
195
|
-
allValues.contentHeightAndWidth = {}
|
|
196
|
-
allValues.curve = {}
|
|
197
|
-
allValues.datePickerStyle = {}
|
|
198
|
-
allValues.defaultItemTemplate = {}
|
|
199
|
-
allValues.dimBackgroundForSearch = {}
|
|
200
|
-
allValues.disableBounce = {}
|
|
201
|
-
allValues.disableContextMenu = {}
|
|
202
|
-
allValues.displayCaps = {}
|
|
203
|
-
allValues.displayHomeAsUp = {}
|
|
204
|
-
allValues.draggingType = {}
|
|
205
|
-
allValues.drawerIndicatorEnabled = {}
|
|
206
|
-
allValues.drawerLockMode = {}
|
|
207
|
-
allValues.dropShadow = {}
|
|
208
|
-
allValues.duration = {}
|
|
209
|
-
allValues.editable = {}
|
|
210
|
-
allValues.editing = {}
|
|
211
|
-
allValues.ellipsize = {}
|
|
212
|
-
allValues.enableCopy = {}
|
|
213
|
-
allValues.enabled = {}
|
|
214
|
-
allValues.enableJavascriptInterface = {}
|
|
215
|
-
allValues.enableReturnKey = {}
|
|
216
|
-
allValues.enableZoomControls = {}
|
|
217
|
-
allValues.exitOnClose = {}
|
|
218
|
-
allValues.extendBackground = {}
|
|
219
|
-
allValues.extendEdges = {}
|
|
220
|
-
allValues.extendSafeArea = {}
|
|
221
|
-
allValues.fastScroll = {}
|
|
222
|
-
allValues.filterAnchored = {}
|
|
223
|
-
allValues.filterAttribute = {}
|
|
224
|
-
allValues.filterCaseInsensitive = {}
|
|
225
|
-
allValues.filterTouchesWhenObscured = {}
|
|
226
|
-
allValues.flags = {}
|
|
227
|
-
allValues.flagSecure = {}
|
|
228
|
-
allValues.flip = {}
|
|
229
|
-
allValues.focusable = {}
|
|
230
|
-
allValues.fontStyle = {}
|
|
231
|
-
allValues.footerDividersEnabled = {}
|
|
232
|
-
allValues.format24 = {}
|
|
233
|
-
allValues.fullscreen = {}
|
|
234
|
-
allValues.gravity = {}
|
|
235
|
-
allValues.gridColumnsRowsStartEnd = {}
|
|
236
|
-
allValues.gridFlow = {}
|
|
237
|
-
allValues.gridSystem = {}
|
|
238
|
-
allValues.hasCheck = {}
|
|
239
|
-
allValues.hasChild = {}
|
|
240
|
-
allValues.hasDetail = {}
|
|
241
|
-
allValues.headerDividersEnabled = {}
|
|
242
|
-
allValues.hiddenBehavior = {}
|
|
243
|
-
allValues.hideLoadIndicator = {}
|
|
244
|
-
allValues.hidesBackButton = {}
|
|
245
|
-
allValues.hidesBarsOnSwipe = {}
|
|
246
|
-
allValues.hidesBarsOnTap = {}
|
|
247
|
-
allValues.hidesBarsWhenKeyboardAppears = {}
|
|
248
|
-
allValues.hideSearchOnSelection = {}
|
|
249
|
-
allValues.hideShadow = {}
|
|
250
|
-
allValues.hidesSearchBarWhenScrolling = {}
|
|
251
|
-
allValues.hintType = {}
|
|
252
|
-
allValues.hires = {}
|
|
253
|
-
allValues.homeButtonEnabled = {}
|
|
254
|
-
allValues.homeIndicatorAutoHidden = {}
|
|
255
|
-
allValues.horizontalWrap = {}
|
|
256
|
-
allValues.html = {}
|
|
257
|
-
allValues.icon = {}
|
|
258
|
-
allValues.iconified = {}
|
|
259
|
-
allValues.iconifiedByDefault = {}
|
|
260
|
-
allValues.iconIsMask = {}
|
|
261
|
-
allValues.ignoreSslError = {}
|
|
262
|
-
allValues.imageTouchFeedback = {}
|
|
263
|
-
allValues.includeFontPadding = {}
|
|
264
|
-
allValues.includeOpaqueBars = {}
|
|
265
|
-
allValues.keepScreenOn = {}
|
|
266
|
-
allValues.keepSectionsInSearch = {}
|
|
267
|
-
allValues.keyboardAppearance = {}
|
|
268
|
-
allValues.keyboardDismissMode = {}
|
|
269
|
-
allValues.keyboardDisplayRequiresUserAction = {}
|
|
270
|
-
allValues.keyboardType = {}
|
|
271
|
-
allValues.largeTitleDisplayMode = {}
|
|
272
|
-
allValues.largeTitleEnabled = {}
|
|
273
|
-
allValues.layout = {}
|
|
274
|
-
allValues.lazyLoadingEnabled = {}
|
|
275
|
-
allValues.leftButtonMode = {}
|
|
276
|
-
allValues.leftDrawerLockMode = {}
|
|
277
|
-
allValues.lightTouchEnabled = {}
|
|
278
|
-
allValues.listViewStyle = {}
|
|
279
|
-
allValues.loginKeyboardType = {}
|
|
280
|
-
allValues.loginReturnKeyType = {}
|
|
281
|
-
allValues.mixedContentMode = {}
|
|
282
|
-
allValues.modal = {}
|
|
283
|
-
allValues.moveable = {}
|
|
284
|
-
allValues.moving = {}
|
|
285
|
-
allValues.nativeSpinner = {}
|
|
286
|
-
allValues.navBarHidden = {}
|
|
287
|
-
allValues.navigationMode = {}
|
|
288
|
-
allValues.orientationModes = {}
|
|
289
|
-
allValues.overlayEnabled = {}
|
|
290
|
-
allValues.overrideCurrentAnimation = {}
|
|
291
|
-
allValues.overScrollMode = {}
|
|
292
|
-
allValues.showPagingControl = {}
|
|
293
|
-
allValues.pagingControlOnTop = {}
|
|
294
|
-
allValues.passwordKeyboardType = {}
|
|
295
|
-
allValues.passwordMask = {}
|
|
296
|
-
allValues.pickerType = {}
|
|
297
|
-
allValues.placement = {}
|
|
298
|
-
allValues.pluginState = {}
|
|
299
|
-
allValues.preventCornerOverlap = {}
|
|
300
|
-
allValues.preventDefaultImage = {}
|
|
301
|
-
allValues.previewActionStyle = {}
|
|
302
|
-
allValues.progressBarStyle = {}
|
|
303
|
-
allValues.progressIndicatorType = {}
|
|
304
|
-
allValues.pruneSectionsOnEdit = {}
|
|
305
|
-
allValues.requestedOrientation = {}
|
|
306
|
-
allValues.resultsSeparatorStyle = {}
|
|
307
|
-
allValues.returnKeyType = {}
|
|
308
|
-
allValues.reverse = {}
|
|
309
|
-
allValues.rightButtonMode = {}
|
|
310
|
-
allValues.rightDrawerLockMode = {}
|
|
311
|
-
allValues.scalesPageToFit = {}
|
|
312
|
-
allValues.scrollable = {}
|
|
313
|
-
allValues.scrollIndicators = {}
|
|
314
|
-
allValues.scrollIndicatorStyle = {}
|
|
315
|
-
allValues.scrollingEnabled = {}
|
|
316
|
-
allValues.scrollsToTop = {}
|
|
317
|
-
allValues.scrollType = {}
|
|
318
|
-
allValues.searchAsChild = {}
|
|
319
|
-
allValues.searchBarStyle = {}
|
|
320
|
-
allValues.searchHidden = {}
|
|
321
|
-
allValues.selectionGranularity = {}
|
|
322
|
-
allValues.selectionOpens = {}
|
|
323
|
-
allValues.selectionStyle = {}
|
|
324
|
-
allValues.separatorStyle = {}
|
|
325
|
-
allValues.shiftMode = {}
|
|
326
|
-
allValues.showAsAction = {}
|
|
327
|
-
allValues.showBookmark = {}
|
|
328
|
-
allValues.showCancel = {}
|
|
329
|
-
allValues.showHorizontalScrollIndicator = {}
|
|
330
|
-
allValues.showSearchBarInNavBar = {}
|
|
331
|
-
allValues.showSelectionCheck = {}
|
|
332
|
-
allValues.showUndoRedoActions = {}
|
|
333
|
-
allValues.showVerticalScrollIndicator = {}
|
|
334
|
-
allValues.smoothScrollOnTabClick = {}
|
|
335
|
-
allValues.statusBarStyle = {}
|
|
336
|
-
allValues.submitEnabled = {}
|
|
337
|
-
allValues.suppressReturn = {}
|
|
338
|
-
allValues.sustainedPerformanceMode = {}
|
|
339
|
-
allValues.swipeToClose = {}
|
|
340
|
-
allValues.switchStyle = {}
|
|
341
|
-
allValues.systemButton = {}
|
|
342
|
-
allValues.tabBarHidden = {}
|
|
343
|
-
allValues.tabbedBarStyle = {}
|
|
344
|
-
allValues.tabGroupStyle = {}
|
|
345
|
-
allValues.tableViewStyle = {}
|
|
346
|
-
allValues.tabsTranslucent = {}
|
|
347
|
-
allValues.textAlign = {}
|
|
348
|
-
allValues.theme = {}
|
|
349
|
-
allValues.titleAttributesShadow = {}
|
|
350
|
-
allValues.toolbarEnabled = {}
|
|
351
|
-
allValues.touchEnabled = {}
|
|
352
|
-
allValues.touchFeedback = {}
|
|
353
|
-
allValues.translucent = {}
|
|
354
|
-
allValues.useCompatPadding = {}
|
|
355
|
-
allValues.useSpinner = {}
|
|
356
|
-
allValues.verticalAlign = {}
|
|
357
|
-
allValues.verticalBounce = {}
|
|
358
|
-
allValues.viewShadow = {}
|
|
359
|
-
allValues.visible = {}
|
|
360
|
-
allValues.willHandleTouches = {}
|
|
361
|
-
allValues.willScrollOnStatusTap = {}
|
|
362
|
-
allValues.windowPixelFormat = {}
|
|
363
|
-
allValues.windowSoftInputMode = {}
|
|
364
|
-
allValues.wobble = {}
|
|
365
|
-
|
|
366
|
-
// ! Configurable properties
|
|
367
|
-
// INFO: configurableProperties: For glossary generator only... Do not move from this position.
|
|
368
|
-
allValues.configurableProperties = {}
|
|
369
|
-
|
|
370
|
-
allValues.activeTab = combineKeys(configFile.theme, base.spacing, 'activeTab')
|
|
371
|
-
allValues.backgroundLeftCap = combineKeys(configFile.theme, base.spacing, 'backgroundLeftCap')
|
|
372
|
-
allValues.backgroundPaddingBottom = combineKeys(configFile.theme, base.spacing, 'backgroundPaddingBottom')
|
|
373
|
-
allValues.backgroundPaddingLeft = combineKeys(configFile.theme, base.spacing, 'backgroundPaddingLeft')
|
|
374
|
-
allValues.backgroundPaddingRight = combineKeys(configFile.theme, base.spacing, 'backgroundPaddingRight')
|
|
375
|
-
allValues.backgroundPaddingTop = combineKeys(configFile.theme, base.spacing, 'backgroundPaddingTop')
|
|
376
|
-
allValues.backgroundTopCap = combineKeys(configFile.theme, base.spacing, 'backgroundTopCap')
|
|
377
|
-
allValues.borderRadius = combineKeys(configFile.theme, base.spacing, 'borderRadius')
|
|
378
|
-
allValues.borderWidth = combineKeys(configFile.theme, base.spacing, 'borderWidth')
|
|
379
|
-
allValues.bottomNavigation = combineKeys(configFile.theme, base.spacing, 'bottomNavigation')
|
|
380
|
-
allValues.cacheSize = combineKeys(configFile.theme, base.spacing, 'cacheSize')
|
|
381
|
-
allValues.columnCount = combineKeys(configFile.theme, { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12 }, 'columnCount')
|
|
382
|
-
allValues.contentHeight = combineKeys(configFile.theme, base.height, 'contentHeight')
|
|
383
|
-
allValues.contentWidth = combineKeys(configFile.theme, base.width, 'contentWidth')
|
|
384
|
-
allValues.countDownDuration = combineKeys(configFile.theme, base.spacing, 'countDownDuration')
|
|
385
|
-
allValues.elevation = combineKeys(configFile.theme, base.spacing, 'elevation')
|
|
386
|
-
allValues.fontFamily = combineKeys(configFile.theme, {}, 'fontFamily')
|
|
387
|
-
allValues.fontSize = combineKeys(configFile.theme, base.fontSize, 'fontSize')
|
|
388
|
-
allValues.fontWeight = combineKeys(configFile.theme, defaultTheme.fontWeight, 'fontWeight')
|
|
389
|
-
allValues.gap = combineKeys(configFile.theme, base.spacing, 'gap')
|
|
390
|
-
allValues.indentionLevel = combineKeys(configFile.theme, base.spacing, 'indentionLevel')
|
|
391
|
-
allValues.keyboardToolbarHeight = combineKeys(configFile.theme, base.spacing, 'keyboardToolbarHeight')
|
|
392
|
-
allValues.leftButtonPadding = combineKeys(configFile.theme, base.spacing, 'leftButtonPadding')
|
|
393
|
-
allValues.leftWidth = combineKeys(configFile.theme, base.width, 'leftWidth')
|
|
394
|
-
allValues.lines = combineKeys(configFile.theme, { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10 }, 'lines')
|
|
395
|
-
allValues.maxElevation = combineKeys(configFile.theme, base.spacing, 'maxElevation')
|
|
396
|
-
allValues.maxLines = combineKeys(configFile.theme, { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10 }, 'maxLines')
|
|
397
|
-
allValues.maxRowHeight = combineKeys(configFile.theme, base.height, 'maxRowHeight')
|
|
398
|
-
allValues.maxZoomScale = combineKeys(configFile.theme, { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 }, 'maxZoomScale')
|
|
399
|
-
allValues.minimumFontSize = combineKeys(configFile.theme, base.fontSize, 'minimumFontSize')
|
|
400
|
-
allValues.minRowHeight = combineKeys(configFile.theme, base.height, 'minRowHeight')
|
|
401
|
-
allValues.minZoomScale = combineKeys(configFile.theme, { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 }, 'minZoomScale')
|
|
402
|
-
allValues.offsets = combineKeys(configFile.theme, base.spacing, 'offsets')
|
|
403
|
-
allValues.opacity = combineKeys(configFile.theme, defaultTheme.opacity, 'opacity')
|
|
404
|
-
allValues.padding = combineKeys(configFile.theme, base.spacing, 'padding')
|
|
405
|
-
allValues.pagingControlAlpha = combineKeys(configFile.theme, defaultTheme.opacity, 'pagingControlAlpha')
|
|
406
|
-
allValues.pagingControlHeight = combineKeys(configFile.theme, base.height, 'pagingControlHeight')
|
|
407
|
-
allValues.pagingControlTimeout = combineKeys(configFile.theme, base.delay, 'pagingControlTimeout')
|
|
408
|
-
allValues.repeat = combineKeys(configFile.theme, { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, infinite: -1 }, 'repeat')
|
|
409
|
-
allValues.repeatCount = combineKeys(configFile.theme, { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, infinite: -1 }, 'repeatCount')
|
|
410
|
-
allValues.rightButtonPadding = combineKeys(configFile.theme, base.spacing, 'rightButtonPadding')
|
|
411
|
-
allValues.rightWidth = combineKeys(configFile.theme, base.width, 'rightWidth')
|
|
412
|
-
allValues.rotate = combineKeys(configFile.theme, defaultTheme.rotate, 'rotate')
|
|
413
|
-
|
|
414
|
-
// ! Custom Color properties
|
|
415
|
-
// INFO: colorProperties: For glossary generator only... Do not move from this position.
|
|
416
|
-
allValues.colorProperties = {}
|
|
417
|
-
|
|
418
|
-
allValues.activeTintColor = combineKeys(configFile.theme, base.colors, 'activeTintColor')
|
|
419
|
-
allValues.activeTitleColor = combineKeys(configFile.theme, base.colors, 'activeTitleColor')
|
|
420
|
-
allValues.backgroundColor = combineKeys(configFile.theme, base.colors, 'backgroundColor')
|
|
421
|
-
allValues.backgroundDisabledColor = combineKeys(configFile.theme, base.colors, 'backgroundDisabledColor')
|
|
422
|
-
allValues.backgroundFocusedColor = combineKeys(configFile.theme, base.colors, 'backgroundFocusedColor')
|
|
423
|
-
allValues.backgroundGradient = combineKeys(configFile.theme, base.colors, 'backgroundGradient')
|
|
424
|
-
allValues.backgroundSelectedColor = combineKeys(configFile.theme, base.colors, 'backgroundSelectedColor')
|
|
425
|
-
allValues.backgroundSelectedGradient = combineKeys(configFile.theme, base.colors, 'backgroundSelectedGradient')
|
|
426
|
-
allValues.badgeColor = combineKeys(configFile.theme, base.colors, 'badgeColor')
|
|
427
|
-
allValues.barColor = combineKeys(configFile.theme, base.colors, 'barColor')
|
|
428
|
-
allValues.borderColor = combineKeys(configFile.theme, base.colors, 'borderColor')
|
|
429
|
-
allValues.currentPageIndicatorColor = combineKeys(configFile.theme, base.colors, 'currentPageIndicatorColor')
|
|
430
|
-
allValues.dateTimeColor = combineKeys(configFile.theme, base.colors, 'dateTimeColor')
|
|
431
|
-
allValues.disabledColor = combineKeys(configFile.theme, base.colors, 'disabledColor')
|
|
432
|
-
allValues.highlightedColor = combineKeys(configFile.theme, base.colors, 'highlightedColor')
|
|
433
|
-
allValues.hintTextColor = combineKeys(configFile.theme, base.colors, 'hintTextColor')
|
|
434
|
-
allValues.imageTouchFeedbackColor = combineKeys(configFile.theme, base.colors, 'imageTouchFeedbackColor')
|
|
435
|
-
allValues.pageIndicatorColor = combineKeys(configFile.theme, base.colors, 'pageIndicatorColor')
|
|
436
|
-
allValues.pagingControlColor = combineKeys(configFile.theme, base.colors, 'pagingControlColor')
|
|
437
|
-
allValues.pullBackgroundColor = combineKeys(configFile.theme, base.colors, 'pullBackgroundColor')
|
|
438
|
-
allValues.resultsBackgroundColor = combineKeys(configFile.theme, base.colors, 'resultsBackgroundColor')
|
|
439
|
-
allValues.resultsSeparatorColor = combineKeys(configFile.theme, base.colors, 'resultsSeparatorColor')
|
|
440
|
-
allValues.selectedBackgroundColor = combineKeys(configFile.theme, base.colors, 'selectedBackgroundColor')
|
|
441
|
-
allValues.selectedBackgroundGradient = combineKeys(configFile.theme, base.colors, 'selectedBackgroundGradient')
|
|
442
|
-
allValues.selectedColor = combineKeys(configFile.theme, base.colors, 'selectedColor')
|
|
443
|
-
allValues.separatorColor = combineKeys(configFile.theme, base.colors, 'separatorColor')
|
|
444
|
-
allValues.shadowColor = combineKeys(configFile.theme, base.colors, 'shadowColor')
|
|
445
|
-
allValues.tabsBackgroundColor = combineKeys(configFile.theme, base.colors, 'tabsBackgroundColor')
|
|
446
|
-
allValues.tabsBackgroundDisabledColor = combineKeys(configFile.theme, base.colors, 'tabsBackgroundDisabledColor')
|
|
447
|
-
allValues.tabsBackgroundFocusedColor = combineKeys(configFile.theme, base.colors, 'tabsBackgroundFocusedColor')
|
|
448
|
-
allValues.tabsBackgroundSelectedColor = combineKeys(configFile.theme, base.colors, 'tabsBackgroundSelectedColor')
|
|
449
|
-
allValues.textColor = combineKeys(configFile.theme, base.colors, 'textColor')
|
|
450
|
-
allValues.tintColor = combineKeys(configFile.theme, base.colors, 'tintColor')
|
|
451
|
-
allValues.titleColor = combineKeys(configFile.theme, base.colors, 'titleColor')
|
|
452
|
-
allValues.titleDisabledColor = combineKeys(configFile.theme, base.colors, 'titleDisabledColor')
|
|
453
|
-
allValues.titleFocusedColor = combineKeys(configFile.theme, base.colors, 'titleFocusedColor')
|
|
454
|
-
allValues.titleHighlightedColor = combineKeys(configFile.theme, base.colors, 'titleHighlightedColor')
|
|
455
|
-
allValues.titleSelectedColor = combineKeys(configFile.theme, base.colors, 'titleSelectedColor')
|
|
456
|
-
allValues.touchFeedbackColor = combineKeys(configFile.theme, base.colors, 'touchFeedbackColor')
|
|
457
|
-
allValues.viewShadowColor = combineKeys(configFile.theme, base.colors, 'viewShadowColor')
|
|
458
|
-
allValues.navTintColor = combineKeys(configFile.theme, base.colors, 'navTintColor')
|
|
459
|
-
|
|
460
|
-
return allValues
|
|
461
|
-
}
|
|
@@ -22,7 +22,9 @@ import { logger } from '../branding/branding-logger.js'
|
|
|
22
22
|
const IMAGES_BLOCK = ` images: {
|
|
23
23
|
quality: 85, // JPEG/WebP/AVIF quality (0-100)
|
|
24
24
|
format: null, // null = keep original; 'webp' | 'jpeg' | 'png' to convert every image
|
|
25
|
-
|
|
25
|
+
autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
|
|
26
|
+
confirmOverwrites: true, // prompt before overwriting files (set false to skip)
|
|
27
|
+
files: [] // per-file overrides: [{ filename: 'images/<sub>/<name>.<ext>', width, height? }]
|
|
26
28
|
},
|
|
27
29
|
`
|
|
28
30
|
|
|
@@ -47,11 +49,11 @@ export function ensureImagesSection() {
|
|
|
47
49
|
fs.writeFileSync(projectsConfigJS, patched, 'utf8')
|
|
48
50
|
console.log()
|
|
49
51
|
logger.success(`Added ${chalk.cyan('images:')} section to ${chalk.cyan('./purgetss/config.cjs')} with default values.`)
|
|
50
|
-
console.log(
|
|
51
|
-
console.log(
|
|
52
|
+
console.log(' Edit that block to customize defaults (quality, format).')
|
|
53
|
+
console.log(' CLI flags always win over config values.')
|
|
52
54
|
console.log()
|
|
53
55
|
} catch (err) {
|
|
54
56
|
logger.warning(`Could not auto-add images: section to config.cjs (${err.message}).`)
|
|
55
|
-
logger.warning(
|
|
57
|
+
logger.warning('The command will still run using built-in defaults.')
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -73,6 +73,49 @@ export const IPHONE_SCALES = Object.freeze([
|
|
|
73
73
|
{ suffix: '@3x', factor: 3 / 4 }
|
|
74
74
|
])
|
|
75
75
|
|
|
76
|
+
// Resolve target dimensions for a single scale.
|
|
77
|
+
// `factor * 4` recovers the integer multiplier (1, 1.5, 2, 3, 4 for Android;
|
|
78
|
+
// 1, 2, 3 for iPhone) only because every entry in *_SCALES is normalized to
|
|
79
|
+
// n/4 with the largest scale (xxxhdpi/@4x) at 4/4. If a future density is
|
|
80
|
+
// added beyond xxxhdpi, this conversion factor needs to be revisited.
|
|
81
|
+
//
|
|
82
|
+
// `baseHeight` pins the height explicitly (e.g. SVG pipeline resolved both
|
|
83
|
+
// w-* and h-* to numbers); when omitted, height follows the source aspect.
|
|
84
|
+
function computeScaleTarget(srcMeta, factor, baseWidth, baseHeight) {
|
|
85
|
+
// No pin in either direction → fall back to the source as the 4× master.
|
|
86
|
+
if (baseWidth == null && baseHeight == null) {
|
|
87
|
+
return {
|
|
88
|
+
targetWidth: Math.max(1, Math.round(srcMeta.width * factor)),
|
|
89
|
+
targetHeight: Math.max(1, Math.round(srcMeta.height * factor))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const multiplier = factor * 4
|
|
93
|
+
const widthOverHeight = srcMeta.width > 0 && srcMeta.height > 0
|
|
94
|
+
? srcMeta.width / srcMeta.height
|
|
95
|
+
: 1
|
|
96
|
+
const heightOverWidth = srcMeta.width > 0 && srcMeta.height > 0
|
|
97
|
+
? srcMeta.height / srcMeta.width
|
|
98
|
+
: 1
|
|
99
|
+
|
|
100
|
+
if (baseWidth != null) {
|
|
101
|
+
const targetWidth = Math.max(1, Math.round(baseWidth * multiplier))
|
|
102
|
+
const targetHeight = baseHeight != null
|
|
103
|
+
? Math.max(1, Math.round(baseHeight * multiplier))
|
|
104
|
+
: Math.max(1, Math.round(baseWidth * multiplier * heightOverWidth))
|
|
105
|
+
return { targetWidth, targetHeight }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Height pinned, width derived from inverse aspect.
|
|
109
|
+
const targetHeight = Math.max(1, Math.round(baseHeight * multiplier))
|
|
110
|
+
const targetWidth = Math.max(1, Math.round(baseHeight * multiplier * widthOverHeight))
|
|
111
|
+
return { targetWidth, targetHeight }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Hard ceiling for any individual PNG output. Mirrors the constant exported
|
|
115
|
+
// from the SVG pipeline; centralizing it here would create a cycle, so we keep
|
|
116
|
+
// a local copy and rely on derive-dimensions to enforce the dp-side budget.
|
|
117
|
+
const MAX_OUTPUT_PIXELS = 4096
|
|
118
|
+
|
|
76
119
|
/**
|
|
77
120
|
* Scale a source image into all Android density variants.
|
|
78
121
|
*
|
|
@@ -85,19 +128,19 @@ export const IPHONE_SCALES = Object.freeze([
|
|
|
85
128
|
* @returns {Promise<string[]>} Paths written
|
|
86
129
|
*/
|
|
87
130
|
export async function genAndroidScales(sourceFile, relPath, androidBaseDir, opts = {}) {
|
|
88
|
-
const { format = null, quality = 85 } = opts
|
|
131
|
+
const { format = null, quality = 85, baseWidth = null, baseHeight = null, opacity = null, padding = null } = opts
|
|
89
132
|
const src = await readSource(sourceFile)
|
|
90
133
|
const written = []
|
|
91
134
|
|
|
92
135
|
for (const { name, factor } of ANDROID_SCALES) {
|
|
93
|
-
const targetWidth =
|
|
94
|
-
|
|
136
|
+
const { targetWidth, targetHeight } = computeScaleTarget(src.meta, factor, baseWidth, baseHeight)
|
|
137
|
+
assertWithinCap(targetWidth, targetHeight, sourceFile, name)
|
|
95
138
|
|
|
96
139
|
const outDir = path.join(androidBaseDir, name, path.dirname(relPath))
|
|
97
140
|
fs.mkdirSync(outDir, { recursive: true })
|
|
98
141
|
|
|
99
142
|
const outPath = path.join(outDir, renameWithFormat(path.basename(relPath), format, src.isSvg))
|
|
100
|
-
await writeScaled(src, outPath, targetWidth, targetHeight, format, quality)
|
|
143
|
+
await writeScaled(src, outPath, targetWidth, targetHeight, format, quality, opacity, padding)
|
|
101
144
|
written.push(outPath)
|
|
102
145
|
}
|
|
103
146
|
return written
|
|
@@ -113,7 +156,7 @@ export async function genAndroidScales(sourceFile, relPath, androidBaseDir, opts
|
|
|
113
156
|
* @returns {Promise<string[]>} Paths written
|
|
114
157
|
*/
|
|
115
158
|
export async function genIphoneScales(sourceFile, relPath, iphoneBaseDir, opts = {}) {
|
|
116
|
-
const { format = null, quality = 85 } = opts
|
|
159
|
+
const { format = null, quality = 85, baseWidth = null, baseHeight = null, opacity = null, padding = null } = opts
|
|
117
160
|
const src = await readSource(sourceFile)
|
|
118
161
|
const written = []
|
|
119
162
|
|
|
@@ -122,8 +165,8 @@ export async function genIphoneScales(sourceFile, relPath, iphoneBaseDir, opts =
|
|
|
122
165
|
fs.mkdirSync(outDir, { recursive: true })
|
|
123
166
|
|
|
124
167
|
for (const { suffix, factor } of IPHONE_SCALES) {
|
|
125
|
-
const targetWidth =
|
|
126
|
-
|
|
168
|
+
const { targetWidth, targetHeight } = computeScaleTarget(src.meta, factor, baseWidth, baseHeight)
|
|
169
|
+
assertWithinCap(targetWidth, targetHeight, sourceFile, suffix || '@1x')
|
|
127
170
|
|
|
128
171
|
// SVG sources can't be written as SVG by Sharp — fall back to PNG if the
|
|
129
172
|
// user didn't specify an explicit output format.
|
|
@@ -131,12 +174,21 @@ export async function genIphoneScales(sourceFile, relPath, iphoneBaseDir, opts =
|
|
|
131
174
|
const outName = `${parsed.name}${suffix}${ext}`
|
|
132
175
|
const outPath = path.join(outDir, outName)
|
|
133
176
|
|
|
134
|
-
await writeScaled(src, outPath, targetWidth, targetHeight, format, quality)
|
|
177
|
+
await writeScaled(src, outPath, targetWidth, targetHeight, format, quality, opacity, padding)
|
|
135
178
|
written.push(outPath)
|
|
136
179
|
}
|
|
137
180
|
return written
|
|
138
181
|
}
|
|
139
182
|
|
|
183
|
+
function assertWithinCap(width, height, sourceFile, label) {
|
|
184
|
+
if (width > MAX_OUTPUT_PIXELS || height > MAX_OUTPUT_PIXELS) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`${path.basename(sourceFile)} at ${label} would render ${width}×${height}px, ` +
|
|
187
|
+
`which exceeds the ${MAX_OUTPUT_PIXELS}px cap. Reduce the resolved width or override it manually in config.cjs > images.files.`
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
140
192
|
function renameWithFormat(filename, format, isSvg = false) {
|
|
141
193
|
if (format) {
|
|
142
194
|
const parsed = path.parse(filename)
|
|
@@ -150,15 +202,46 @@ function renameWithFormat(filename, format, isSvg = false) {
|
|
|
150
202
|
return filename
|
|
151
203
|
}
|
|
152
204
|
|
|
153
|
-
async function writeScaled(src, outPath, width, height, format, quality) {
|
|
154
|
-
|
|
205
|
+
async function writeScaled(src, outPath, width, height, format, quality, opacity, paddingPct) {
|
|
206
|
+
// Padding shrinks the rendered image inside the same canvas so each density
|
|
207
|
+
// gets symmetric transparent borders. Computed from the canvas dimensions so
|
|
208
|
+
// the visual ratio (e.g. 15%) is identical across every density variant.
|
|
209
|
+
const padX = paddingPct ? Math.floor(width * paddingPct / 100) : 0
|
|
210
|
+
const padY = paddingPct ? Math.floor(height * paddingPct / 100) : 0
|
|
211
|
+
const innerW = Math.max(1, width - 2 * padX)
|
|
212
|
+
const innerH = Math.max(1, height - 2 * padY)
|
|
213
|
+
const targetMax = Math.max(innerW, innerH)
|
|
214
|
+
|
|
155
215
|
let pipeline = buildScalePipeline(src, targetMax).resize({
|
|
156
|
-
width,
|
|
157
|
-
height,
|
|
216
|
+
width: innerW,
|
|
217
|
+
height: innerH,
|
|
158
218
|
fit: 'contain',
|
|
159
219
|
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
160
220
|
})
|
|
161
221
|
|
|
222
|
+
if (padX > 0 || padY > 0) {
|
|
223
|
+
pipeline = pipeline.extend({
|
|
224
|
+
top: padY,
|
|
225
|
+
bottom: padY,
|
|
226
|
+
left: padX,
|
|
227
|
+
right: padX,
|
|
228
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Apply opacity by multiplying the dest alpha against a uniform-alpha tile.
|
|
233
|
+
// `dest-in` keeps RGB and multiplies dest alpha by source alpha (opacity/100).
|
|
234
|
+
if (opacity != null && opacity < 100) {
|
|
235
|
+
pipeline = pipeline
|
|
236
|
+
.ensureAlpha()
|
|
237
|
+
.composite([{
|
|
238
|
+
input: Buffer.from([255, 255, 255, Math.round(255 * opacity / 100)]),
|
|
239
|
+
raw: { width: 1, height: 1, channels: 4 },
|
|
240
|
+
tile: true,
|
|
241
|
+
blend: 'dest-in'
|
|
242
|
+
}])
|
|
243
|
+
}
|
|
244
|
+
|
|
162
245
|
// For SVG sources without an explicit format, coerce output to PNG
|
|
163
246
|
// (Sharp cannot write SVG).
|
|
164
247
|
const fallbackExt = src.isSvg ? 'png' : path.extname(src.path).slice(1).toLowerCase()
|
|
@@ -170,7 +253,7 @@ async function writeScaled(src, outPath, width, height, format, quality) {
|
|
|
170
253
|
|
|
171
254
|
function applyFormat(pipeline, format, quality) {
|
|
172
255
|
switch (format) {
|
|
173
|
-
case 'png': return pipeline.png({
|
|
256
|
+
case 'png': return pipeline.png({ compressionLevel: 9 })
|
|
174
257
|
case 'webp': return pipeline.webp({ quality })
|
|
175
258
|
case 'avif': return pipeline.avif({ quality })
|
|
176
259
|
case 'tiff': return pipeline.tiff({ quality, compression: 'lzw' })
|