@peaceroad/markdown-it-figure-with-p-caption 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,22 +1,44 @@
1
1
  import {
2
+ analyzeCaptionParagraph,
2
3
  analyzeCaptionStart,
3
- buildLabelClassLookup,
4
+ applyCaptionParagraph,
4
5
  buildLabelPrefixMarkerRegFromMarkers,
6
+ canonicalizeCaptionNumberingMark,
7
+ createCaptionNumberingPolicy,
8
+ createCaptionNumberingRuntime,
5
9
  getGeneratedLabelDefaults,
10
+ normalizeAutoLabelNumberSets,
6
11
  normalizeLabelPrefixMarkers,
7
12
  setCaptionParagraph,
8
13
  getMarkRegStateForLanguages,
9
14
  stripLabelPrefixMarker,
10
15
  } from 'p7d-markdown-it-p-captions'
11
- import { detectHtmlFigureCandidate } from './embeds/detect.js'
16
+ import { applyHtmlFigureTransform, detectHtmlFigureCandidate } from './embeds/detect.js'
17
+ import {
18
+ createFigureCaptionCounterKeyResolverFromMarkRegState,
19
+ } from './caption-numbering/counter-series.js'
20
+ import {
21
+ createUnscopedNumberingContext,
22
+ formatFigureCaptionGeneratedNumberUnchecked,
23
+ getCaptionNumberingContext,
24
+ parseFigureCaptionExplicitNumberUnchecked,
25
+ } from './caption-numbering/number-codec.js'
26
+ import {
27
+ getFigureCaptionNumberingPolicyState,
28
+ normalizeFigureCaptionNumberingPolicy,
29
+ } from './caption-numbering/options.js'
30
+ import {
31
+ createFigureCaptionScopeRuntime,
32
+ getFigureCaptionScopeNestedContainerType,
33
+ updateFigureCaptionScopeFromHeading,
34
+ } from './caption-numbering/scope.js'
12
35
 
13
36
  const imageAttrsReg = /^ *\{(.*?)\} *$/
14
- const classAttrReg = /^\./
15
- const idAttrReg = /^#/
16
37
  const sampLangReg = /^ *(?:samp|shell|console)(?:(?= )|$)/
17
38
  const asciiLabelReg = /^[A-Za-z]/
18
39
  const attrNameReg = /^[^\s=]+$/
