diff --git a/Sources/Generator/Files/Configuration.swift b/Sources/Generator/Files/Configuration.swift index d481735..54ddca0 100644 --- a/Sources/Generator/Files/Configuration.swift +++ b/Sources/Generator/Files/Configuration.swift @@ -15,6 +15,11 @@ struct Configuration: Codable { */ let pageImageWidth: Int + /** + The maximum width (in pixels) for images shown full screen. + */ + let fullScreenImageWidth: Int + /** Automatically minify all `.css` and `.js` resources which are copied to the output folder. diff --git a/Sources/Generator/Generators/PageContentGenerator.swift b/Sources/Generator/Generators/PageContentGenerator.swift index fb78772..9d9ac3f 100644 --- a/Sources/Generator/Generators/PageContentGenerator.swift +++ b/Sources/Generator/Generators/PageContentGenerator.swift @@ -4,6 +4,8 @@ import Splash struct PageContentGenerator { + private let largeImageIndicator = "*large*" + private let factory: TemplateFactory private let swift = SyntaxHighlighter(format: HTMLOutputFormat()) @@ -20,9 +22,10 @@ struct PageContentGenerator { func generate(page: Element, language: String, content: String) -> (content: String, includesCode: Bool) { var hasCodeContent = false + var largeImageCount = 0 let imageModifier = Modifier(target: .images) { html, markdown in - processMarkdownImage(markdown: markdown, html: html, page: page, language: language) + processMarkdownImage(markdown: markdown, html: html, page: page, language: language, largeImageCount: &largeImageCount) } let codeModifier = Modifier(target: .codeBlocks) { html, markdown in if markdown.starts(with: "```swift") { @@ -74,10 +77,10 @@ struct PageContentGenerator { return html } - private func processMarkdownImage(markdown: Substring, html: String, page: Element, language: String) -> String { + private func processMarkdownImage(markdown: Substring, html: String, page: Element, language: String, largeImageCount: inout Int) -> String { // Split the markdown ![alt](file title) // There are several known shorthand commands - // For images: ![left_title](file right_title) + // For images: ![*large* left_title](file right_title) // For videos: ![option1,option2,...](file) // For svg with custom area: ![x,y,width,height](file.svg) // For downloads: ![download](file1, text1; file2, text2, ...) @@ -95,7 +98,7 @@ struct PageContentGenerator { let fileExtension = file.lastComponentAfter(".").lowercased() if let _ = ImageType(fileExtension: fileExtension) { - return handleImage(page: page, file: file, rightTitle: title, leftTitle: alt) + return handleImage(page: page, file: file, rightTitle: title, leftTitle: alt, largeImageCount: &largeImageCount) } if let _ = VideoType(rawValue: fileExtension) { return handleVideo(page: page, file: file, optionString: alt) @@ -121,22 +124,48 @@ struct PageContentGenerator { } } - private func handleImage(page: Element, file: String, rightTitle: String?, leftTitle: String?) -> String { + private func handleImage(page: Element, file: String, rightTitle: String?, leftTitle: String?, largeImageCount: inout Int) -> String { let imagePath = page.pathRelativeToRootForContainedInputFile(file) - + let left: String + let createFullScreenVersion: Bool + if let leftTitle { + createFullScreenVersion = leftTitle.hasPrefix(largeImageIndicator) + left = leftTitle.dropBeforeFirst(largeImageIndicator).trimmed + } else { + left = "" + createFullScreenVersion = false + } let size = results.requireFullSizeMultiVersionImage( source: imagePath, destination: imagePath, requiredBy: page.path) - let content: [PageImageTemplate.Key : String] = [ + guard createFullScreenVersion else { + let content: [PageImageTemplate.Key : String] = [ + .image: file.dropAfterLast("."), + .imageExtension: file.lastComponentAfter("."), + .width: "\(Int(size.width))", + .height: "\(Int(size.height))", + .leftText: left, + .rightText: rightTitle ?? ""] + return factory.image.generate(content) + } + + results.requireOriginalSizeImages( + source: imagePath, + destination: imagePath, + requiredBy: page.path) + + largeImageCount += 1 + let content: [EnlargeableImageTemplate.Key : String] = [ .image: file.dropAfterLast("."), .imageExtension: file.lastComponentAfter("."), .width: "\(Int(size.width))", .height: "\(Int(size.height))", - .leftText: leftTitle ?? "", - .rightText: rightTitle ?? ""] - return factory.image.generate(content) + .leftText: left, + .rightText: rightTitle ?? "", + .number: "\(largeImageCount)"] + return factory.largeImage.generate(content) } private func handleVideo(page: Element, file: String, optionString: String?) -> String { diff --git a/Sources/Generator/Processing/GenerationResultsHandler.swift b/Sources/Generator/Processing/GenerationResultsHandler.swift index 83e1cf2..0328d29 100644 --- a/Sources/Generator/Processing/GenerationResultsHandler.swift +++ b/Sources/Generator/Processing/GenerationResultsHandler.swift @@ -314,7 +314,18 @@ final class GenerationResultsHandler { requireMultiVersionImage(source: source, destination: destination, requiredBy: path, width: configuration.pageImageWidth, desiredHeight: nil) } - @discardableResult + func requireOriginalSizeImages( + source: String, + destination: String, + requiredBy path: String) { + _ = requireScaledMultiImage( + source: source, + destination: destination.insert("@full", beforeLast: "."), + requiredBy: path, + width: configuration.fullScreenImageWidth, + desiredHeight: nil) + } + private func requireScaledMultiImage(source: String, destination: String, requiredBy path: String, width: Int, desiredHeight: Int?) -> NSSize { let rawDestinationPath = destination.dropAfterLast(".") let avifPath = rawDestinationPath + ".avif" diff --git a/Sources/Generator/Templates/Elements/EnlargeableImageTemplate.swift b/Sources/Generator/Templates/Elements/EnlargeableImageTemplate.swift new file mode 100644 index 0000000..315b4e2 --- /dev/null +++ b/Sources/Generator/Templates/Elements/EnlargeableImageTemplate.swift @@ -0,0 +1,21 @@ +import Foundation + +struct EnlargeableImageTemplate: Template { + + enum Key: String, CaseIterable { + case image = "IMAGE" + case imageExtension = "IMAGE_EXT" + case width = "WIDTH" + case height = "HEIGHT" + case leftText = "LEFT_TEXT" + case rightText = "RIGHT_TEXT" + case number = "NUMBER" + } + + static let templateName = "image-enlargeable.html" + + let raw: String + + let results: GenerationResultsHandler + +} diff --git a/Sources/Generator/Templates/TemplateFactory.swift b/Sources/Generator/Templates/TemplateFactory.swift index 0cc006d..e8ba985 100644 --- a/Sources/Generator/Templates/TemplateFactory.swift +++ b/Sources/Generator/Templates/TemplateFactory.swift @@ -51,6 +51,8 @@ final class TemplateFactory { let image: PageImageTemplate + let largeImage: EnlargeableImageTemplate + let video: PageVideoTemplate // MARK: Slideshow @@ -69,24 +71,28 @@ final class TemplateFactory { init(templateFolder: URL, results: GenerationResultsHandler) throws { self.templateFolder = templateFolder - self.backNavigation = try .init(in: templateFolder, results: results) - self.pageHead = try .init(in: templateFolder, results: results) - self.topBar = try .init(in: templateFolder, results: results) - self.overviewSection = try .init(in: templateFolder, results: results) - self.overviewSectionClean = try .init(in: templateFolder, results: results) - self.box = try .init(in: templateFolder, results: results) - self.pageLink = try .init(in: templateFolder, results: results) - self.largeThumbnail = try .init(in: templateFolder, results: results) - self.squareThumbnail = try .init(in: templateFolder, results: results) - self.smallThumbnail = try .init(in: templateFolder, results: results) - self.leftHeader = try .init(in: templateFolder, results: results) - self.centeredHeader = try .init(in: templateFolder, results: results) - self.page = try .init(in: templateFolder, results: results) - self.image = try .init(in: templateFolder, results: results) - self.video = try .init(in: templateFolder, results: results) - self.slideshow = try .init(in: templateFolder, results: results) - self.slideshows = try .init(in: templateFolder, results: results) - self.slideshowImage = try .init(in: templateFolder, results: results) + func create() throws -> T where T: Template { + try .init(in: templateFolder, results: results) + } + self.backNavigation = try create() + self.pageHead = try create() + self.topBar = try create() + self.overviewSection = try create() + self.overviewSectionClean = try create() + self.box = try create() + self.pageLink = try create() + self.largeThumbnail = try create() + self.squareThumbnail = try create() + self.smallThumbnail = try create() + self.leftHeader = try create() + self.centeredHeader = try create() + self.page = try create() + self.image = try create() + self.largeImage = try create() + self.video = try create() + self.slideshow = try create() + self.slideshows = try create() + self.slideshowImage = try create() self.html = .init() }