* Cost function for calculating the cost of converting from one format to another using a specific handler.
(
from: { format: FileFormat; index: number; },
to: { format: FileFormat; index: number; },
strictCategories: boolean,
handler: string,
handlerIndex: number
)
| 232 | * Cost function for calculating the cost of converting from one format to another using a specific handler. |
| 233 | */ |
| 234 | private costFunction( |
| 235 | from: { format: FileFormat; index: number; }, |
| 236 | to: { format: FileFormat; index: number; }, |
| 237 | strictCategories: boolean, |
| 238 | handler: string, |
| 239 | handlerIndex: number |
| 240 | ) { |
| 241 | let cost = DEPTH_COST; // Base cost for each conversion step |
| 242 | |
| 243 | const handlerPairs = new Map<string, string>(this.categoryChangeCosts.filter(c => c.handler) |
| 244 | .map(c => [`${c.from}->${c.to}`, c.handler] as [string, string])); |
| 245 | // Calculate category change cost |
| 246 | const fromCategory = from.format.category || from.format.mime.split("/")[0]; |
| 247 | const toCategory = to.format.category || to.format.mime.split("/")[0]; |
| 248 | if (fromCategory && toCategory) { |
| 249 | const fromCategories = Array.isArray(fromCategory) ? fromCategory : [fromCategory]; |
| 250 | const toCategories = Array.isArray(toCategory) ? toCategory : [toCategory]; |
| 251 | if (strictCategories) { |
| 252 | cost += this.categoryChangeCosts.reduce((totalCost, c) => { |
| 253 | // If the category change defined in CATEGORY_CHANGE_COSTS matches the categories of the formats, add the specified cost. Otherwise, if the categories are the same, add no cost. If the categories differ but no specific cost is defined for that change, add a default cost. |
| 254 | if (fromCategories.includes(c.from) |
| 255 | && toCategories.includes(c.to) |
| 256 | && (!c.handler || c.handler === handler.toLowerCase()) |
| 257 | ) |
| 258 | return totalCost + c.cost; |
| 259 | return totalCost + DEFAULT_CATEGORY_CHANGE_COST; |
| 260 | }, 0); |
| 261 | } |
| 262 | else if (!fromCategories.some(c => toCategories.includes(c))) { |
| 263 | let costs = this.categoryChangeCosts.filter(c => |
| 264 | fromCategories.includes(c.from) |
| 265 | && toCategories.includes(c.to) |
| 266 | && ( |
| 267 | (!c.handler && handlerPairs.get(`${c.from}->${c.to}`) !== handler.toLowerCase()) |
| 268 | || c.handler === handler.toLowerCase() |
| 269 | ) |
| 270 | ); |
| 271 | if (costs.length === 0) cost += DEFAULT_CATEGORY_CHANGE_COST; // If no specific cost is defined for this category change, use the default cost |
| 272 | else cost += Math.min(...costs.map(c => c.cost)); // If multiple category changes are involved, use the lowest cost defined for those changes. This allows for more nuanced cost calculations when formats belong to multiple categories. |
| 273 | } |
| 274 | } |
| 275 | else if (fromCategory || toCategory) { |
| 276 | // If one format has a category and the other doesn't, consider it a category change |
| 277 | // Should theoretically never be encountered, unless the MIME type is misspecified |
| 278 | cost += DEFAULT_CATEGORY_CHANGE_COST; |
| 279 | } |
| 280 | |
| 281 | // Add cost based on handler priority |
| 282 | cost += HANDLER_PRIORITY_COST * handlerIndex; |
| 283 | |
| 284 | // Add cost based on format priority |
| 285 | const handlerObj = this.handlers.find(h => h.name === handler) |
| 286 | cost += FORMAT_PRIORITY_COST * (handlerObj?.supportedFormats?.findIndex(f => f.mime === to.format.mime) ?? 0); |
| 287 | |
| 288 | // Add cost multiplier for lossy conversions |
| 289 | if (!to.format.lossless) cost *= LOSSY_COST_MULTIPLIER; |
| 290 | |
| 291 | return cost; |