Skip to content

Commit 5df61f9

Browse files
authored
Extra image types (#99)
1 parent f33f3af commit 5df61f9

File tree

6 files changed

+197
-15
lines changed

6 files changed

+197
-15
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ The plugin is highly configurable. Please refer to the default configuration bel
115115
process_cmd = "", ---@type string | fun(): string
116116
copy_images = false, ---@type boolean | fun(): boolean
117117
download_images = true, ---@type boolean | fun(): boolean
118+
formats = { "jpeg", "jpg", "png" }, ---@type string[]
118119

119120
-- drag and drop options
120121
drag_and_drop = {
@@ -151,6 +152,8 @@ The plugin is highly configurable. Please refer to the default configuration bel
151152
\label{fig:$LABEL}
152153
\end{figure}
153154
]], ---@type string | fun(context: table): string
155+
156+
formats = { "jpeg", "jpg", "png", "pdf" }, ---@type table
154157
},
155158

156159
typst = {

lua/img-clip/config.lua

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ local defaults = {
4040
process_cmd = "", ---@type string
4141
copy_images = false, ---@type boolean
4242
download_images = true, ---@type boolean
43+
formats = { "jpeg", "jpg", "png" }, ---@type string[]
4344

4445
-- drag and drop options
4546
drag_and_drop = {
@@ -76,6 +77,8 @@ local defaults = {
7677
\label{fig:$LABEL}
7778
\end{figure}
7879
]], ---@type string
80+
81+
formats = { "jpeg", "jpg", "png", "pdf" }, ---@type table
7982
},
8083

8184
typst = {
@@ -203,7 +206,7 @@ end
203206
---get the value of the option, executing it if it's a function
204207
---@param val any
205208
---@param args? table
206-
---@return string | nil
209+
---@return string | table | nil
207210
local function get_val(val, args)
208211
if val == nil then
209212
return nil
@@ -217,7 +220,7 @@ end
217220
---get the option from the custom table
218221
---@param key string
219222
---@param args? table
220-
---@return string | nil
223+
---@return string | table | nil
221224
local function get_custom_opt(key, opts, args)
222225
if opts["custom"] == nil then
223226
return nil
@@ -233,7 +236,7 @@ end
233236
---get the option from the files table
234237
---@param key string
235238
---@param args? table
236-
---@return string | nil
239+
---@return string | table | nil
237240
local function get_file_opt(key, opts, args, file)
238241
if opts["files"] == nil then
239242
return nil
@@ -255,7 +258,7 @@ end
255258
---get the option from the dirs table
256259
---@param key string
257260
---@param args? table
258-
---@return string | nil
261+
---@return string | table | nil
259262
local function get_dir_opt(key, opts, args, dir)
260263
if opts["dirs"] == nil then
261264
return nil
@@ -298,7 +301,7 @@ end
298301
---@param key string: The key, may be nested (e.g. "default.debug")
299302
---@param args? table: Args that should be passed to the option function
300303
---@param opts? table: Opts passed explicitly to the function
301-
---@return string | nil
304+
---@return string | table | nil
302305
M.get_opt = function(key, args, opts)
303306
-- use explicit opts if provided
304307
-- otherwise, try to get the value from the api_opts

lua/img-clip/mime_types.lua

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
local M = {}
2+
3+
--- Check if the MIME type is a supported format
4+
--- @param mime_type string
5+
--- @param supported_formats string[]
6+
--- @return boolean
7+
M.is_supported_mime_type = function(mime_type, supported_formats)
8+
local mime_fmts = M.formats_from_mime_type(mime_type)
9+
10+
-- Handle unknown formats
11+
if mime_fmts == nil then
12+
return false
13+
end
14+
15+
-- Convert into a set (https://www.lua.org/pil/11.5.html)
16+
local valid_formats = {}
17+
for _, format in ipairs(supported_formats) do
18+
valid_formats[format] = true
19+
end
20+
21+
for _, fmt in pairs(mime_fmts) do
22+
if valid_formats[fmt] ~= nil then
23+
return true
24+
end
25+
end
26+
27+
return false
28+
end
29+
30+
-- Return a table of the image format extensions corresponding to this mime type
31+
--- @return string[]
32+
M.formats_from_mime_type = function(mime_type)
33+
local mime_fmts = M.mime_types[mime_type]
34+
35+
if type(mime_fmts) == "string" then
36+
-- Make a table, because multiple formats could map to the MIME type
37+
mime_fmts = { mime_fmts }
38+
end
39+
40+
return mime_fmts
41+
end
42+
43+
-- A table of common MIME types mapping to file extensions.
44+
-- Based on public documentation and observed conventions across platforms.
45+
M.mime_types = {
46+
["audio/aac"] = "aac",
47+
["application/x-abiword"] = "abw",
48+
["image/apng"] = "apng",
49+
["application/x-freearc"] = "arc",
50+
["image/avif"] = "avif",
51+
["video/x-msvideo"] = "avi",
52+
["application/vnd.amazon.ebook"] = "azw",
53+
["application/octet-stream"] = "bin",
54+
["image/bmp"] = "bmp",
55+
["application/x-bzip"] = "bz",
56+
["application/x-bzip2"] = "bz2",
57+
["application/x-cdf"] = "cda",
58+
["application/x-csh"] = "csh",
59+
["text/css"] = "css",
60+
["text/csv"] = "csv",
61+
["application/msword"] = "doc",
62+
["application/vnd.openxmlformats-officedocument.wordprocessingml.document"] = "docx",
63+
["application/vnd.ms-fontobject"] = "eot",
64+
["application/epub+zip"] = "epub",
65+
["application/gzip"] = "gz",
66+
-- Windows and macOS upload `.gz` files with this the non-standard MIME type.
67+
["application/x-gzip"] = "gz",
68+
["image/gif"] = "gif",
69+
["text/html"] = { "htm", "html" },
70+
["image/vnd.microsoft.icon"] = "ico",
71+
["text/calendar"] = "ics",
72+
["application/java-archive"] = "jar",
73+
["image/jpeg"] = { "jpeg", "jpg" },
74+
-- (Specifications: HTML and RFC 9239)
75+
["text/javascript "] = "js",
76+
["application/json"] = "json",
77+
["application/ld+json"] = "jsonld",
78+
["audio/midi, audio/x-midi"] = { "mid", "midi" },
79+
["text/javascript"] = "mjs",
80+
["audio/mpeg"] = "mp3",
81+
["video/mp4"] = "mp4",
82+
["video/mpeg"] = "mpeg",
83+
["application/vnd.apple.installer+xml"] = "mpkg",
84+
["application/vnd.oasis.opendocument.presentation"] = "odp",
85+
["application/vnd.oasis.opendocument.spreadsheet"] = "ods",
86+
["application/vnd.oasis.opendocument.text"] = "odt",
87+
["audio/ogg"] = { "oga", "opus" },
88+
["video/ogg"] = "ogv",
89+
["application/ogg"] = "ogx",
90+
["font/otf"] = "otf",
91+
["image/png"] = "png",
92+
["application/pdf"] = "pdf",
93+
["application/x-httpd-php"] = "php",
94+
["application/vnd.ms-powerpoint"] = "ppt",
95+
["application/vnd.openxmlformats-officedocument.presentationml.presentation"] = "pptx",
96+
["application/vnd.rar"] = "rar",
97+
["application/rtf"] = "rtf",
98+
["application/x-sh"] = "sh",
99+
["image/svg+xml"] = "svg",
100+
["application/x-tar"] = "tar",
101+
["image/tiff"] = { "tif", "tiff" },
102+
["video/mp2t"] = "ts",
103+
["font/ttf"] = "ttf",
104+
["text/plain"] = "txt",
105+
["application/vnd.visio"] = "vsd",
106+
["audio/wav"] = "wav",
107+
["audio/webm"] = "weba",
108+
["video/webm"] = "webm",
109+
["image/webp"] = "webp",
110+
["font/woff"] = "woff",
111+
["font/woff2"] = "woff2",
112+
["application/xhtml+xml"] = "xhtml",
113+
["application/vnd.ms-excel"] = "xls",
114+
["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] = "xlsx",
115+
-- `application/xml` is recommended but `text/xml` is still commonly used.
116+
-- The specific MIME types used depends on the content semantics.
117+
["application/xml"] = "xml",
118+
["text/xml"] = "xml",
119+
["application/vnd.mozilla.xul+xml"] = "xul",
120+
["application/zip"] = "zip",
121+
-- Windows use this non-standard MIME type for `.zip` files.
122+
["application/x-zip-compressed"] = "zip",
123+
["video/3gpp"] = "3gp",
124+
-- `audio/3gpp` if it doesn't contain video
125+
["audio/3gpp"] = "3gp",
126+
["video/3gpp2"] = "3g2",
127+
-- `audio/3gpp2` if it doesn't contain video
128+
["audo/3gpp2"] = "3g2",
129+
["application/x-7z-compressed"] = "7z",
130+
}
131+
132+
return M

lua/img-clip/util.lua

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local config = require("img-clip.config")
22
local debug = require("img-clip.debug")
3+
local mime_types = require("img-clip.mime_types")
34

45
local M = {}
56

@@ -111,14 +112,40 @@ M.is_image_url = function(str)
111112
end
112113

113114
-- assume its a valid image link if it the url ends with an extension
114-
if str:match("%.png$") or str:match("%.jpg$") or str:match("%.jpeg$") then
115-
return true
115+
local extension = str:match("%.(%w+)$") -- Assumes that the extensions are alphanumeric
116+
117+
local image_formats = config.get_opt("formats")
118+
119+
if extension ~= nil then
120+
--- @cast image_formats table
121+
for _, ext in ipairs(image_formats) do
122+
if extension == ext then
123+
return true
124+
end
125+
end
126+
127+
-- This format was not supported in the user's config
128+
return false
116129
end
117130

118-
-- send a head request to the url and check content type
119-
local command = string.format("curl -s -I -w '%%{content_type}' '%s'", str)
131+
-- send a head request to the url and check content type.
132+
-- Add the 'CONTENT_TYPE' text on the last line for easier matching
133+
local command = string.format("curl -s -I -w 'CONTENT_TYPE: %%{content_type}' '%s'", str)
134+
120135
local output, exit_code = M.execute(command)
121-
return exit_code == 0 and output ~= nil and (output:match("image/png") ~= nil or output:match("image/jpeg") ~= nil)
136+
137+
if exit_code ~= 0 or output == nil then
138+
return false
139+
end
140+
141+
-- Match the content type
142+
-- The capture group is any pattern, until the next semi-colon or white space.
143+
-- Note this makes the assumption that the actual content type is first
144+
---@cast output string
145+
local content_type = string.match(output, "CONTENT_TYPE:%s([^%s;]+)")
146+
147+
--- @cast image_formats table
148+
return content_type ~= nil and mime_types.is_supported_mime_type(content_type, image_formats)
122149
end
123150

124151
---@param str string
@@ -127,11 +154,25 @@ M.is_image_path = function(str)
127154
str = string.lower(str)
128155

129156
local has_path_sep = str:find("/") ~= nil or str:find("\\") ~= nil
130-
local has_image_ext = str:match("^.*%.(png)$") ~= nil
131-
or str:match("^.*%.(jpg)$") ~= nil
132-
or str:match("^.*%.(jpeg)$") ~= nil
133157

134-
return has_path_sep and has_image_ext
158+
local extension = str:match("%.(%w+)$") -- Assumes that the extensions are alphanumeric
159+
160+
if extension == nil then
161+
return false
162+
end
163+
164+
local formats = config.get_opt("formats")
165+
166+
local has_supported_format = false
167+
--- @cast formats table
168+
for _, ext in ipairs(formats) do
169+
has_supported_format = has_supported_format or (ext == extension)
170+
if has_supported_format then
171+
break
172+
end
173+
end
174+
175+
return has_path_sep and has_supported_format
135176
end
136177

137178
return M

tests/util_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe("util", function()
115115

116116
it("should return true for a valid image URL without an extension but with image content type", function()
117117
util.execute = function()
118-
return "image/png", 0
118+
return "CONTENT_TYPE: image/png", 0
119119
end
120120
assert.is_true(util.is_image_url("http://example.com/image"))
121121
end)

vimdoc.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ The plugin is highly configurable. Please refer to the default configuration bel
115115
process_cmd = "", ---@type string | fun(): string
116116
copy_images = false, ---@type boolean | fun(): boolean
117117
download_images = true, ---@type boolean | fun(): boolean
118+
formats = { "jpeg", "jpg", "png" }, ---@type string[]
118119

119120
-- drag and drop options
120121
drag_and_drop = {
@@ -151,6 +152,8 @@ The plugin is highly configurable. Please refer to the default configuration bel
151152
\label{fig:$LABEL}
152153
\end{figure}
153154
]], ---@type string | fun(context: table): string
155+
156+
formats = { "jpeg", "jpg", "png", "pdf" }, ---@type table
154157
},
155158

156159
typst = {

0 commit comments

Comments
 (0)