Compare commits
3 Commits
756629d2dc
...
31edd35463
Author | SHA1 | Date | |
---|---|---|---|
|
31edd35463 | ||
|
b39066f47f | ||
|
152a76935b |
@ -126,6 +126,20 @@ extension Element {
|
|||||||
This property is mandatory at root level, and is propagated to child elements.
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
*/
|
*/
|
||||||
let relatedContentText: String
|
let relatedContentText: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display on the navigation element pointing to this element as the next page.
|
||||||
|
|
||||||
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
|
*/
|
||||||
|
let navigationTextAsNextPage: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display on a navigation element pointing to this element as the previous page.
|
||||||
|
|
||||||
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
|
*/
|
||||||
|
let navigationTextAsPreviousPage: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +185,10 @@ extension Element.LocalizedMetadata {
|
|||||||
self.externalUrl = log.unexpected(data.externalUrl, name: "externalUrl", source: source)
|
self.externalUrl = log.unexpected(data.externalUrl, name: "externalUrl", source: source)
|
||||||
self.relatedContentText = log
|
self.relatedContentText = log
|
||||||
.required(data.relatedContentText, name: "relatedContentText", source: source) ?? ""
|
.required(data.relatedContentText, name: "relatedContentText", source: source) ?? ""
|
||||||
|
self.navigationTextAsNextPage = log
|
||||||
|
.required(data.navigationTextAsNextPage, name: "navigationTextAsNextPage", source: source) ?? ""
|
||||||
|
self.navigationTextAsPreviousPage = log
|
||||||
|
.required(data.navigationTextAsPreviousPage, name: "navigationTextAsPreviousPage", source: source) ?? ""
|
||||||
|
|
||||||
guard isComplete else {
|
guard isComplete else {
|
||||||
return nil
|
return nil
|
||||||
@ -207,6 +225,8 @@ extension Element.LocalizedMetadata {
|
|||||||
self.cornerText = data.cornerText
|
self.cornerText = data.cornerText
|
||||||
self.externalUrl = data.externalUrl
|
self.externalUrl = data.externalUrl
|
||||||
self.relatedContentText = data.relatedContentText ?? parent.relatedContentText
|
self.relatedContentText = data.relatedContentText ?? parent.relatedContentText
|
||||||
|
self.navigationTextAsPreviousPage = data.navigationTextAsPreviousPage ?? parent.navigationTextAsPreviousPage
|
||||||
|
self.navigationTextAsNextPage = data.navigationTextAsNextPage ?? parent.navigationTextAsNextPage
|
||||||
|
|
||||||
guard isComplete else {
|
guard isComplete else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -310,6 +310,16 @@ extension Element {
|
|||||||
elements.filter { $0.state.isShownInOverview }
|
elements.filter { $0.state.isShownInOverview }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var linkedElements: [LinkedElement] {
|
||||||
|
let items = sortedItems
|
||||||
|
let connected = items.enumerated().map { i, element in
|
||||||
|
let previous = i+1 < items.count ? items[i+1] : nil
|
||||||
|
let next = i > 0 ? items[i-1] : nil
|
||||||
|
return (previous, element, next)
|
||||||
|
}
|
||||||
|
return connected + elements.filter { !$0.state.isShownInOverview }.map { (nil, $0, nil )}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The url of the top-level section of the element.
|
The url of the top-level section of the element.
|
||||||
*/
|
*/
|
||||||
|
@ -119,6 +119,20 @@ extension GenericMetadata {
|
|||||||
This property is mandatory at root level, and is propagated to child elements.
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
*/
|
*/
|
||||||
let relatedContentText: String?
|
let relatedContentText: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display on a navigation element pointing to this element as the previous page.
|
||||||
|
|
||||||
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
|
*/
|
||||||
|
let navigationTextAsPreviousPage: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
The text to display on the navigation element pointing to this element as the next page.
|
||||||
|
|
||||||
|
This property is mandatory at root level, and is propagated to child elements.
|
||||||
|
*/
|
||||||
|
let navigationTextAsNextPage: String?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +156,8 @@ extension GenericMetadata.LocalizedMetadata: Codable {
|
|||||||
.cornerText,
|
.cornerText,
|
||||||
.externalUrl,
|
.externalUrl,
|
||||||
.relatedContentText,
|
.relatedContentText,
|
||||||
|
.navigationTextAsPreviousPage,
|
||||||
|
.navigationTextAsNextPage,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +188,9 @@ extension GenericMetadata.LocalizedMetadata {
|
|||||||
thumbnailSuffix: nil,
|
thumbnailSuffix: nil,
|
||||||
cornerText: nil,
|
cornerText: nil,
|
||||||
externalUrl: nil,
|
externalUrl: nil,
|
||||||
relatedContentText: nil)
|
relatedContentText: nil,
|
||||||
|
navigationTextAsPreviousPage: nil,
|
||||||
|
navigationTextAsNextPage: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,7 +212,9 @@ extension GenericMetadata.LocalizedMetadata {
|
|||||||
thumbnailSuffix: nil,
|
thumbnailSuffix: nil,
|
||||||
cornerText: nil,
|
cornerText: nil,
|
||||||
externalUrl: nil,
|
externalUrl: nil,
|
||||||
relatedContentText: "")
|
relatedContentText: "",
|
||||||
|
navigationTextAsPreviousPage: "",
|
||||||
|
navigationTextAsNextPage: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var full: GenericMetadata.LocalizedMetadata {
|
static var full: GenericMetadata.LocalizedMetadata {
|
||||||
@ -213,6 +233,8 @@ extension GenericMetadata.LocalizedMetadata {
|
|||||||
thumbnailSuffix: "",
|
thumbnailSuffix: "",
|
||||||
cornerText: "",
|
cornerText: "",
|
||||||
externalUrl: "",
|
externalUrl: "",
|
||||||
relatedContentText: "")
|
relatedContentText: "",
|
||||||
|
navigationTextAsPreviousPage: "",
|
||||||
|
navigationTextAsNextPage: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,9 @@ enum ThumbnailStyle: String, CaseIterable {
|
|||||||
case .large:
|
case .large:
|
||||||
return 374
|
return 374
|
||||||
case .square:
|
case .square:
|
||||||
return height
|
return 178
|
||||||
case .small:
|
case .small:
|
||||||
return height
|
return 78
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ extension NSImage {
|
|||||||
guard self.size.width > size.width else {
|
guard self.size.width > size.width else {
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
return NSImage(size: size, flipped: false) { (resizedRect) -> Bool in
|
return NSImage(size: size, flipped: false) { [weak self] (resizedRect) -> Bool in
|
||||||
self.draw(in: resizedRect)
|
self?.draw(in: resizedRect)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,7 @@ final class ImageGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func requireImage(at destination: String, generatedFrom source: String, requiredBy path: String, width: Int, height: Int?) -> NSSize {
|
func requireImage(at destination: String, generatedFrom source: String, requiredBy path: String, width: Int, height: Int?) -> NSSize {
|
||||||
|
requiredImages.insert(destination)
|
||||||
let height = height.unwrapped(CGFloat.init)
|
let height = height.unwrapped(CGFloat.init)
|
||||||
let sourceUrl = input.appendingPathComponent(source)
|
let sourceUrl = input.appendingPathComponent(source)
|
||||||
guard sourceUrl.exists else {
|
guard sourceUrl.exists else {
|
||||||
@ -158,6 +159,8 @@ final class ImageGenerator {
|
|||||||
}
|
}
|
||||||
printMissingImages()
|
printMissingImages()
|
||||||
printImageWarnings()
|
printImageWarnings()
|
||||||
|
printGeneratedImages()
|
||||||
|
printTotalImageCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func printMissingImages() {
|
private func printMissingImages() {
|
||||||
@ -180,6 +183,20 @@ final class ImageGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func printGeneratedImages() {
|
||||||
|
guard !generatedImages.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print("\(generatedImages.count) images generated:")
|
||||||
|
for image in generatedImages {
|
||||||
|
print(" " + image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func printTotalImageCount() {
|
||||||
|
print("\(requiredImages.count) images")
|
||||||
|
}
|
||||||
|
|
||||||
private func addWarning(_ message: String, destination: String, path: String) {
|
private func addWarning(_ message: String, destination: String, path: String) {
|
||||||
let warning = " \(destination): \(message) required by \(path)"
|
let warning = " \(destination): \(message) required by \(path)"
|
||||||
imageWarnings.insert(warning)
|
imageWarnings.insert(warning)
|
||||||
@ -204,14 +221,13 @@ final class ImageGenerator {
|
|||||||
missingImages[source] = images.first?.path
|
missingImages[source] = images.first?.path
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if imageHasChanged {
|
let jobs = imageHasChanged ? images : images.filter(isMissing)
|
||||||
// Update all images
|
// Update all images
|
||||||
images.forEach { create(job: $0, from: image, source: source) }
|
jobs.forEach { job in
|
||||||
} else {
|
// Prevent memory overflow due to repeated NSImage operations
|
||||||
// Update only missing images
|
autoreleasepool {
|
||||||
images
|
create(job: job, from: image, source: source)
|
||||||
.filter(isMissing)
|
}
|
||||||
.forEach { create(job: $0, from: image, source: source) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,31 +240,43 @@ final class ImageGenerator {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
let desiredWidth = CGFloat(image.size.width)
|
|
||||||
|
|
||||||
let destinationSize = image.size.scaledDown(to: desiredWidth)
|
|
||||||
let scaledImage = image.scaledDown(to: destinationSize)
|
|
||||||
let scaledSize = scaledImage.size
|
|
||||||
|
|
||||||
if abs(scaledSize.width - desiredWidth) > 2 {
|
|
||||||
addWarning("Invalid width (\(scaledSize.width) instead of \(desiredWidth))", job: job)
|
|
||||||
}
|
|
||||||
|
|
||||||
if scaledSize.width > desiredWidth {
|
|
||||||
addWarning("Invalid width (\(scaledSize.width) instead of \(desiredWidth))", job: job)
|
|
||||||
}
|
|
||||||
|
|
||||||
let destinationExtension = destinationUrl.pathExtension.lowercased()
|
let destinationExtension = destinationUrl.pathExtension.lowercased()
|
||||||
guard let type = ImageType(fileExtension: destinationExtension)?.fileType else {
|
guard let type = ImageType(fileExtension: destinationExtension)?.fileType else {
|
||||||
addWarning("Invalid image extension \(destinationExtension)", job: job)
|
addWarning("Invalid image extension \(destinationExtension)", job: job)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let tiff = scaledImage.tiffRepresentation, let tiffData = NSBitmapImageRep(data: tiff) else {
|
|
||||||
addWarning("Failed to get data", job: job)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let data = tiffData.representation(using: type, properties: [.compressionFactor: NSNumber(0.7)]) else {
|
let desiredWidth = CGFloat(job.width)
|
||||||
|
|
||||||
|
let sourceRep = image.representations[0]
|
||||||
|
let destinationSize = NSSize(width: sourceRep.pixelsWide, height: sourceRep.pixelsHigh)
|
||||||
|
.scaledDown(to: desiredWidth)
|
||||||
|
//image.size.scaledDown(to: desiredWidth)
|
||||||
|
|
||||||
|
print("\(job.destination):")
|
||||||
|
print(" Source: \(image.size.width) x \(image.size.height)")
|
||||||
|
print(" Wanted: \(destinationSize.width) x \(destinationSize.height) (\(job.width))")
|
||||||
|
// create NSBitmapRep manually, if using cgImage, the resulting size is wrong
|
||||||
|
let rep = NSBitmapImageRep(bitmapDataPlanes: nil,
|
||||||
|
pixelsWide: Int(destinationSize.width),
|
||||||
|
pixelsHigh: Int(destinationSize.height),
|
||||||
|
bitsPerSample: 8,
|
||||||
|
samplesPerPixel: 4,
|
||||||
|
hasAlpha: true,
|
||||||
|
isPlanar: false,
|
||||||
|
colorSpaceName: NSColorSpaceName.deviceRGB,
|
||||||
|
bytesPerRow: Int(destinationSize.width) * 4,
|
||||||
|
bitsPerPixel: 32)!
|
||||||
|
|
||||||
|
let ctx = NSGraphicsContext(bitmapImageRep: rep)
|
||||||
|
NSGraphicsContext.saveGraphicsState()
|
||||||
|
NSGraphicsContext.current = ctx
|
||||||
|
image.draw(in: NSMakeRect(0, 0, destinationSize.width, destinationSize.height))
|
||||||
|
ctx?.flushGraphics()
|
||||||
|
NSGraphicsContext.restoreGraphicsState()
|
||||||
|
|
||||||
|
// Get NSData, and save it
|
||||||
|
guard let data = rep.representation(using: type, properties: [.compressionFactor: NSNumber(0.7)]) else {
|
||||||
addWarning("Failed to get data", job: job)
|
addWarning("Failed to get data", job: job)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -258,5 +286,6 @@ final class ImageGenerator {
|
|||||||
addWarning("Failed to write image (\(error))", job: job)
|
addWarning("Failed to write image (\(error))", job: job)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
generatedImages.insert(job.destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,13 @@ import Ink
|
|||||||
|
|
||||||
struct PageGenerator {
|
struct PageGenerator {
|
||||||
|
|
||||||
struct NavigationLink {
|
|
||||||
|
|
||||||
let link: String
|
|
||||||
|
|
||||||
let text: String
|
|
||||||
}
|
|
||||||
|
|
||||||
private let factory: LocalizedSiteTemplate
|
private let factory: LocalizedSiteTemplate
|
||||||
|
|
||||||
init(factory: LocalizedSiteTemplate) {
|
init(factory: LocalizedSiteTemplate) {
|
||||||
self.factory = factory
|
self.factory = factory
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate(page: Element, language: String, nextPage: NavigationLink?, previousPage: NavigationLink?) {
|
func generate(page: Element, language: String, previousPage: Element?, nextPage: Element?) {
|
||||||
guard !page.isExternalPage else {
|
guard !page.isExternalPage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -35,10 +28,10 @@ struct PageGenerator {
|
|||||||
|
|
||||||
content[.header] = makeHeader(page: page, metadata: metadata, language: language)
|
content[.header] = makeHeader(page: page, metadata: metadata, language: language)
|
||||||
content[.content] = pageContent
|
content[.content] = pageContent
|
||||||
content[.previousPageLinkText] = previousPage.unwrapped { factory.factory.html.makePrevText($0.text) }
|
content[.previousPageLinkText] = previousText(for: previousPage, language: language)
|
||||||
content[.previousPageUrl] = previousPage?.link
|
content[.previousPageUrl] = navLink(from: page, to: previousPage, language: language)
|
||||||
content[.nextPageLinkText] = nextPage.unwrapped { factory.factory.html.makeNextText($0.text) }
|
content[.nextPageLinkText] = nextText(for: nextPage, language: language)
|
||||||
content[.nextPageUrl] = nextPage?.link
|
content[.nextPageUrl] = navLink(from: page, to: nextPage, language: language)
|
||||||
content[.footer] = page.customFooterContent()
|
content[.footer] = page.customFooterContent()
|
||||||
|
|
||||||
if pageIncludesCode {
|
if pageIncludesCode {
|
||||||
@ -58,6 +51,27 @@ struct PageGenerator {
|
|||||||
files.generated(page: path)
|
files.generated(page: path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func navLink(from element: Element, to destination: Element?, language: String) -> String? {
|
||||||
|
guard let fullPath = destination?.fullPageUrl(for: language) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return element.relativePathToOtherSiteElement(file: fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func previousText(for element: Element?, language: String) -> String? {
|
||||||
|
guard let text = element?.localized(for: language).navigationTextAsPreviousPage else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return factory.factory.html.makePrevText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func nextText(for element: Element?, language: String) -> String? {
|
||||||
|
guard let text = element?.localized(for: language).navigationTextAsNextPage else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return factory.factory.html.makeNextText(text)
|
||||||
|
}
|
||||||
|
|
||||||
private func makeContent(page: Element, metadata: Element.LocalizedMetadata, language: String, path: String) -> (content: String, includesCode: Bool, isEmpty: Bool) {
|
private func makeContent(page: Element, metadata: Element.LocalizedMetadata, language: String, path: String) -> (content: String, includesCode: Bool, isEmpty: Bool) {
|
||||||
let create = configuration.createMdFilesIfMissing
|
let create = configuration.createMdFilesIfMissing
|
||||||
if let raw = files.contentOfOptionalFile(atPath: path, source: page.path, createEmptyFileIfMissing: create)?
|
if let raw = files.contentOfOptionalFile(atPath: path, source: page.path, createEmptyFileIfMissing: create)?
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
typealias LinkedElement = (previous: Element?, element: Element, next: Element?)
|
||||||
|
|
||||||
struct SiteGenerator {
|
struct SiteGenerator {
|
||||||
|
|
||||||
let templates: TemplateFactory
|
let templates: TemplateFactory
|
||||||
@ -26,22 +28,21 @@ struct SiteGenerator {
|
|||||||
let overviewGenerator = OverviewPageGenerator(factory: template)
|
let overviewGenerator = OverviewPageGenerator(factory: template)
|
||||||
let pageGenerator = PageGenerator(factory: template)
|
let pageGenerator = PageGenerator(factory: template)
|
||||||
|
|
||||||
var elementsToProcess: [Element] = [site]
|
var elementsToProcess: [LinkedElement] = [(nil, site, nil)]
|
||||||
while let element = elementsToProcess.popLast() {
|
while let (previous, element, next) = elementsToProcess.popLast() {
|
||||||
// Move recursively down to all pages
|
// Move recursively down to all pages
|
||||||
elementsToProcess.append(contentsOf: element.elements)
|
elementsToProcess.append(contentsOf: element.linkedElements)
|
||||||
|
|
||||||
processAllFiles(for: element)
|
processAllFiles(for: element)
|
||||||
|
|
||||||
if !element.elements.isEmpty {
|
if !element.elements.isEmpty {
|
||||||
overviewGenerator.generate(section: element, language: language)
|
overviewGenerator.generate(section: element, language: language)
|
||||||
} else {
|
} else {
|
||||||
#warning("Determine previous and next pages (with relative links)")
|
|
||||||
pageGenerator.generate(
|
pageGenerator.generate(
|
||||||
page: element,
|
page: element,
|
||||||
language: language,
|
language: language,
|
||||||
nextPage: nil,
|
previousPage: previous,
|
||||||
previousPage: nil)
|
nextPage: next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user