19
- const labelTrailingJointReg = /[.\u3002\uff0e::]\s*$/
40
+ const labelTrailingJointReg = /[.\u3002\uff0e::\u3000]\s*$/
41
+ const installedKey = Symbol.for('p7d-markdown-it-figure-with-p-caption.installed')
20
42
  const CHECK_TYPE_TOKEN_MAP = {
21
43
  table_open: 'table',
22
44
  pre_open: 'pre',
@@ -34,11 +56,10 @@ const normalizeLanguageCode = (value) => {
34
56
  return separatorIndex === -1 ? normalized : normalized.slice(0, separatorIndex)
35
57
  }
36
58
  const appendAvailableLanguage = (target, lang, availableLanguages) => {
37
- if (!lang) return false
38
- if (availableLanguages.indexOf(lang) === -1) return false
39
- if (target.indexOf(lang) !== -1) return false
59
+ if (!lang) return
60
+ if (availableLanguages.indexOf(lang) === -1) return
61
+ if (target.indexOf(lang) !== -1) return
40
62
  target.push(lang)
41
- return true
42
63
  }
43
64
  const normalizePreferredLanguages = (value, availableLanguages) => {
44
65
  if (!Array.isArray(availableLanguages) || availableLanguages.length === 0) return []
@@ -103,31 +124,30 @@ const isHyphenFenceLine = (src, lineStart) => {
103
124
  if (index >= src.length || src.charCodeAt(index) !== 0x0a) return 0
104
125
  return hyphenCount
105
126
  }
106
- const skipLeadingFrontmatter = (src) => {
107
- if (typeof src !== 'string' || isHyphenFenceLine(src, 0) === 0) return src
127
+ const getBodyStartAfterLeadingFrontmatter = (src) => {
128
+ if (typeof src !== 'string' || isHyphenFenceLine(src, 0) === 0) return 0
108
129
  let lineStart = src.indexOf('\n')
109
- if (lineStart === -1) return src
130
+ if (lineStart === -1) return 0
110
131
  lineStart++
111
132
  while (lineStart < src.length) {
112
133
  if (isHyphenFenceLine(src, lineStart) > 0) {
113
134
  const nextLineStart = src.indexOf('\n', lineStart)
114
- if (nextLineStart === -1) return ''
115
- return src.slice(nextLineStart + 1)
135
+ return nextLineStart === -1 ? src.length : nextLineStart + 1
116
136
  }
117
137
  const nextLineStart = src.indexOf('\n', lineStart)
118
138
  if (nextLineStart === -1) break
119
139
  lineStart = nextLineStart + 1
120
140
  }
121
- return src
141
+ return 0
122
142
  }
123
143
  const detectDocumentPrimaryLanguage = (src, availableLanguages) => {
124
144
  if (!src || availableLanguages.indexOf('ja') === -1) return ''
125
- const body = skipLeadingFrontmatter(src)
126
- const limit = Math.min(body.length, 8192)
145
+ const bodyStart = getBodyStartAfterLeadingFrontmatter(src)
146
+ const limit = Math.min(src.length, bodyStart + 8192)
127
147
  let japaneseCount = 0
128
148
  let asciiAlphaCount = 0
129
- for (let i = 0; i < limit; i++) {
130
- const code = body.charCodeAt(i)
149
+ for (let i = bodyStart; i < limit; i++) {
150
+ const code = src.charCodeAt(i)
131
151
  if (isJapaneseCharCode(code)) {
132
152
  japaneseCount++
133
153
  continue
@@ -140,10 +160,6 @@ const detectDocumentPrimaryLanguage = (src, availableLanguages) => {
140
160
  if (asciiAlphaCount === 0) return 'ja'
141
161
  return japaneseCount * 2 >= asciiAlphaCount ? 'ja' : ''
142
162
  }
143
- const sourceMayNeedPreferredLanguages = (state) => {
144
- const src = state && typeof state.src === 'string' ? state.src : ''
145
- return src.indexOf('![') !== -1
146
- }
147
163
  const resolvePreferredLanguagesForState = (state, opt) => {
148
164
  const availableLanguages = (
149
165
  opt &&
@@ -152,10 +168,7 @@ const resolvePreferredLanguagesForState = (state, opt) => {
152
168
  ) ? opt.markRegState.languages : []
153
169
  if (availableLanguages.length === 0) return []
154
170
 
155
- const optionLanguages = opt && Array.isArray(opt.normalizedOptionLanguages)
156
- ? opt.normalizedOptionLanguages
157
- : []
158
- const baseLanguages = optionLanguages.length > 0 ? optionLanguages : availableLanguages
171
+ const baseLanguages = availableLanguages
159
172
  const env = state && state.env ? state.env : null
160
173
 
161
174
  const envLocale = normalizeLanguageCode(env && env.locale)
@@ -199,13 +212,9 @@ const needsPreferredLanguagesResolution = (opt) => {
199
212
  }
200
213
  const normalizeOptionalClassName = (value) => {
201
214
  if (value === null || value === undefined) return ''
202
- const normalized = String(value).trim()
203
- return normalized || ''
204
- }
205
- const buildClassPrefix = (value) => {
206
- const normalized = normalizeOptionalClassName(value)
207
- return normalized ? normalized + '-' : ''
215
+ return String(value).trim()
208
216
  }
217
+ const buildClassPrefix = (normalized) => normalized ? normalized + '-' : ''
209
218
  const normalizeClassOptionWithFallback = (value, fallbackValue) => {
210
219
  const normalized = normalizeOptionalClassName(value)
211
220
  return normalized || fallbackValue
@@ -287,10 +296,11 @@ const parseImageAttrs = (raw) => {
287
296
  for (let i = 0; i < parts.length; i++) {
288
297
  let entry = parts[i]
289
298
  if (!entry) continue
290
- if (classAttrReg.test(entry)) {
291
- entry = entry.replace(classAttrReg, 'class=')
292
- } else if (idAttrReg.test(entry)) {
293
- entry = entry.replace(idAttrReg, 'id=')
299
+ const firstCode = entry.charCodeAt(0)
300
+ if (firstCode === 0x2e) {
301
+ entry = 'class=' + entry.slice(1)
302
+ } else if (firstCode === 0x23) {
303
+ entry = 'id=' + entry.slice(1)
294
304
  }
295
305
  const equalIndex = entry.indexOf('=')
296
306
  if (equalIndex === -1) {
@@ -305,22 +315,50 @@ const parseImageAttrs = (raw) => {
305
315
  return attrs
306
316
  }
307
317
 
308
- const normalizeAutoLabelNumberSets = (value) => {
309
- const normalized = { img: false, table: false }
310
- if (!value) return normalized
311
- if (Array.isArray(value)) {
312
- for (const entry of value) {
313
- if (entry === 'img' || entry === 'table') normalized[entry] = true
314
- }
315
- return normalized
316
- }
317
- return normalized
318
- }
319
-
320
- const shouldApplyLabelNumbering = (captionType, opt) => {
321
- const setting = opt.autoLabelNumberSets
322
- if (!setting) return false
323
- return !!setting[captionType]
318
+ const getEnabledCaptionNumberingMarks = (sets, opt) => {
319
+ if (!Array.isArray(sets) || sets.length === 0) return []
320
+ const exceptMarks = opt && Array.isArray(opt.removeUnnumberedLabelExceptMarks)
321
+ ? opt.removeUnnumberedLabelExceptMarks
322
+ : []
323
+ if (!opt || !opt.removeUnnumberedLabel) return sets
324
+ return sets.filter(mark => exceptMarks.indexOf(mark) !== -1)
325
+ }
326
+
327
+ const createFigureCaptionNumberingPolicy = (enabledMarks, advancedPolicy, markRegState) => {
328
+ if (enabledMarks.length === 0) return null
329
+ const resolveCounterKey = createFigureCaptionCounterKeyResolverFromMarkRegState(markRegState)
330
+ const getCounterKey = ({ captionDecision }) => resolveCounterKey(captionDecision)
331
+ if (!advancedPolicy) {
332
+ return createCaptionNumberingPolicy({
333
+ enabledMarks,
334
+ explicitCounter: 'max',
335
+ generatedNumberHasNumClass: false,
336
+ getCounterKey,
337
+ })
338
+ }
339
+ return createCaptionNumberingPolicy({
340
+ enabledMarks,
341
+ explicitCounter: 'max',
342
+ generatedNumberHasNumClass: false,
343
+ getCounterKey,
344
+ getSequenceKey({ captionContext }) {
345
+ return getCaptionNumberingContext(captionContext).sequenceKey
346
+ },
347
+ // p-captions resolves getSequenceKey first, so the following callbacks can
348
+ // reuse that already-branded context without a second WeakSet lookup.
349
+ parseExplicitNumber({ number, captionContext }) {
350
+ return parseFigureCaptionExplicitNumberUnchecked(
351
+ number,
352
+ captionContext && captionContext.numbering,
353
+ )
354
+ },
355
+ formatGeneratedNumber({ sequence, captionContext }) {
356
+ return formatFigureCaptionGeneratedNumberUnchecked(
357
+ sequence,
358
+ captionContext && captionContext.numbering,
359
+ )
360
+ },
361
+ })
324
362
  }
325
363
 
326
364
  const isOnlySpacesText = (token) => {
@@ -422,7 +460,24 @@ const buildCaptionWithFallback = (text, fallbackOption, mark, markRegState, pref
422
460
  return label + getFallbackStringLabelJoint(label) + trimmedText
423
461
  }
424
462
 
463
+ const resolveCaptionPreferredLanguages = (captionState, opt) => {
464
+ const renderState = captionState.preferredLanguageState
465
+ if (!renderState) return captionState.preferredLanguages
466
+ captionState.preferredLanguages = resolvePreferredLanguagesForState(renderState, opt)
467
+ captionState.preferredLanguageState = null
468
+ return captionState.preferredLanguages
469
+ }
470
+
425
471
  const validateFallbackCaptionLabelOption = (optionName, fallbackOption, markRegState) => {
472
+ if (
473
+ fallbackOption !== undefined &&
474
+ fallbackOption !== null &&
475
+ fallbackOption !== false &&
476
+ fallbackOption !== true &&
477
+ typeof fallbackOption !== 'string'
478
+ ) {
479
+ throw new TypeError(`${optionName} must be false, true, a string label, or null.`)
480
+ }
426
481
  if (typeof fallbackOption !== 'string') return
427
482
  const sampleCaption = buildCaptionWithFallback('caption', fallbackOption, 'img', markRegState, null)
428
483
  const analysis = analyzeCaptionStart(sampleCaption, {
@@ -448,103 +503,6 @@ const createAutoCaptionParagraph = (captionText, TokenConstructor) => {
448
503
  return [paragraphOpen, inlineToken, paragraphClose]
449
504
  }
450
505
 
451
- const getCaptionInlineToken = (tokens, range, caption) => {
452
- if (caption.isPrev) {
453
- const inlineIndex = range.start - 2
454
- if (inlineIndex >= 0) return tokens[inlineIndex]
455
- } else if (caption.isNext) {
456
- return tokens[range.end + 2]
457
- }
458
- return null
459
- }
460
-
461
- const hasClassName = (classAttr, className) => {
462
- if (!classAttr || !className) return false
463
- let index = 0
464
- while (index < classAttr.length) {
465
- index = classAttr.indexOf(className, index)
466
- if (index === -1) return false
467
- const end = index + className.length
468
- const beforeBoundary = index === 0 || classAttr.charCodeAt(index - 1) <= 0x20
469
- const afterBoundary = end >= classAttr.length || classAttr.charCodeAt(end) <= 0x20
470
- if (beforeBoundary && afterBoundary) return true
471
- index = end
472
- }
473
- return false
474
- }
475
-
476
- const hasAnyClassName = (classAttr, classNames) => {
477
- for (let i = 0; i < classNames.length; i++) {
478
- if (hasClassName(classAttr, classNames[i])) return true
479
- }
480
- return false
481
- }
482
-
483
- const getInlineLabelTextToken = (inlineToken, type, opt) => {
484
- if (!inlineToken || !inlineToken.children) return null
485
- const children = inlineToken.children
486
- const classNames = opt.labelClassLookup[type] || opt.labelClassLookup.default
487
- for (let i = 0; i < children.length; i++) {
488
- const child = children[i]
489
- if (!child || !child.attrs) continue
490
- const classAttr = getTokenAttr(child, 'class')
491
- if (!classAttr) continue
492
- if (!hasAnyClassName(classAttr, classNames)) continue
493
- const textToken = children[i + 1]
494
- if (textToken && textToken.type === 'text') {
495
- return textToken
496
- }
497
- }
498
- return null
499
- }
500
-
501
- const updateInlineTokenContent = (inlineToken, originalText, newText) => {
502
- if (!inlineToken || typeof inlineToken.content !== 'string') return
503
- if (!originalText) return
504
- const index = inlineToken.content.indexOf(originalText)
505
- if (index === -1) return
506
- inlineToken.content =
507
- inlineToken.content.slice(0, index) +
508
- newText +
509
- inlineToken.content.slice(index + originalText.length)
510
- }
511
-
512
- const parsePositiveInteger = (text) => {
513
- if (typeof text !== 'string' || text.length === 0) return null
514
- let value = 0
515
- for (let i = 0; i < text.length; i++) {
516
- const code = text.charCodeAt(i)
517
- if (code < 0x30 || code > 0x39) return null
518
- value = value * 10 + code - 0x30
519
- }
520
- return value
521
- }
522
-
523
- const ensureAutoFigureNumbering = (tokens, range, caption, figureNumberState, sp, opt) => {
524
- const captionType = caption.name === 'img' ? 'img' : (caption.name === 'table' ? 'table' : '')
525
- if (!captionType) return
526
- if (!shouldApplyLabelNumbering(captionType, opt)) return
527
- const inlineToken = getCaptionInlineToken(tokens, range, caption)
528
- if (!inlineToken) return
529
- const labelTextToken = getInlineLabelTextToken(inlineToken, captionType, opt)
530
- if (!labelTextToken || typeof labelTextToken.content !== 'string') return
531
- const originalText = labelTextToken.content
532
- if (sp && sp.captionDecision && sp.captionDecision.hasExplicitNumber) {
533
- const explicitValue = parsePositiveInteger(sp.captionDecision.number)
534
- if (explicitValue !== null && explicitValue > (figureNumberState[captionType] || 0)) {
535
- figureNumberState[captionType] = explicitValue
536
- }
537
- return
538
- }
539
- figureNumberState[captionType] = (figureNumberState[captionType] || 0) + 1
540
- const baseLabel = originalText.trim()
541
- if (!baseLabel) return
542
- const joint = asciiLabelReg.test(baseLabel) ? ' ' : ''
543
- const newLabelText = baseLabel + joint + figureNumberState[captionType]
544
- labelTextToken.content = newLabelText
545
- updateInlineTokenContent(inlineToken, originalText, newLabelText)
546
- }
547
-
548
506
  const matchAutoCaptionText = (text, opt, preferredMark = 'img') => {
549
507
  if (!text || !opt || !opt.markRegState) return ''
550
508
  const trimmed = text.trim()
@@ -557,7 +515,7 @@ const matchAutoCaptionText = (text, opt, preferredMark = 'img') => {
557
515
  return ''
558
516
  }
559
517
 
560
- const getAutoCaptionFromImage = (imageToken, opt, preferredLanguages) => {
518
+ const getAutoCaptionFromImage = (imageToken, opt, captionState) => {
561
519
  if (!opt.autoAltCaption && !opt.autoTitleCaption && !(opt.markRegState && opt.markRegState.markReg && opt.markRegState.markReg.img)) return null
562
520
 
563
521
  const altText = getImageAltText(imageToken)
@@ -566,9 +524,10 @@ const getAutoCaptionFromImage = (imageToken, opt, preferredLanguages) => {
566
524
  return { text: caption, source: 'alt' }
567
525
  }
568
526
  if (opt.autoAltCaption) {
569
- const altForFallback = altText || ''
570
- const fallbackCaption = buildCaptionWithFallback(altForFallback, opt.autoAltCaption, 'img', opt.markRegState, preferredLanguages)
571
- caption = fallbackCaption
527
+ const preferredLanguages = opt.autoAltCaption === true
528
+ ? resolveCaptionPreferredLanguages(captionState, opt)
529
+ : captionState.preferredLanguages
530
+ caption = buildCaptionWithFallback(altText, opt.autoAltCaption, 'img', opt.markRegState, preferredLanguages)
572
531
  }
573
532
  if (caption) return { text: caption, source: 'alt' }
574
533
 
@@ -578,9 +537,10 @@ const getAutoCaptionFromImage = (imageToken, opt, preferredLanguages) => {
578
537
  return { text: caption, source: 'title' }
579
538
  }
580
539
  if (opt.autoTitleCaption) {
581
- const titleForFallback = titleText || ''
582
- const fallbackCaption = buildCaptionWithFallback(titleForFallback, opt.autoTitleCaption, 'img', opt.markRegState, preferredLanguages)
583
- caption = fallbackCaption
540
+ const preferredLanguages = opt.autoTitleCaption === true
541
+ ? resolveCaptionPreferredLanguages(captionState, opt)
542
+ : captionState.preferredLanguages
543
+ caption = buildCaptionWithFallback(titleText, opt.autoTitleCaption, 'img', opt.markRegState, preferredLanguages)
584
544
  }
585
545
  return caption ? { text: caption, source: 'title' } : null
586
546
  }
@@ -594,6 +554,33 @@ const consumeAutoCaptionSource = (imageToken, autoCaption) => {
594
554
  }
595
555
  }
596
556
 
557
+ const setFigureCaptionParagraph = (index, captionState, caption, sp, opt) => {
558
+ const needsCaptionDrivenClassBeforeApply = !!(
559
+ opt.labelClassFollowsFigure &&
560
+ caption.name === 'iframe' &&
561
+ !opt.allIframeTypeFigureClassName
562
+ )
563
+ if (!needsCaptionDrivenClassBeforeApply) {
564
+ return setCaptionParagraph(index, captionState, caption, captionState.numberingRuntime, sp, opt)
565
+ }
566
+ const decision = analyzeCaptionParagraph(index, captionState, {
567
+ captionName: caption.name,
568
+ isIframeTypeBlockquote: sp.isIframeTypeBlockquote,
569
+ isVideoIframe: sp.isVideoIframe,
570
+ }, opt)
571
+ if (!decision) return false
572
+ // Figure-following label classes need the final caption-driven wrapper class
573
+ // before p-captions constructs the label/body spans.
574
+ applyCaptionDrivenFigureClass(caption, sp, opt, decision)
575
+ return applyCaptionParagraph(
576
+ decision,
577
+ captionState,
578
+ sp,
579
+ captionState.numberingRuntime,
580
+ opt,
581
+ )
582
+ }
583
+
597
584
  const checkPrevCaption = (tokens, n, caption, sp, opt, captionState) => {
598
585
  if (n < 3) return
599
586
  const captionStartToken = tokens[n - 3]
@@ -601,7 +588,7 @@ const checkPrevCaption = (tokens, n, caption, sp, opt, captionState) => {
601
588
  const captionEndToken = tokens[n - 1]
602
589
  if (captionStartToken === undefined || captionEndToken === undefined) return
603
590
  if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
604
- setCaptionParagraph(n - 3, captionState, caption, null, sp, opt)
591
+ setFigureCaptionParagraph(n - 3, captionState, caption, sp, opt)
605
592
  const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
606
593
  if (!captionName) {
607
594
  if (opt.labelPrefixMarkerWithoutLabelPrevReg) {
@@ -624,7 +611,7 @@ const checkNextCaption = (tokens, en, caption, sp, opt, captionState) => {
624
611
  const captionEndToken = tokens[en + 3]
625
612
  if (captionStartToken === undefined || captionEndToken === undefined) return
626
613
  if (captionStartToken.type !== 'paragraph_open' || captionEndToken.type !== 'paragraph_close') return
627
- setCaptionParagraph(en + 1, captionState, caption, null, sp, opt)
614
+ setFigureCaptionParagraph(en + 1, captionState, caption, sp, opt)
628
615
  const captionName = sp && sp.captionDecision ? sp.captionDecision.mark : ''
629
616
  if (!captionName) {
630
617
  if (opt.labelPrefixMarkerWithoutLabelNextReg) {
@@ -685,24 +672,26 @@ const resolveFigureClassName = (checkTokenTagName, sp, opt) => {
685
672
  return className
686
673
  }
687
674
 
688
- const applyCaptionDrivenFigureClass = (caption, sp, opt) => {
675
+ const applyCaptionDrivenFigureClass = (caption, sp, opt, decision = sp && sp.captionDecision) => {
689
676
  if (!sp) return
690
677
  const figureClassForSlides = opt.figureClassThatWrapsSlides
691
678
  if (!figureClassForSlides) return
692
- const detectedMark = (sp.captionDecision && sp.captionDecision.mark) || (caption && caption.name) || ''
679
+ const detectedMark = (decision && decision.mark) || (caption && caption.name) || ''
693
680
  if (detectedMark !== 'slide') return
694
681
  if (opt.allIframeTypeFigureClassName && sp.figureClassName === opt.allIframeTypeFigureClassName) return
682
+ if (sp.figureClassName === figureClassForSlides) return
695
683
  sp.figureClassName = figureClassForSlides
696
684
  }
697
685
 
698
-
699
- const changePrevCaptionPosition = (tokens, n, caption, opt) => {
700
- const captionStartToken = tokens[n-3]
701
- const captionInlineToken = tokens[n-2]
702
- const captionEndToken = tokens[n-1]
703
- const figureBaseLevel = getTokenLevel(tokens[n])
704
-
705
- cleanCaptionTokenAttrs(captionStartToken, caption.name, opt)
686
+ const convertCaptionTokens = (
687
+ captionStartToken,
688
+ captionInlineToken,
689
+ captionEndToken,
690
+ figureBaseLevel,
691
+ captionName,
692
+ opt,
693
+ ) => {
694
+ cleanCaptionTokenAttrs(captionStartToken, captionName, opt)
706
695
  captionStartToken.type = 'figcaption_open'
707
696
  captionStartToken.tag = 'figcaption'
708
697
  captionStartToken.block = true
@@ -712,31 +701,61 @@ const changePrevCaptionPosition = (tokens, n, caption, opt) => {
712
701
  captionEndToken.tag = 'figcaption'
713
702
  captionEndToken.block = true
714
703
  captionEndToken.level = figureBaseLevel + 1
715
- tokens.splice(n + 2, 0, captionStartToken, captionInlineToken, captionEndToken)
716
- tokens.splice(n-3, 3)
704
+ }
705
+
706
+ const changePrevCaptionPosition = (tokens, n, caption, opt) => {
707
+ const captionStartToken = tokens[n-3]
708
+ const captionInlineToken = tokens[n-2]
709
+ const captionEndToken = tokens[n-1]
710
+ const figureStartToken = tokens[n]
711
+ const figureStartPaddingToken = tokens[n+1]
712
+ convertCaptionTokens(
713
+ captionStartToken,
714
+ captionInlineToken,
715
+ captionEndToken,
716
+ getTokenLevel(tokens[n]),
717
+ caption.name,
718
+ opt,
719
+ )
720
+ tokens[n-3] = figureStartToken
721
+ tokens[n-2] = figureStartPaddingToken
722
+ tokens[n-1] = captionStartToken
723
+ tokens[n] = captionInlineToken
724
+ tokens[n+1] = captionEndToken
717
725
  }
718
726
 
719
727
  const changeNextCaptionPosition = (tokens, en, caption, opt) => {
728
+ const figureEndToken = tokens[en]
720
729
  const captionStartToken = tokens[en+1]
721
730
  const captionInlineToken = tokens[en+2]
722
731
  const captionEndToken = tokens[en+3]
723
- const figureBaseLevel = getTokenLevel(tokens[en])
724
- cleanCaptionTokenAttrs(captionStartToken, caption.name, opt)
725
- captionStartToken.type = 'figcaption_open'
726
- captionStartToken.tag = 'figcaption'
727
- captionStartToken.block = true
728
- captionStartToken.level = figureBaseLevel + 1
729
- captionInlineToken.level = figureBaseLevel + 2
730
- captionEndToken.type = 'figcaption_close'
731
- captionEndToken.tag = 'figcaption'
732
- captionEndToken.block = true
733
- captionEndToken.level = figureBaseLevel + 1
734
- tokens.splice(en, 0, captionStartToken, captionInlineToken, captionEndToken)
735
- tokens.splice(en+4, 3)
732
+ convertCaptionTokens(
733
+ captionStartToken,
734
+ captionInlineToken,
735
+ captionEndToken,
736
+ getTokenLevel(tokens[en]),
737
+ caption.name,
738
+ opt,
739
+ )
740
+ tokens[en] = captionStartToken
741
+ tokens[en+1] = captionInlineToken
742
+ tokens[en+2] = captionEndToken
743
+ tokens[en+3] = figureEndToken
736
744
  }
737
745
 
738
746
  const getTokenMap = (token) => {
739
- return token && Array.isArray(token.map) && token.map.length === 2 ? token.map : null
747
+ if (!token || !Array.isArray(token.map) || token.map.length !== 2) return null
748
+ const startLine = token.map[0]
749
+ const endLine = token.map[1]
750
+ if (
751
+ !Number.isSafeInteger(startLine) ||
752
+ !Number.isSafeInteger(endLine) ||
753
+ startLine < 0 ||
754
+ endLine < startLine
755
+ ) {
756
+ return null
757
+ }
758
+ return token.map
740
759
  }
741
760
 
742
761
  const findNearestMapInRange = (tokens, start, end, step) => {
@@ -755,9 +774,6 @@ const getRangeMap = (tokens, start, end) => {
755
774
  const endMap = getTokenMap(tokens[end]) || findNearestMapInRange(tokens, end, start, -1) || startMap
756
775
  const startLine = startMap[0]
757
776
  const endLine = Math.max(startMap[1], endMap[1])
758
- if (typeof startLine !== 'number' || typeof endLine !== 'number' || endLine < startLine) {
759
- return [startMap[0], startMap[1]]
760
- }
761
777
  return [startLine, endLine]
762
778
  }
763
779
 
@@ -775,14 +791,27 @@ const adjustTokenLevels = (tokens, start, end, delta) => {
775
791
  }
776
792
  }
777
793
 
794
+ const createTextToken = (TokenConstructor, content, level) => {
795
+ const token = new TokenConstructor('text', '', 0)
796
+ token.content = content
797
+ token.level = level
798
+ return token
799
+ }
800
+
801
+ const joinTokenAttrs = (token, attrs) => {
802
+ if (!attrs || attrs.length === 0) return
803
+ for (let i = 0; i < attrs.length; i++) {
804
+ token.attrJoin(attrs[i][0], attrs[i][1])
805
+ }
806
+ }
807
+
778
808
  const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInsteadOfWrap, sp, opt, TokenConstructor) => {
779
809
  let n = range.start
780
810
  let en = range.end
781
811
  const baseLevel = getTokenLevel(tokens[n])
782
812
  const childLevel = baseLevel + 1
783
813
  const figureStartToken = new TokenConstructor('figure_open', 'figure', 1)
784
- const figureClassName = sp.figureClassName || resolveFigureClassName(checkTokenTagName, sp, opt)
785
- figureStartToken.attrSet('class', figureClassName)
814
+ figureStartToken.attrSet('class', sp.figureClassName)
786
815
  figureStartToken.block = true
787
816
  figureStartToken.level = baseLevel
788
817
 
@@ -797,39 +826,40 @@ const wrapWithFigure = (tokens, range, checkTokenTagName, caption, replaceInstea
797
826
  figureStartToken.map = [rangeMap[0], rangeMap[1]]
798
827
  figureEndToken.map = [rangeMap[0], rangeMap[1]]
799
828
  }
800
- const createBreakToken = () => {
801
- const breakToken = new TokenConstructor('text', '', 0)
802
- breakToken.content = '\n'
803
- breakToken.level = childLevel
804
- return breakToken
805
- }
806
- const createEmptyTextToken = () => {
807
- const emptyToken = new TokenConstructor('text', '', 0)
808
- emptyToken.content = ''
809
- emptyToken.level = childLevel
810
- return emptyToken
811
- }
812
829
  if (caption.name === 'img') {
813
- const joinAttrs = (attrs) => {
814
- if (!attrs || attrs.length === 0) return
815
- for (let i = 0; i < attrs.length; i++) {
816
- const attr = attrs[i]
817
- figureStartToken.attrJoin(attr[0], attr[1])
818
- }
819
- }
820
830
  // `styleProcess` should keep working even when markdown-it-attrs is absent.
821
- if (opt.styleProcess) joinAttrs(sp.attrs)
831
+ if (opt.styleProcess) joinTokenAttrs(figureStartToken, sp.attrs)
822
832
  // Forward attrs already materialized by markdown-it-attrs on the image paragraph.
823
- joinAttrs(tokens[n].attrs)
833
+ joinTokenAttrs(figureStartToken, tokens[n].attrs)
824
834
  }
825
835
  if (replaceInsteadOfWrap) {
826
- tokens.splice(en, 1, createBreakToken(), figureEndToken)
827
- tokens.splice(n, 1, figureStartToken, createEmptyTextToken())
836
+ const contentToken = tokens[n + 1]
837
+ tokens.splice(
838
+ n,
839
+ en - n + 1,
840
+ figureStartToken,
841
+ createTextToken(TokenConstructor, '', childLevel),
842
+ contentToken,
843
+ createTextToken(TokenConstructor, '\n', childLevel),
844
+ figureEndToken,
845
+ )
828
846
  en = en + 2
847
+ } else if (n === en) {
848
+ const contentToken = tokens[n]
849
+ adjustTokenLevels(tokens, n, en, 1)
850
+ tokens.splice(
851
+ n,
852
+ 1,
853
+ figureStartToken,
854
+ createTextToken(TokenConstructor, '', childLevel),
855
+ contentToken,
856
+ figureEndToken,
857
+ )
858
+ en = en + 3
829
859
  } else {
830
860
  adjustTokenLevels(tokens, n, en, 1)
831
861
  tokens.splice(en+1, 0, figureEndToken)
832
- tokens.splice(n, 0, figureStartToken, createEmptyTextToken())
862
+ tokens.splice(n, 0, figureStartToken, createTextToken(TokenConstructor, '', childLevel))
833
863
  en = en + 3
834
864
  }
835
865
  range.start = n
@@ -842,20 +872,6 @@ const checkCaption = (tokens, n, en, caption, sp, opt, captionState) => {
842
872
  checkNextCaption(tokens, en, caption, sp, opt, captionState)
843
873
  }
844
874
 
845
- const getNestedContainerType = (token) => {
846
- if (!token) return null
847
- switch (token.type) {
848
- case 'blockquote_open':
849
- return 'blockquote'
850
- case 'list_item_open':
851
- return 'list_item'
852
- case 'dd_open':
853
- return 'dd'
854
- default:
855
- return null
856
- }
857
- }
858
-
859
875
  const resetRangeState = (range, start) => {
860
876
  range.start = start
861
877
  range.end = start
@@ -874,6 +890,7 @@ const resetSpecialState = (sp) => {
874
890
  sp.isIframeTypeBlockquote = false
875
891
  sp.figureClassName = ''
876
892
  sp.captionDecision = null
893
+ sp.numbering = null
877
894
  }
878
895
 
879
896
  const findClosingTokenIndex = (tokens, startIndex, tag) => {
@@ -890,19 +907,20 @@ const findClosingTokenIndex = (tokens, startIndex, tag) => {
890
907
  }
891
908
  i++
892
909
  }
893
- return startIndex
910
+ return -1
894
911
  }
895
912
 
896
913
  const detectCheckTypeOpen = (tokens, token, n, caption, baseType) => {
897
914
  if (!token || !baseType) return null
898
915
  if (n > 1 && tokens[n - 2] && tokens[n - 2].type === 'figure_open') return null
899
- let tagName = token.tag
916
+ const tagName = token.tag
900
917
  caption.name = baseType
901
918
  if (baseType === 'pre') {
902
919
  if (tokens[n + 1] && tokens[n + 1].tag === 'code') caption.name = 'pre-code'
903
920
  if (tokens[n + 1] && tokens[n + 1].tag === 'samp') caption.name = 'pre-samp'
904
921
  }
905
922
  const en = findClosingTokenIndex(tokens, n, tagName)
923
+ if (en < 0) return null
906
924
  return {
907
925
  type: 'block',
908
926
  tagName,
@@ -915,11 +933,8 @@ const detectCheckTypeOpen = (tokens, token, n, caption, baseType) => {
915
933
 
916
934
  const detectFenceToken = (token, n, caption) => {
917
935
  if (!token || token.type !== 'fence' || token.tag !== 'code' || !token.block) return null
918
- let tagName = 'pre-code'
919
- if (sampLangReg.test(token.info)) {
920
- token.tag = 'samp'
921
- tagName = 'pre-samp'
922
- }
936
+ const useSampTag = sampLangReg.test(token.info)
937
+ const tagName = useSampTag ? 'pre-samp' : 'pre-code'
923
938
  caption.name = tagName
924
939
  return {
925
940
  type: 'fence',
@@ -928,6 +943,8 @@ const detectFenceToken = (token, n, caption) => {
928
943
  replaceInsteadOfWrap: false,
929
944
  wrapWithoutCaption: false,
930
945
  canWrap: true,
946
+ token,
947
+ useSampTag,
931
948
  }
932
949
  }
933
950
 
@@ -1056,28 +1073,37 @@ const applyImageParagraphTransform = (detection) => {
1056
1073
  }
1057
1074
  }
1058
1075
 
1059
- const figureWithCaption = (state, opt) => {
1060
- const figureNumberState = {
1061
- img: 0,
1062
- table: 0,
1076
+ const applyWrappedCandidateTransform = (detection) => {
1077
+ if (detection.type === 'image') {
1078
+ applyImageParagraphTransform(detection)
1079
+ } else if (detection.type === 'fence' && detection.useSampTag) {
1080
+ detection.token.tag = 'samp'
1063
1081
  }
1082
+ }
1064
1083
 
1084
+ const figureWithCaption = (state, opt) => {
1085
+ const numberingRuntime = opt.captionNumberingPolicy
1086
+ ? createCaptionNumberingRuntime(opt.captionNumberingPolicy, { env: state.env })
1087
+ : null
1088
+ const numberingScopeState = numberingRuntime
1089
+ ? createFigureCaptionScopeRuntime(
1090
+ state,
1091
+ opt.normalizedAutoLabelNumberPolicy,
1092
+ opt.unscopedNumberingContext,
1093
+ )
1094
+ : null
1065
1095
  const captionState = {
1066
1096
  tokens: state.tokens,
1067
1097
  Token: state.Token,
1068
1098
  preferredLanguages: opt.preferredLanguages,
1099
+ numberingRuntime,
1100
+ numberingScopeState,
1069
1101
  }
1070
- const shouldResolvePreferredLanguages = !!(
1071
- opt.shouldResolvePreferredLanguages &&
1072
- sourceMayNeedPreferredLanguages(state)
1073
- )
1074
- if (shouldResolvePreferredLanguages) {
1075
- captionState.preferredLanguages = resolvePreferredLanguagesForState(state, opt)
1076
- }
1077
- figureWithCaptionCore(state.tokens, opt, figureNumberState, state.Token, captionState, null, 0)
1102
+ if (opt.shouldResolvePreferredLanguages) captionState.preferredLanguageState = state
1103
+ figureWithCaptionCore(state.tokens, opt, state.Token, captionState, null, 0)
1078
1104
  }
1079
1105
 
1080
- const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor, captionState, parentType = null, startIndex = 0) => {
1106
+ const figureWithCaptionCore = (tokens, opt, TokenConstructor, captionState, parentType = null, startIndex = 0) => {
1081
1107
  const rRange = { start: startIndex, end: startIndex }
1082
1108
  const rCaption = {
1083
1109
  name: '', nameSuffix: '', isPrev: false, isNext: false
@@ -1087,21 +1113,31 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
1087
1113
  isVideoIframe: false,
1088
1114
  isIframeTypeBlockquote: false,
1089
1115
  figureClassName: '',
1090
- captionDecision: null
1091
- }
1116
+ captionDecision: null,
1117
+ numbering: null,
1118
+ }
1119
+ const numberingScopeState = parentType ? null : captionState.numberingScopeState
1120
+ const headingScopeState = numberingScopeState &&
1121
+ !numberingScopeState.fixed &&
1122
+ numberingScopeState.scopeConfig.usesHeading
1123
+ ? numberingScopeState
1124
+ : null
1125
+ const parentCloseType = parentType ? parentType + '_close' : ''
1092
1126
  let n = startIndex
1093
1127
  while (n < tokens.length) {
1094
1128
  const token = tokens[n]
1095
- const containerType = getNestedContainerType(token)
1129
+ if (headingScopeState && token.type === 'heading_open') {
1130
+ updateFigureCaptionScopeFromHeading(tokens, n, headingScopeState)
1131
+ }
1132
+ const containerType = getFigureCaptionScopeNestedContainerType(token)
1096
1133
 
1097
1134
  if (containerType && containerType !== 'blockquote') {
1098
- const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, n + 1)
1099
- n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
1135
+ const closeIndex = figureWithCaptionCore(tokens, opt, TokenConstructor, captionState, containerType, n + 1)
1136
+ n = closeIndex + 1
1100
1137
  continue
1101
1138
  }
1102
1139
 
1103
-
1104
- if (parentType && token.type === `${parentType}_close`) {
1140
+ if (parentCloseType && token.type === parentCloseType) {
1105
1141
  return n
1106
1142
  }
1107
1143
 
@@ -1140,8 +1176,8 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
1140
1176
 
1141
1177
  if (!detection) {
1142
1178
  if (containerType === 'blockquote') {
1143
- const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, n + 1)
1144
- n = (typeof closeIndex === 'number' ? closeIndex : n) + 1
1179
+ const closeIndex = figureWithCaptionCore(tokens, opt, TokenConstructor, captionState, containerType, n + 1)
1180
+ n = closeIndex + 1
1145
1181
  } else {
1146
1182
  n++
1147
1183
  }
@@ -1150,61 +1186,77 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
1150
1186
 
1151
1187
  rRange.end = detection.en
1152
1188
 
1189
+ if (detection.canWrap === false || (detection.type === 'image' && token.hidden === true)) {
1190
+ let nextIndex = rRange.end + 1
1191
+ if (containerType === 'blockquote') {
1192
+ const closeIndex = figureWithCaptionCore(tokens, opt, TokenConstructor, captionState, containerType, rRange.start + 1)
1193
+ nextIndex = Math.max(nextIndex, closeIndex + 1)
1194
+ }
1195
+ n = nextIndex
1196
+ continue
1197
+ }
1198
+
1199
+ if (detection.type === 'html') {
1200
+ rRange.end = applyHtmlFigureTransform(tokens, detection)
1201
+ }
1202
+
1203
+ if (captionState.numberingRuntime) {
1204
+ rSp.numbering = captionState.numberingScopeState
1205
+ ? captionState.numberingScopeState.currentContext
1206
+ : opt.unscopedNumberingContext
1207
+ }
1153
1208
  rSp.figureClassName = resolveFigureClassName(detection.tagName, rSp, opt)
1154
1209
  checkCaption(tokens, rRange.start, rRange.end, rCaption, rSp, opt, captionState)
1155
- applyCaptionDrivenFigureClass(rCaption, rSp, opt)
1156
1210
 
1157
1211
  let hasCaption = rCaption.isPrev || rCaption.isNext
1212
+ if (hasCaption) applyCaptionDrivenFigureClass(rCaption, rSp, opt)
1158
1213
  let pendingAutoCaption = null
1159
1214
  if (!hasCaption && detection.type === 'image' && opt.autoCaptionDetection) {
1160
- pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt, captionState.preferredLanguages)
1215
+ pendingAutoCaption = getAutoCaptionFromImage(detection.imageToken, opt, captionState)
1161
1216
  if (pendingAutoCaption) {
1162
1217
  hasCaption = true
1163
1218
  }
1164
1219
  }
1165
1220
 
1166
- if (detection.canWrap === false) {
1167
- let nextIndex = rRange.end + 1
1168
- if (containerType === 'blockquote') {
1169
- const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, rRange.start + 1)
1170
- nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : rRange.end) + 1)
1171
- }
1172
- n = nextIndex
1173
- continue
1174
- }
1175
-
1176
1221
  let shouldWrap = hasCaption
1177
1222
  if (detection.type === 'html' || detection.type === 'image') {
1178
1223
  shouldWrap = shouldWrap || detection.wrapWithoutCaption
1179
1224
  }
1180
- if (detection.type === 'image' && token.hidden === true) {
1181
- shouldWrap = false
1182
- }
1183
-
1184
- if (shouldWrap) {
1185
- if (pendingAutoCaption) {
1186
- consumeAutoCaptionSource(detection.imageToken, pendingAutoCaption)
1187
- const captionTokens = createAutoCaptionParagraph(pendingAutoCaption.text, TokenConstructor)
1188
- tokens.splice(rRange.start, 0, ...captionTokens)
1189
- const insertedLength = captionTokens.length
1190
- rRange.start += insertedLength
1191
- rRange.end += insertedLength
1192
- n += insertedLength
1225
+ if (pendingAutoCaption) {
1226
+ const captionTokens = createAutoCaptionParagraph(pendingAutoCaption.text, TokenConstructor)
1227
+ const insertIndex = rRange.start
1228
+ const insertedLength = captionTokens.length
1229
+ tokens.splice(insertIndex, 0, ...captionTokens)
1230
+ rRange.start += insertedLength
1231
+ rRange.end += insertedLength
1232
+ n += insertedLength
1233
+ try {
1193
1234
  checkCaption(tokens, rRange.start, rRange.end, rCaption, rSp, opt, captionState)
1235
+ } catch (error) {
1236
+ tokens.splice(insertIndex, insertedLength)
1237
+ throw error
1238
+ }
1239
+ if (rCaption.isPrev || rCaption.isNext) {
1240
+ consumeAutoCaptionSource(detection.imageToken, pendingAutoCaption)
1194
1241
  applyCaptionDrivenFigureClass(rCaption, rSp, opt)
1242
+ } else {
1243
+ tokens.splice(insertIndex, insertedLength)
1244
+ rRange.start -= insertedLength
1245
+ rRange.end -= insertedLength
1246
+ n -= insertedLength
1247
+ shouldWrap = detection.wrapWithoutCaption
1248
+ }
1249
+ }
1250
+ if (shouldWrap) {
1251
+ if (detection.type !== 'html') {
1252
+ applyWrappedCandidateTransform(detection)
1195
1253
  }
1196
- applyImageParagraphTransform(detection)
1197
- ensureAutoFigureNumbering(tokens, rRange, rCaption, figureNumberState, rSp, opt)
1198
1254
  wrapWithFigure(tokens, rRange, detection.tagName, rCaption, detection.replaceInsteadOfWrap, rSp, opt, TokenConstructor)
1199
1255
  }
1200
1256
 
1201
1257
  let nextIndex
1202
1258
  if (!rCaption.isPrev && !rCaption.isNext) {
1203
- if (shouldWrap && detection.type === 'html') {
1204
- nextIndex = rRange.end + 1
1205
- } else {
1206
- nextIndex = n + 1
1207
- }
1259
+ nextIndex = shouldWrap ? rRange.end + 1 : n + 1
1208
1260
  } else {
1209
1261
  const en = rRange.end
1210
1262
  if (rCaption.isPrev) {
@@ -1217,9 +1269,16 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
1217
1269
  }
1218
1270
 
1219
1271
  if (containerType === 'blockquote') {
1220
- const closeIndex = figureWithCaptionCore(tokens, opt, figureNumberState, TokenConstructor, captionState, containerType, rRange.start + 1)
1221
- const fallbackIndex = rCaption.name ? rRange.end : n
1222
- nextIndex = Math.max(nextIndex, (typeof closeIndex === 'number' ? closeIndex : fallbackIndex) + 1)
1272
+ const nestedContentStart = rRange.start + (shouldWrap ? 3 : 1)
1273
+ const closeIndex = figureWithCaptionCore(
1274
+ tokens,
1275
+ opt,
1276
+ TokenConstructor,
1277
+ captionState,
1278
+ containerType,
1279
+ nestedContentStart,
1280
+ )
1281
+ nextIndex = Math.max(nextIndex, closeIndex + 1)
1223
1282
  }
1224
1283
 
1225
1284
  n = nextIndex
@@ -1228,6 +1287,10 @@ const figureWithCaptionCore = (tokens, opt, figureNumberState, TokenConstructor,
1228
1287
  }
1229
1288
 
1230
1289
  const mditFigureWithPCaption = (md, option) => {
1290
+ if (md[installedKey]) return
1291
+ if (hasOwnOption(option, 'setFigureNumber')) {
1292
+ throw new Error('setFigureNumber is not supported by figure-with-p-caption; use autoLabelNumber or autoLabelNumberSets')
1293
+ }
1231
1294
  const opt = {
1232
1295
  // Caption languages delegated to p-captions.
1233
1296
  languages: ['en', 'ja'],
@@ -1261,7 +1324,8 @@ const mditFigureWithPCaption = (md, option) => {
1261
1324
 
1262
1325
  // --- numbering controls ---
1263
1326
  autoLabelNumber: false, // shorthand for enabling numbering for both img/table unless autoLabelNumberSets is provided explicitly
1264
- autoLabelNumberSets: [], // preferred; supports ['img'], ['table'], or both
1327
+ autoLabelNumberSets: [], // preferred; supports img/table/code/samp/video marks
1328
+ autoLabelNumberPolicy: undefined, // omitted: automatic Chapter/Appendix scope; explicit null: document-wide numbering
1265
1329
 
1266
1330
  // --- caption text formatting (delegated to p7d-markdown-it-p-captions) ---
1267
1331
  hasNumClass: false,
@@ -1282,10 +1346,12 @@ const mditFigureWithPCaption = (md, option) => {
1282
1346
  const hasExplicitFigureClassThatWrapsIframeTypeBlockquote = hasOwnOption(option, 'figureClassThatWrapsIframeTypeBlockquote')
1283
1347
  const hasExplicitFigureClassThatWrapsSlides = hasOwnOption(option, 'figureClassThatWrapsSlides')
1284
1348
  const hasExplicitLabelClassFollowsFigure = hasOwnOption(option, 'labelClassFollowsFigure')
1285
- if (hasOwnOption(option, 'setFigureNumber')) {
1286
- throw new Error('setFigureNumber is not supported by figure-with-p-caption; use autoLabelNumber or autoLabelNumberSets')
1287
- }
1288
1349
  if (option) Object.assign(opt, option)
1350
+ if (Array.isArray(opt.removeUnnumberedLabelExceptMarks)) {
1351
+ opt.removeUnnumberedLabelExceptMarks = opt.removeUnnumberedLabelExceptMarks.map(
1352
+ canonicalizeCaptionNumberingMark,
1353
+ )
1354
+ }
1289
1355
  opt.imageOnlyParagraphWithoutCaption = hasExplicitImageOnlyParagraphWithoutCaption
1290
1356
  ? !!opt.imageOnlyParagraphWithoutCaption
1291
1357
  : !!opt.oneImageWithoutCaption
@@ -1297,7 +1363,6 @@ const mditFigureWithPCaption = (md, option) => {
1297
1363
  opt.markRegState = getMarkRegStateForLanguages(opt.languages)
1298
1364
  opt.preferredLanguages = normalizePreferredLanguages(opt.preferredLanguages, opt.markRegState.languages)
1299
1365
  if (opt.preferredLanguages.length === 0) opt.preferredLanguages = null
1300
- opt.normalizedOptionLanguages = normalizePreferredLanguages(opt.languages, opt.markRegState.languages)
1301
1366
  opt.shouldResolvePreferredLanguages = needsPreferredLanguagesResolution(opt)
1302
1367
  validateFallbackCaptionLabelOption('autoAltCaption', opt.autoAltCaption, opt.markRegState)
1303
1368
  validateFallbackCaptionLabelOption('autoTitleCaption', opt.autoTitleCaption, opt.markRegState)
@@ -1307,12 +1372,27 @@ const mditFigureWithPCaption = (md, option) => {
1307
1372
  audio: !!opt.audioWithoutCaption,
1308
1373
  iframeTypeBlockquote: !!opt.iframeTypeBlockquoteWithoutCaption,
1309
1374
  }
1310
- // Normalize option shorthands now so downstream logic works with a consistent { img, table } shape.
1311
- opt.autoLabelNumberSets = normalizeAutoLabelNumberSets(opt.autoLabelNumberSets)
1312
- if (opt.autoLabelNumber && !hasExplicitAutoLabelNumberSets) {
1313
- opt.autoLabelNumberSets.img = true
1314
- opt.autoLabelNumberSets.table = true
1315
- }
1375
+ const requestedAutoLabelNumberSets = hasExplicitAutoLabelNumberSets
1376
+ ? option.autoLabelNumberSets
1377
+ : opt.autoLabelNumber
1378
+ ? ['img', 'table']
1379
+ : []
1380
+ opt.autoLabelNumberSets = normalizeAutoLabelNumberSets(requestedAutoLabelNumberSets)
1381
+ const enabledNumberingMarks = getEnabledCaptionNumberingMarks(opt.autoLabelNumberSets, opt)
1382
+ opt.normalizedAutoLabelNumberPolicy = enabledNumberingMarks.length === 0 &&
1383
+ opt.autoLabelNumberPolicy === undefined
1384
+ ? null
1385
+ : normalizeFigureCaptionNumberingPolicy(opt.autoLabelNumberPolicy)
1386
+ opt.captionNumberingPolicy = createFigureCaptionNumberingPolicy(
1387
+ enabledNumberingMarks,
1388
+ opt.normalizedAutoLabelNumberPolicy,
1389
+ opt.markRegState,
1390
+ )
1391
+ opt.unscopedNumberingContext = opt.captionNumberingPolicy && opt.normalizedAutoLabelNumberPolicy
1392
+ ? createUnscopedNumberingContext(
1393
+ getFigureCaptionNumberingPolicyState(opt.normalizedAutoLabelNumberPolicy).separator,
1394
+ )
1395
+ : null
1316
1396
  const classPrefix = buildClassPrefix(opt.classPrefix)
1317
1397
  opt.figureClassPrefix = classPrefix
1318
1398
  opt.captionClassPrefix = classPrefix
@@ -1334,8 +1414,6 @@ const mditFigureWithPCaption = (md, option) => {
1334
1414
  defaultSlideFigureClass,
1335
1415
  )
1336
1416
  }
1337
- // Precompute label-class permutations so numbering lookup doesn't rebuild arrays per caption.
1338
- opt.labelClassLookup = buildLabelClassLookup(opt)
1339
1417
  const markerList = normalizeLabelPrefixMarkers(opt.labelPrefixMarker)
1340
1418
  opt.labelPrefixMarkerReg = buildLabelPrefixMarkerRegFromMarkers(markerList)
1341
1419
  opt.cleanCaptionRegCache = new Map()
@@ -1352,6 +1430,10 @@ const mditFigureWithPCaption = (md, option) => {
1352
1430
  md.core.ruler.before('replacements', 'figure_with_caption', (state) => {
1353
1431
  figureWithCaption(state, opt)
1354
1432
  })
1433
+ Object.defineProperty(md, installedKey, {
1434
+ value: true,
1435
+ configurable: true,
1436
+ })
1355
1437
  }
1356
1438
 
1357
1439
  export default mditFigureWithPCaption