diff --git a/_config.yml b/_config.yml
index dd74651ad5..b5fe7db4c6 100644
--- a/_config.yml
+++ b/_config.yml
@@ -546,6 +546,21 @@ mermaid:
wavedrom:
enable: false
+# Picture Caption tag
+picture_caption:
+ picture_border_radius:
+ enable: false
+ radius_size: 5px
+ # Picture gap size between pictures. No strict validation for units; the `px` unit is also acceptable.
+ picture_gap: 0.5em
+ # Available caption position: top | bottom
+ caption_position: bottom
+ # Available caption alignment: left | center | right
+ caption_alignment: center
+ # Caption margin size between picture and caption. No strict validation for units; the `px` unit is also acceptable.
+ caption_margin: 0.5em
+ caption_font_size: 0.875em
+
# ---------------------------------------------------------------
# Third Party Plugins & Services Settings
# See: https://theme-next.js.org/docs/third-party-services/
diff --git a/scripts/tags/index.js b/scripts/tags/index.js
index d0c4392744..b1ca3a9dff 100644
--- a/scripts/tags/index.js
+++ b/scripts/tags/index.js
@@ -48,6 +48,12 @@ const pdf = require('./pdf')(hexo);
hexo.extend.tag.register('pdf', pdf);
+const pictureCaption = require('./picture-caption')(hexo);
+
+hexo.extend.tag.register('picturecaption', pictureCaption);
+hexo.extend.tag.register('piccap', pictureCaption);
+hexo.extend.tag.register('pc', pictureCaption);
+
const postTabs = require('./tabs')(hexo);
hexo.extend.tag.register('tabs', postTabs, true);
diff --git a/scripts/tags/picture-caption.js b/scripts/tags/picture-caption.js
new file mode 100644
index 0000000000..f86f2dab1e
--- /dev/null
+++ b/scripts/tags/picture-caption.js
@@ -0,0 +1,76 @@
+/**
+ * picture-caption.js | https://theme-next.js.org/docs/tag-plugins/picture-caption
+ * Description: Insert an image with caption.
+ * Usage:
+ * {% picturecaption pictureUrl, captionText %}
+ * {% picturecaption pictureUrl, pictureWidth, pictureHeight, captionText, captionUrl, captionIcon, captionPosition, captionAlignment %}
+ */
+
+'use strict';
+
+module.exports = ctx => function(args) {
+ args = args.join(' ').split(',');
+
+ const pictureUrl = args[0].trim();
+ if (!pictureUrl) {
+ ctx.log.warn('Image URL can NOT be empty in picture-caption tag.');
+ return '';
+ }
+
+ // Default value.
+ let pictureWidth = 'auto';
+ let pictureHeight = 'auto';
+ let captionText = '';
+ let captionUrl = '';
+ let captionIcon = '';
+ const theme = ctx.theme.config;
+ let captionPosition = theme.picture_caption.caption_position;
+ let captionAlignment = theme.picture_caption.caption_alignment;
+
+ // Determine the invocation method based on the number of parameters.
+ if (args.length === 2) {
+ captionText = args[1].trim() || 'Picture';
+ } else if (args.length >= 3) {
+ // Take parameters from back to front.
+ captionAlignment = ['left', 'center', 'right'].includes(args[args.length - 1].trim()) ? args.pop().trim() : 'center';
+ captionPosition = ['top', 'bottom'].includes(args[args.length - 1].trim()) ? args.pop().trim() : 'bottom';
+ captionIcon = args.pop().trim(); // This value can be null.
+ captionUrl = args.pop().trim(); // This value can be null.
+ // Take parameters from front to back.
+ pictureWidth = args[1].trim() || 'auto';
+ pictureHeight = args[2].trim() || 'auto';
+ // Allow the captionText to contain English commas, and extract all characters starting from index 3 to the end.
+ captionText = args.slice(3).join(',') || 'Picture';
+ }
+
+ const style = `width: ${pictureWidth}; height: ${pictureHeight}; object-fit: contain;`;
+
+ const imgTag = ``;
+
+ // Handle captionUrl and captionIcon, default value is null, 'None/none' or empty string is considered as no link.
+ const hascaptionUrl = captionUrl && captionUrl.toLowerCase() !== 'none';
+ const hascaptionIcon = captionIcon && captionIcon.toLowerCase() !== 'none';
+
+ let captionHtml = '';
+ if (captionText) {
+ let captionContent = '';
+ if (hascaptionIcon) {
+ if (!captionIcon.startsWith('fa')) { captionIcon = 'fa fa-' + captionIcon; }
+ captionContent = `${captionText}`;
+ } else {
+ captionContent = captionText;
+ }
+ if (hascaptionUrl) {
+ captionContent = `${captionContent}`;
+ }
+ captionHtml = `