Sources/OrgCore/Commands/InlineImages.swift
43 lines · 2222 bytes
1import Foundation
2
3/// Image links shown as images (`org-display-inline-images`, Org 9.8.7): a line that is only a
4/// link to an image file, without a description, and the width `#+ATTR_ORG: :width` asks for.
5public enum InlineImages {
6 public struct Image: Sendable, Equatable {
7 public let path: String
8 public let width: Int?
9 }
10
11 static let extensions = "png|jpe?g|gif|svg|webp|tiff?|bmp|heic|avif"
12 static let pattern = try! NSRegularExpression(pattern: "^[ \\t]*\\[\\[(?:file:)?([^\\]\\n]+\\.(?:\(extensions)))\\]\\][ \\t]*$", options: .caseInsensitive)
13
14 /// The image on the line at `lineStart`, if the line is an image link.
15 public static func image(atLine lineStart: Int, in text: NSString) -> Image? {
16 let line = text.lineRange(for: NSRange(location: lineStart, length: 0))
17 let content = text.substring(with: line).trimmingCharacters(in: .newlines)
18 guard let m = pattern.firstMatch(in: content, range: NSRange(location: 0, length: (content as NSString).length)) else { return nil }
19 let path = (content as NSString).substring(with: m.range(at: 1))
20 guard !path.contains("://"), !path.hasPrefix("attachment:") else { return nil }
21 // `#+ATTR_ORG: :width N` in the affiliated keywords above.
22 var width: Int?
23 var at = line.location
24 while at > 0 {
25 let previous = text.lineRange(for: NSRange(location: at - 1, length: 0))
26 let keyword = text.substring(with: previous)
27 guard keyword.range(of: "^[ \\t]*#\\+[A-Za-z_]+:", options: .regularExpression) != nil else { break }
28 if width == nil, let w = keyword.firstMatch(of: /(?i)^[ \t]*#\+ATTR_ORG:.*:width[ \t]+([0-9]+)/) { width = Int(w.1) }
29 at = previous.location
30 }
31 return Image(path: path, width: width)
32 }
33
34 /// `#+STARTUP: inlineimages` or `noinlineimages`, if the file says.
35 public static func startup(_ tree: OrgTree) -> Bool? {
36 var shown: Bool?
37 for word in tree.settings.startup {
38 if word.lowercased() == "inlineimages" { shown = true }
39 if word.lowercased() == "noinlineimages" { shown = false }
40 }
41 return shown
42 }
43}