diff --git a/assets/bg_default.png b/assets/bg_default.png new file mode 100644 index 0000000..f866c48 Binary files /dev/null and b/assets/bg_default.png differ diff --git a/calendar/scripts/export-kindle-background.sh b/calendar/scripts/export-kindle-background.sh index 4d1038f..160af85 100644 --- a/calendar/scripts/export-kindle-background.sh +++ b/calendar/scripts/export-kindle-background.sh @@ -18,6 +18,7 @@ trap 'kill "$SERVER_PID" 2>/dev/null || true' EXIT INT TERM sleep 1 /usr/bin/swift "$CALENDAR_DIR/scripts/export-kindle-background.swift" "$URL" "$OUT_PNG" "$OUT_REGION" + node "$CALENDAR_DIR/scripts/generate-dashboard-manifest.mjs" >/dev/null cat "$OUT_REGION" diff --git a/calendar/scripts/export-kindle-background.swift b/calendar/scripts/export-kindle-background.swift index ad0e1a7..45b2c79 100644 --- a/calendar/scripts/export-kindle-background.swift +++ b/calendar/scripts/export-kindle-background.swift @@ -2,6 +2,8 @@ import AppKit import Foundation +import ImageIO +import UniformTypeIdentifiers import WebKit enum ExportError: Error, CustomStringConvertible { @@ -29,7 +31,8 @@ final class SnapshotExporter: NSObject, WKNavigationDelegate { private let pngOutputURL: URL private let regionOutputURL: URL private let completion: (Result) -> Void - private let targetSize = CGSize(width: 1024, height: 600) + // 直接按 Kindle Voyage 的系统屏保尺寸导出,避免额外旋转和补边。 + private let targetSize = CGSize(width: 1072, height: 1448) private lazy var window: NSWindow = { let window = NSWindow( @@ -129,16 +132,52 @@ final class SnapshotExporter: NSObject, WKNavigationDelegate { image.draw(in: NSRect(origin: .zero, size: targetSize)) normalizedImage.unlockFocus() - guard - let tiffRepresentation = normalizedImage.tiffRepresentation, - let bitmap = NSBitmapImageRep(data: tiffRepresentation), - let pngData = bitmap.representation(using: .png, properties: [:]) - else { + guard let sourceCGImage = normalizedImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else { + throw ExportError.pngEncodingFailed(url.path) + } + + let width = Int(targetSize.width) + let height = Int(targetSize.height) + let colorSpace = CGColorSpaceCreateDeviceGray() + + // 输出 8-bit 灰度 PNG,但页面本身仍按纯白底和纯黑字设计,避免额外灰阶装饰。 + guard let context = CGContext( + data: nil, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: 0, + space: colorSpace, + bitmapInfo: CGImageAlphaInfo.none.rawValue + ) else { + throw ExportError.pngEncodingFailed(url.path) + } + + context.setFillColor(gray: 1, alpha: 1) + context.fill(CGRect(x: 0, y: 0, width: width, height: height)) + context.interpolationQuality = .high + context.draw(sourceCGImage, in: CGRect(x: 0, y: 0, width: width, height: height)) + + guard let grayscaleImage = context.makeImage() else { throw ExportError.pngEncodingFailed(url.path) } try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) - try pngData.write(to: url) + + guard let destination = CGImageDestinationCreateWithURL( + url as CFURL, + UTType.png.identifier as CFString, + 1, + nil + ) else { + throw ExportError.pngEncodingFailed(url.path) + } + + CGImageDestinationAddImage(destination, grayscaleImage, nil) + + guard CGImageDestinationFinalize(destination) else { + throw ExportError.pngEncodingFailed(url.path) + } } private func saveRegion(region: [String: NSNumber]) throws { diff --git a/calendar/src/components/AnalogClock.vue b/calendar/src/components/AnalogClock.vue index 24b5929..3335787 100644 --- a/calendar/src/components/AnalogClock.vue +++ b/calendar/src/components/AnalogClock.vue @@ -58,8 +58,7 @@ const stageStyle = computed(() => ({ :style="stageStyle" data-clock-region="true" > -
-