11import CoreGraphics
2+ import CryptoKit
23import Foundation
34import ImageIO
45import UniformTypeIdentifiers
@@ -40,6 +41,7 @@ struct AssetCatalogEntry: Encodable {
4041 let type : AssetType ?
4142 let idiom : String ?
4243 let colorspace : String ?
44+ let contentHash : String ?
4345}
4446
4547enum Error : Swift . Error {
@@ -112,7 +114,7 @@ enum AssetUtil {
112114
113115 let ( structuredThemeStore, assetKeys) = initializeCatalog ( from: file)
114116
115- var images : [ String : ( cgImage: CGImage , format: String ) ] = [ : ]
117+ var cgImages : [ String : ( cgImage: CGImage , format: String ) ] = [ : ]
116118
117119 // First pass: Build map of multisize sets and cache renditions for performance
118120 var multisizeSets : [ MultisizeSetInfo ] = [ ]
@@ -216,17 +218,22 @@ enum AssetUtil {
216218 var width : Int ?
217219 var height : Int ?
218220 var unslicedImage : CGImage ?
221+ var contentHash : String ? = nil
219222
220223 if isMultisizeImageSet {
221224 continue
222225 } else {
223226 // Get image dimensions from regular rendition
224227 ( width, height, unslicedImage) = resolveImageDimensions ( rendition, isVector)
225228
226- // Skip SVGs, but save images even if they don't have an extension (default to png)
227- if fileExtension != " svg " , let unslicedImage = unslicedImage {
229+ // Compute content hash for PDFs/SVGs without saving to disk
230+ if fileExtension == " pdf " || fileExtension == " svg " {
231+ contentHash = data. sha256Hash ( )
232+ }
233+ // Save images that can be converted to CGImage (excluding PDFs/SVGs)
234+ else if let unslicedImage = unslicedImage {
228235 let format = fileExtension. isEmpty ? " png " : fileExtension
229- images [ imageId] = ( cgImage: unslicedImage, format: format)
236+ cgImages [ imageId] = ( cgImage: unslicedImage, format: format)
230237 }
231238 }
232239
@@ -251,7 +258,8 @@ enum AssetUtil {
251258 filename: renditionTypeName,
252259 type: assetType,
253260 idiom: idiomToString ( idiomValue) ,
254- colorspace: colorSpaceIDToString ( colorSpaceID)
261+ colorspace: colorSpaceIDToString ( colorSpaceID) ,
262+ contentHash: contentHash
255263 )
256264 assets. append ( asset)
257265 }
@@ -266,7 +274,8 @@ enum AssetUtil {
266274 filename: nil ,
267275 type: nil ,
268276 idiom: nil ,
269- colorspace: nil
277+ colorspace: nil ,
278+ contentHash: nil
270279 ) )
271280
272281 let data = try ! JSONEncoder ( ) . encode ( assets)
@@ -275,7 +284,7 @@ enum AssetUtil {
275284 . appendingPathComponent ( " Assets " )
276285 . appendingPathExtension ( " json " )
277286 try ! data. write ( to: url, options: [ ] )
278- for (id, imageInfo) in images {
287+ for (id, imageInfo) in cgImages {
279288 let format = imageInfo. format
280289 let cgImage = imageInfo. cgImage
281290 let fileURL = folder. appendingPathComponent ( id) . appendingPathExtension ( format)
@@ -460,6 +469,17 @@ enum AssetUtil {
460469 }
461470}
462471
472+ private extension Data {
473+ func sha256Hash( ) -> String {
474+ if #available( macOS 10 . 15 , * ) {
475+ let digest = SHA256 . hash ( data: self )
476+ return digest. map { String ( format: " %02x " , $0) } . joined ( )
477+ }
478+ // Fallback for older macOS (shouldn't happen with version 13+ requirement)
479+ return " "
480+ }
481+ }
482+
463483private extension NSObject {
464484 func getUInt( forKey key: String ) -> UInt ? {
465485 if let result = perform ( Selector ( key) ) {
0 commit comments