postext 1.15.0 → 1.16.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/dist/__tests__/arabic/harakatProfile.test.d.ts +2 -0
- package/dist/__tests__/arabic/harakatProfile.test.d.ts.map +1 -0
- package/dist/__tests__/arabic/harakatProfile.test.js +105 -0
- package/dist/__tests__/arabic/harakatProfile.test.js.map +1 -0
- package/dist/__tests__/bundleLocaleAndBytes.test.js +9 -9
- package/dist/__tests__/bundleLocaleAndBytes.test.js.map +1 -1
- package/dist/__tests__/chineseLocale.test.js +10 -5
- package/dist/__tests__/chineseLocale.test.js.map +1 -1
- package/dist/__tests__/cjk/annotationsRender.test.js +5 -2
- package/dist/__tests__/cjk/annotationsRender.test.js.map +1 -1
- package/dist/__tests__/cjk/chipTracking.test.d.ts +2 -0
- package/dist/__tests__/cjk/chipTracking.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/chipTracking.test.js +52 -0
- package/dist/__tests__/cjk/chipTracking.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseHtml.test.d.ts +2 -0
- package/dist/__tests__/cjk/japaneseHtml.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/japaneseHtml.test.js +163 -0
- package/dist/__tests__/cjk/japaneseHtml.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseMarks.test.d.ts +2 -0
- package/dist/__tests__/cjk/japaneseMarks.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/japaneseMarks.test.js +289 -0
- package/dist/__tests__/cjk/japaneseMarks.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseMarksRender.test.d.ts +2 -0
- package/dist/__tests__/cjk/japaneseMarksRender.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/japaneseMarksRender.test.js +161 -0
- package/dist/__tests__/cjk/japaneseMarksRender.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseRuby.test.d.ts +2 -0
- package/dist/__tests__/cjk/japaneseRuby.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/japaneseRuby.test.js +457 -0
- package/dist/__tests__/cjk/japaneseRuby.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseSegmentLanguage.test.d.ts +2 -0
- package/dist/__tests__/cjk/japaneseSegmentLanguage.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js +50 -0
- package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseSpacing.test.d.ts +2 -0
- package/dist/__tests__/cjk/japaneseSpacing.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/japaneseSpacing.test.js +417 -0
- package/dist/__tests__/cjk/japaneseSpacing.test.js.map +1 -0
- package/dist/__tests__/cjk/kanbunKunten.test.d.ts +2 -0
- package/dist/__tests__/cjk/kanbunKunten.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/kanbunKunten.test.js +230 -0
- package/dist/__tests__/cjk/kanbunKunten.test.js.map +1 -0
- package/dist/__tests__/cjk/kanbunKuntenRender.test.d.ts +2 -0
- package/dist/__tests__/cjk/kanbunKuntenRender.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/kanbunKuntenRender.test.js +127 -0
- package/dist/__tests__/cjk/kanbunKuntenRender.test.js.map +1 -0
- package/dist/__tests__/cjk/kinsokuJa.test.d.ts +2 -0
- package/dist/__tests__/cjk/kinsokuJa.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/kinsokuJa.test.js +388 -0
- package/dist/__tests__/cjk/kinsokuJa.test.js.map +1 -0
- package/dist/__tests__/cjk/kinsokuJaBuild.test.d.ts +2 -0
- package/dist/__tests__/cjk/kinsokuJaBuild.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/kinsokuJaBuild.test.js +38 -0
- package/dist/__tests__/cjk/kinsokuJaBuild.test.js.map +1 -0
- package/dist/__tests__/cjk/resourceAnnotations.test.d.ts +2 -0
- package/dist/__tests__/cjk/resourceAnnotations.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/resourceAnnotations.test.js +223 -0
- package/dist/__tests__/cjk/resourceAnnotations.test.js.map +1 -0
- package/dist/__tests__/defaults/cjk.test.js +10 -2
- package/dist/__tests__/defaults/cjk.test.js.map +1 -1
- package/dist/__tests__/exports.test.js +36 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/headingLetterSpacing.test.js +2 -1
- package/dist/__tests__/headingLetterSpacing.test.js.map +1 -1
- package/dist/__tests__/indexChinese.test.js +1 -1
- package/dist/__tests__/indexJapanese.test.d.ts +2 -0
- package/dist/__tests__/indexJapanese.test.d.ts.map +1 -0
- package/dist/__tests__/indexJapanese.test.js +242 -0
- package/dist/__tests__/indexJapanese.test.js.map +1 -0
- package/dist/__tests__/japaneseHeadings.test.d.ts +2 -0
- package/dist/__tests__/japaneseHeadings.test.d.ts.map +1 -0
- package/dist/__tests__/japaneseHeadings.test.js +319 -0
- package/dist/__tests__/japaneseHeadings.test.js.map +1 -0
- package/dist/__tests__/japaneseLocale.test.d.ts +2 -0
- package/dist/__tests__/japaneseLocale.test.d.ts.map +1 -0
- package/dist/__tests__/japaneseLocale.test.js +201 -0
- package/dist/__tests__/japaneseLocale.test.js.map +1 -0
- package/dist/__tests__/japaneseNotes.test.d.ts +2 -0
- package/dist/__tests__/japaneseNotes.test.d.ts.map +1 -0
- package/dist/__tests__/japaneseNotes.test.js +336 -0
- package/dist/__tests__/japaneseNotes.test.js.map +1 -0
- package/dist/__tests__/japaneseNumbering.test.d.ts +2 -0
- package/dist/__tests__/japaneseNumbering.test.d.ts.map +1 -0
- package/dist/__tests__/japaneseNumbering.test.js +292 -0
- package/dist/__tests__/japaneseNumbering.test.js.map +1 -0
- package/dist/__tests__/parse/anchors.test.js +17 -0
- package/dist/__tests__/parse/anchors.test.js.map +1 -1
- package/dist/__tests__/pipeline/citationsJapanese.test.d.ts +2 -0
- package/dist/__tests__/pipeline/citationsJapanese.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/citationsJapanese.test.js +110 -0
- package/dist/__tests__/pipeline/citationsJapanese.test.js.map +1 -0
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.js +1 -1
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.js.map +1 -1
- package/dist/__tests__/pipeline/safeArea.test.d.ts +2 -0
- package/dist/__tests__/pipeline/safeArea.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/safeArea.test.js +207 -0
- package/dist/__tests__/pipeline/safeArea.test.js.map +1 -0
- package/dist/__tests__/vertical/compositionPaint.test.js +6 -5
- package/dist/__tests__/vertical/compositionPaint.test.js.map +1 -1
- package/dist/__tests__/vertical/japaneseVertical.test.d.ts +2 -0
- package/dist/__tests__/vertical/japaneseVertical.test.d.ts.map +1 -0
- package/dist/__tests__/vertical/japaneseVertical.test.js +247 -0
- package/dist/__tests__/vertical/japaneseVertical.test.js.map +1 -0
- package/dist/__tests__/vertical/verticalSpans.d.ts +2 -1
- package/dist/__tests__/vertical/verticalSpans.d.ts.map +1 -1
- package/dist/__tests__/vertical/verticalSpans.js +3 -2
- package/dist/__tests__/vertical/verticalSpans.js.map +1 -1
- package/dist/__tests__/video.test.d.ts +2 -0
- package/dist/__tests__/video.test.d.ts.map +1 -0
- package/dist/__tests__/video.test.js +305 -0
- package/dist/__tests__/video.test.js.map +1 -0
- package/dist/arabicMarks.d.ts +11 -1
- package/dist/arabicMarks.d.ts.map +1 -1
- package/dist/arabicMarks.js +72 -5
- package/dist/arabicMarks.js.map +1 -1
- package/dist/bundle/adapters.d.ts +6 -0
- package/dist/bundle/adapters.d.ts.map +1 -1
- package/dist/bundle/adapters.js +30 -0
- package/dist/bundle/adapters.js.map +1 -1
- package/dist/bundle/codec.d.ts.map +1 -1
- package/dist/bundle/codec.js +67 -3
- package/dist/bundle/codec.js.map +1 -1
- package/dist/bundle/index.d.ts +2 -2
- package/dist/bundle/index.d.ts.map +1 -1
- package/dist/bundle/index.js +2 -2
- package/dist/bundle/index.js.map +1 -1
- package/dist/bundle/manifest.d.ts +8 -0
- package/dist/bundle/manifest.d.ts.map +1 -1
- package/dist/bundle/manifest.js +48 -1
- package/dist/bundle/manifest.js.map +1 -1
- package/dist/bundle/types.d.ts +5 -0
- package/dist/bundle/types.d.ts.map +1 -1
- package/dist/bundle/zip.js +1 -1
- package/dist/bundle/zip.js.map +1 -1
- package/dist/canvas-backend/annotations.d.ts +20 -5
- package/dist/canvas-backend/annotations.d.ts.map +1 -1
- package/dist/canvas-backend/annotations.js +59 -6
- package/dist/canvas-backend/annotations.js.map +1 -1
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +29 -3
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts +6 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +124 -5
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/canvas-backend/verticalText.d.ts +9 -4
- package/dist/canvas-backend/verticalText.d.ts.map +1 -1
- package/dist/canvas-backend/verticalText.js +20 -9
- package/dist/canvas-backend/verticalText.js.map +1 -1
- package/dist/chineseNumerals.d.ts +3 -2
- package/dist/chineseNumerals.d.ts.map +1 -1
- package/dist/chineseNumerals.js +11 -8
- package/dist/chineseNumerals.js.map +1 -1
- package/dist/citations/bibtex.d.ts.map +1 -1
- package/dist/citations/bibtex.js +4 -0
- package/dist/citations/bibtex.js.map +1 -1
- package/dist/citations/data.d.ts.map +1 -1
- package/dist/citations/data.js +6 -1
- package/dist/citations/data.js.map +1 -1
- package/dist/citations/types.d.ts +8 -0
- package/dist/citations/types.d.ts.map +1 -1
- package/dist/cjkMarks.d.ts +23 -14
- package/dist/cjkMarks.d.ts.map +1 -1
- package/dist/cjkMarks.js +251 -49
- package/dist/cjkMarks.js.map +1 -1
- package/dist/configWarnings.js +9 -3
- package/dist/configWarnings.js.map +1 -1
- package/dist/defaults/cjk.d.ts +64 -14
- package/dist/defaults/cjk.d.ts.map +1 -1
- package/dist/defaults/cjk.js +203 -29
- package/dist/defaults/cjk.js.map +1 -1
- package/dist/defaults/footnotes.d.ts +27 -4
- package/dist/defaults/footnotes.d.ts.map +1 -1
- package/dist/defaults/footnotes.js +95 -36
- package/dist/defaults/footnotes.js.map +1 -1
- package/dist/defaults/headings.d.ts +5 -0
- package/dist/defaults/headings.d.ts.map +1 -1
- package/dist/defaults/headings.js +46 -1
- package/dist/defaults/headings.js.map +1 -1
- package/dist/defaults/index.d.ts +3 -2
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +12 -3
- package/dist/defaults/index.js.map +1 -1
- package/dist/defaults/indexConfig.d.ts.map +1 -1
- package/dist/defaults/indexConfig.js +7 -4
- package/dist/defaults/indexConfig.js.map +1 -1
- package/dist/defaults/paragraphStyles.d.ts.map +1 -1
- package/dist/defaults/paragraphStyles.js +4 -0
- package/dist/defaults/paragraphStyles.js.map +1 -1
- package/dist/defaults/resourceTypes.d.ts +16 -3
- package/dist/defaults/resourceTypes.d.ts.map +1 -1
- package/dist/defaults/resourceTypes.js +56 -8
- package/dist/defaults/resourceTypes.js.map +1 -1
- package/dist/defaults/shared.d.ts.map +1 -1
- package/dist/defaults/shared.js +21 -0
- package/dist/defaults/shared.js.map +1 -1
- package/dist/defaults/tableStyle.d.ts.map +1 -1
- package/dist/defaults/tableStyle.js +4 -1
- package/dist/defaults/tableStyle.js.map +1 -1
- package/dist/defaults/videoStyle.d.ts +9 -0
- package/dist/defaults/videoStyle.d.ts.map +1 -0
- package/dist/defaults/videoStyle.js +130 -0
- package/dist/defaults/videoStyle.js.map +1 -0
- package/dist/design/placeholders.d.ts.map +1 -1
- package/dist/design/placeholders.js +5 -3
- package/dist/design/placeholders.js.map +1 -1
- package/dist/html-backend.d.ts +13 -0
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +233 -26
- package/dist/html-backend.js.map +1 -1
- package/dist/htmlAnnotations.d.ts +47 -9
- package/dist/htmlAnnotations.d.ts.map +1 -1
- package/dist/htmlAnnotations.js +89 -17
- package/dist/htmlAnnotations.js.map +1 -1
- package/dist/index.d.ts +13 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/japaneseNumerals.d.ts +44 -0
- package/dist/japaneseNumerals.d.ts.map +1 -0
- package/dist/japaneseNumerals.js +100 -0
- package/dist/japaneseNumerals.js.map +1 -0
- package/dist/knuthPlass/richAdapter.d.ts +5 -1
- package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
- package/dist/knuthPlass/richAdapter.js +2 -0
- package/dist/knuthPlass/richAdapter.js.map +1 -1
- package/dist/locale.d.ts +19 -12
- package/dist/locale.d.ts.map +1 -1
- package/dist/locale.js +24 -12
- package/dist/locale.js.map +1 -1
- package/dist/measure/cache.d.ts.map +1 -1
- package/dist/measure/cache.js +17 -8
- package/dist/measure/cache.js.map +1 -1
- package/dist/measure/canvas.d.ts +21 -0
- package/dist/measure/canvas.d.ts.map +1 -1
- package/dist/measure/canvas.js +82 -0
- package/dist/measure/canvas.js.map +1 -1
- package/dist/measure/cjk.d.ts +3 -1
- package/dist/measure/cjk.d.ts.map +1 -1
- package/dist/measure/cjk.js +4 -2
- package/dist/measure/cjk.js.map +1 -1
- package/dist/measure/cjkAnnotate.d.ts +62 -3
- package/dist/measure/cjkAnnotate.d.ts.map +1 -1
- package/dist/measure/cjkAnnotate.js +99 -2
- package/dist/measure/cjkAnnotate.js.map +1 -1
- package/dist/measure/cjkClasses.d.ts +93 -13
- package/dist/measure/cjkClasses.d.ts.map +1 -1
- package/dist/measure/cjkClasses.js +204 -14
- package/dist/measure/cjkClasses.js.map +1 -1
- package/dist/measure/cjkCompose.d.ts +10 -4
- package/dist/measure/cjkCompose.d.ts.map +1 -1
- package/dist/measure/cjkCompose.js +715 -75
- package/dist/measure/cjkCompose.js.map +1 -1
- package/dist/measure/cjkPunctuation.d.ts +68 -14
- package/dist/measure/cjkPunctuation.d.ts.map +1 -1
- package/dist/measure/cjkPunctuation.js +96 -21
- package/dist/measure/cjkPunctuation.js.map +1 -1
- package/dist/measure/plain.js +2 -2
- package/dist/measure/plain.js.map +1 -1
- package/dist/measure/rich.d.ts +12 -2
- package/dist/measure/rich.d.ts.map +1 -1
- package/dist/measure/rich.js +46 -8
- package/dist/measure/rich.js.map +1 -1
- package/dist/measure/rubyJis.d.ts +96 -0
- package/dist/measure/rubyJis.d.ts.map +1 -0
- package/dist/measure/rubyJis.js +245 -0
- package/dist/measure/rubyJis.js.map +1 -0
- package/dist/measure/vertical.d.ts +13 -5
- package/dist/measure/vertical.d.ts.map +1 -1
- package/dist/measure/vertical.js +16 -6
- package/dist/measure/vertical.js.map +1 -1
- package/dist/numberWords.d.ts +4 -3
- package/dist/numberWords.d.ts.map +1 -1
- package/dist/numberWords.js +15 -5
- package/dist/numberWords.js.map +1 -1
- package/dist/numbering.d.ts +24 -12
- package/dist/numbering.d.ts.map +1 -1
- package/dist/numbering.js +57 -11
- package/dist/numbering.js.map +1 -1
- package/dist/parse/annotations.d.ts +21 -18
- package/dist/parse/annotations.d.ts.map +1 -1
- package/dist/parse/annotations.js +140 -51
- package/dist/parse/annotations.js.map +1 -1
- package/dist/parse/index.d.ts +1 -1
- package/dist/parse/index.d.ts.map +1 -1
- package/dist/parse/index.js.map +1 -1
- package/dist/parse/indexMarks.d.ts.map +1 -1
- package/dist/parse/indexMarks.js +74 -10
- package/dist/parse/indexMarks.js.map +1 -1
- package/dist/parse/inlineFormatting.d.ts +4 -3
- package/dist/parse/inlineFormatting.d.ts.map +1 -1
- package/dist/parse/inlineFormatting.js +9 -8
- package/dist/parse/inlineFormatting.js.map +1 -1
- package/dist/parse/types.d.ts +93 -5
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/annotations.d.ts +25 -15
- package/dist/pipeline/annotations.d.ts.map +1 -1
- package/dist/pipeline/annotations.js +102 -16
- package/dist/pipeline/annotations.js.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +284 -31
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/buildBlockKind.js +2 -1
- package/dist/pipeline/buildBlockKind.js.map +1 -1
- package/dist/pipeline/buildHelpers.d.ts +2 -0
- package/dist/pipeline/buildHelpers.d.ts.map +1 -1
- package/dist/pipeline/buildHelpers.js +17 -0
- package/dist/pipeline/buildHelpers.js.map +1 -1
- package/dist/pipeline/buildMeasurement.d.ts +3 -0
- package/dist/pipeline/buildMeasurement.d.ts.map +1 -1
- package/dist/pipeline/buildMeasurement.js +1 -0
- package/dist/pipeline/buildMeasurement.js.map +1 -1
- package/dist/pipeline/calloutLayout.d.ts +2 -0
- package/dist/pipeline/calloutLayout.d.ts.map +1 -1
- package/dist/pipeline/calloutLayout.js +5 -5
- package/dist/pipeline/calloutLayout.js.map +1 -1
- package/dist/pipeline/citations.d.ts +1 -1
- package/dist/pipeline/citations.d.ts.map +1 -1
- package/dist/pipeline/citations.js +15 -7
- package/dist/pipeline/citations.js.map +1 -1
- package/dist/pipeline/columnBalancing.d.ts +22 -0
- package/dist/pipeline/columnBalancing.d.ts.map +1 -1
- package/dist/pipeline/columnBalancing.js +80 -3
- package/dist/pipeline/columnBalancing.js.map +1 -1
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +3 -2
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/contentWarnings.d.ts.map +1 -1
- package/dist/pipeline/contentWarnings.js +19 -0
- package/dist/pipeline/contentWarnings.js.map +1 -1
- package/dist/pipeline/continuation.d.ts.map +1 -1
- package/dist/pipeline/continuation.js +3 -3
- package/dist/pipeline/continuation.js.map +1 -1
- package/dist/pipeline/crossRefs.d.ts.map +1 -1
- package/dist/pipeline/crossRefs.js +3 -0
- package/dist/pipeline/crossRefs.js.map +1 -1
- package/dist/pipeline/footnotes.d.ts +20 -12
- package/dist/pipeline/footnotes.d.ts.map +1 -1
- package/dist/pipeline/footnotes.js +62 -16
- package/dist/pipeline/footnotes.js.map +1 -1
- package/dist/pipeline/headingStyles.js +1 -1
- package/dist/pipeline/headingStyles.js.map +1 -1
- package/dist/pipeline/indexDirective.d.ts.map +1 -1
- package/dist/pipeline/indexDirective.js +89 -12
- package/dist/pipeline/indexDirective.js.map +1 -1
- package/dist/pipeline/indexGroups.d.ts +5 -3
- package/dist/pipeline/indexGroups.d.ts.map +1 -1
- package/dist/pipeline/indexGroups.js +9 -4
- package/dist/pipeline/indexGroups.js.map +1 -1
- package/dist/pipeline/indexJapanese.d.ts +75 -0
- package/dist/pipeline/indexJapanese.d.ts.map +1 -0
- package/dist/pipeline/indexJapanese.js +264 -0
- package/dist/pipeline/indexJapanese.js.map +1 -0
- package/dist/pipeline/lineSpan.d.ts +36 -0
- package/dist/pipeline/lineSpan.d.ts.map +1 -0
- package/dist/pipeline/lineSpan.js +36 -0
- package/dist/pipeline/lineSpan.js.map +1 -0
- package/dist/pipeline/lists.js +2 -2
- package/dist/pipeline/lists.js.map +1 -1
- package/dist/pipeline/measureContentBlock.d.ts +4 -0
- package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
- package/dist/pipeline/measureContentBlock.js +54 -5
- package/dist/pipeline/measureContentBlock.js.map +1 -1
- package/dist/pipeline/outline.d.ts.map +1 -1
- package/dist/pipeline/outline.js +3 -1
- package/dist/pipeline/outline.js.map +1 -1
- package/dist/pipeline/paragraphContainers.d.ts +25 -2
- package/dist/pipeline/paragraphContainers.d.ts.map +1 -1
- package/dist/pipeline/paragraphContainers.js +82 -2
- package/dist/pipeline/paragraphContainers.js.map +1 -1
- package/dist/pipeline/parts.d.ts +2 -2
- package/dist/pipeline/parts.js +3 -3
- package/dist/pipeline/parts.js.map +1 -1
- package/dist/pipeline/placement.d.ts.map +1 -1
- package/dist/pipeline/placement.js +2 -2
- package/dist/pipeline/placement.js.map +1 -1
- package/dist/pipeline/resourceLayout.d.ts +6 -0
- package/dist/pipeline/resourceLayout.d.ts.map +1 -1
- package/dist/pipeline/resourceLayout.js +85 -15
- package/dist/pipeline/resourceLayout.js.map +1 -1
- package/dist/pipeline/resourceNumbering.d.ts +5 -2
- package/dist/pipeline/resourceNumbering.d.ts.map +1 -1
- package/dist/pipeline/resourceNumbering.js +12 -7
- package/dist/pipeline/resourceNumbering.js.map +1 -1
- package/dist/pipeline/safeArea.d.ts +29 -0
- package/dist/pipeline/safeArea.d.ts.map +1 -0
- package/dist/pipeline/safeArea.js +89 -0
- package/dist/pipeline/safeArea.js.map +1 -0
- package/dist/pipeline/styles.d.ts +12 -0
- package/dist/pipeline/styles.d.ts.map +1 -1
- package/dist/pipeline/styles.js +7 -1
- package/dist/pipeline/styles.js.map +1 -1
- package/dist/pipeline/verse.d.ts +3 -0
- package/dist/pipeline/verse.d.ts.map +1 -1
- package/dist/pipeline/verse.js +1 -1
- package/dist/pipeline/verse.js.map +1 -1
- package/dist/pipeline/verticalMetrics.d.ts.map +1 -1
- package/dist/pipeline/verticalMetrics.js +4 -0
- package/dist/pipeline/verticalMetrics.js.map +1 -1
- package/dist/pipeline/videoOverlay.d.ts +24 -0
- package/dist/pipeline/videoOverlay.d.ts.map +1 -0
- package/dist/pipeline/videoOverlay.js +134 -0
- package/dist/pipeline/videoOverlay.js.map +1 -0
- package/dist/types.d.ts +567 -50
- package/dist/types.d.ts.map +1 -1
- package/dist/uprightArabic.js +1 -1
- package/dist/uprightArabic.js.map +1 -1
- package/dist/vdt.d.ts +221 -17
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/dist/video/qr.d.ts +21 -0
- package/dist/video/qr.d.ts.map +1 -0
- package/dist/video/qr.js +326 -0
- package/dist/video/qr.js.map +1 -0
- package/dist/video/url.d.ts +62 -0
- package/dist/video/url.d.ts.map +1 -0
- package/dist/video/url.js +206 -0
- package/dist/video/url.js.map +1 -0
- package/dist/writingMode.d.ts +74 -20
- package/dist/writingMode.d.ts.map +1 -1
- package/dist/writingMode.js +146 -27
- package/dist/writingMode.js.map +1 -1
- package/package.json +1 -1
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
* segment of its own. A line that needs more than the cap (½ em, or
|
|
22
22
|
* `bodyText.maxJustifyTracking` when set) is set with the cap and flagged
|
|
23
23
|
* `cjkLoose` and `ragged`; a line with no CJK character on it (the head of
|
|
24
|
-
* a long web address) is set `ragged` without the flag.
|
|
24
|
+
* a long web address) is set `ragged` without the flag. Japanese
|
|
25
|
+
* full-width text (`isJlreqSpacing`) follows JLReq §3.8 instead where it
|
|
26
|
+
* differs: a line takes one more character by giving up blank in Table 3's
|
|
27
|
+
* order (`fitLine`), and is never spread inside a bracket or next to
|
|
28
|
+
* 、。・?!, a hyphen or U+3000 (`gapStretches`, JLReq §3.1.11).
|
|
25
29
|
*
|
|
26
30
|
* Segments: a Western run keeps one of its own, and single characters
|
|
27
31
|
* share one only when they have one style, link and spacing and advance
|
|
@@ -29,18 +33,20 @@
|
|
|
29
33
|
* and a Markdown link covers only its own.
|
|
30
34
|
*/
|
|
31
35
|
import { createBoundingBox } from '../vdt';
|
|
36
|
+
import { isJapaneseLanguage } from '../locale';
|
|
32
37
|
import { lineMeasure } from './types';
|
|
33
38
|
import { measureInkBox, measureInkExtent, measureTextWidth, normalSpaceWidthFor } from './canvas';
|
|
34
39
|
import { atomicSpanToken, setLabelTabs, emergencySplit, expandSmallCaps, pickSpanFont, scriptMetrics, setsObject, spanScriptFields, stackedScriptPairs, textWidth, tokenSegment, URL_LIKE_RE, urlBreakIndices, } from './rich';
|
|
35
|
-
import { cjkBreakAllowed, cjkClassOf, getCjkLineBreak, isCjkGrapheme, isFullwidthAlnum, isFullwidthDigit, isLineEndProhibited, isLineStartProhibited, isWordInnerMark, } from './cjkClasses';
|
|
40
|
+
import { cjkBreakAllowed, cjkClassOf, getCjkLineBreak, isCjkGrapheme, isFullwidthAlnum, isFullwidthDigit, isJapaneseLineBreak, isLineEndProhibited, isLineStartProhibited, isWordInnerMark, } from './cjkClasses';
|
|
36
41
|
import { hasCJK } from './cjk';
|
|
37
42
|
import { graphemeCount, graphemesOf, lastGrapheme } from './graphemes';
|
|
38
43
|
import { isBreakingSpace } from './spaces';
|
|
39
44
|
import { trimChipLineEdges } from './chipEdges';
|
|
40
|
-
import { cellAdvance, fontEm, fontFamilyOf, getMeasureRegion, getMeasureUprightDigits, getMeasureWritingMode, lineBaselineOffset, measureCentralBaseline, verticalTrackCount, withMeasureWritingMode } from './vertical';
|
|
41
|
-
import { verticalRuns } from '../writingMode';
|
|
42
|
-
import { foldWidth, isZhuyin, noteRowBaselines, readingAdvance, rubyGeometry, splitNote, withFontSize, ZHUYIN_SIZE_RATIO } from './cjkAnnotate';
|
|
43
|
-
import {
|
|
45
|
+
import { cellAdvance, flowTextWidth, fontEm, fontFamilyOf, getMeasureRegion, getMeasureUprightDigits, getMeasureWritingMode, lineBaselineOffset, measureCentralBaseline, verticalTrackCount, withMeasureWritingMode } from './vertical';
|
|
46
|
+
import { isUprightMarkPair, verticalRuns } from '../writingMode';
|
|
47
|
+
import { foldWidth, isZhuyin, kuntenGeometry, noteRowBaselines, readingAdvance, readingBaseline, rubyGeometry, splitNote, withFontSize, ZHUYIN_SIZE_RATIO } from './cjkAnnotate';
|
|
48
|
+
import { boxesWidth, fullSizeKana, layoutJisRuby, layoutJukugo } from './rubyJis';
|
|
49
|
+
import { applyLineEdges, blankEnd, blankStart, boxCut, carryStopBlank, compositionFor, compressPair, getCjkComposition, isLatinSpacingHan, isLatinSpacingLatin, isJlreqSpacing, isPlainComposition, jlreqShrinkStep, latinSpacingPx, lineEdgeCut, mayHang, punctuationBox, punctuationShrink, routesSharedMarks, shrinkPunctuation, shrinkStep, } from './cjkPunctuation';
|
|
44
50
|
/** Fit tolerance of the breaker (px): running sums of many widths may
|
|
45
51
|
* land a hair past a measure they fill exactly. */
|
|
46
52
|
const FIT_EPS = 1e-6;
|
|
@@ -97,11 +103,13 @@ export function isCjkParagraph(text) {
|
|
|
97
103
|
export function composesAsCjk(text) {
|
|
98
104
|
return hasCJK(text) && isCjkParagraph(text);
|
|
99
105
|
}
|
|
100
|
-
/** The marks a span sets on its characters (#193), the
|
|
101
|
-
*
|
|
102
|
-
*
|
|
106
|
+
/** The marks a span sets on its characters (#193, #421), the defaults
|
|
107
|
+
* of what the span leaves unset filled in (`cjk.emphasisMark` has filled
|
|
108
|
+
* the region's in already, `pipeline/annotations.ts`): emphasis dots a
|
|
109
|
+
* filled dot (an open circle), a side line solid; both under the text,
|
|
110
|
+
* or right of it in vertical text. */
|
|
103
111
|
export function spanMarks(span, vertical) {
|
|
104
|
-
if (!span.emphasisMark && span.properName === undefined && !span.bookTitle)
|
|
112
|
+
if (!span.emphasisMark && span.properName === undefined && !span.bookTitle && !span.sideline)
|
|
105
113
|
return undefined;
|
|
106
114
|
const marks = {};
|
|
107
115
|
if (span.emphasisMark) {
|
|
@@ -116,6 +124,13 @@ export function spanMarks(span, vertical) {
|
|
|
116
124
|
marks.properName = span.properName;
|
|
117
125
|
if (span.bookTitle)
|
|
118
126
|
marks.bookTitle = span.bookTitle.id;
|
|
127
|
+
if (span.sideline) {
|
|
128
|
+
marks.sideline = {
|
|
129
|
+
id: span.sideline.id,
|
|
130
|
+
style: span.sideline.style ?? 'solid',
|
|
131
|
+
position: span.sideline.position ?? (vertical ? 'over' : 'under'),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
119
134
|
return marks;
|
|
120
135
|
}
|
|
121
136
|
/** The part of a style key the marks and added characters make. */
|
|
@@ -123,14 +138,32 @@ function marksKey(marks, inserted) {
|
|
|
123
138
|
if (!marks && !inserted)
|
|
124
139
|
return '';
|
|
125
140
|
const d = marks?.dots;
|
|
126
|
-
|
|
141
|
+
const l = marks?.sideline;
|
|
142
|
+
return `|m:${d ? `${d.style}${d.fill}${d.position}` : ''}:${marks?.properName ?? ''}:${marks?.bookTitle ?? ''}${l ? `:sl${l.id}${l.style}${l.position}` : ''}${inserted ? ':ins' : ''}`;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The language a span's isolate names (the innermost that names one,
|
|
146
|
+
* `:ltr[…]{lang=en}`), which its segments carry (`VDTLineSegment.lang`)
|
|
147
|
+
* so the renderers declare it (a PDF `Span` with its `/Lang`) and shape it
|
|
148
|
+
* in its own forms (#427): in a Japanese document (the `japan` region), or
|
|
149
|
+
* for an isolate in Japanese. A segment then never holds text of two
|
|
150
|
+
* languages. Other composed text carries none, as before.
|
|
151
|
+
*/
|
|
152
|
+
function spanLanguageHere(span) {
|
|
153
|
+
let lang;
|
|
154
|
+
for (let d = span.direction; d && lang === undefined; d = d.outer)
|
|
155
|
+
lang = d.lang;
|
|
156
|
+
if (lang === undefined)
|
|
157
|
+
return undefined;
|
|
158
|
+
return getMeasureRegion() === 'japan' || isJapaneseLanguage(lang) ? lang : undefined;
|
|
127
159
|
}
|
|
128
160
|
function styleOf(span, fonts, vertical = false) {
|
|
129
161
|
const script = spanScriptFields(span, fonts.normal, fonts.bold, fonts.italic, fonts.boldItalic);
|
|
130
162
|
const font = script.scriptFont ?? pickSpanFont(span.bold, span.italic, fonts.normal, fonts.bold, fonts.italic, fonts.boldItalic);
|
|
131
163
|
const marks = spanMarks(span, vertical);
|
|
164
|
+
const lang = spanLanguageHere(span);
|
|
132
165
|
return {
|
|
133
|
-
key: `${span.bold ? 'b' : ''}${span.italic ? 'i' : ''}${span.captionLabel ? 'c' : ''}${span.smallCaps ? 's' : ''}|${script.script ?? ''}|${font}|${script.baselineShift ?? ''}${marksKey(marks, span.inserted)}`,
|
|
166
|
+
key: `${span.bold ? 'b' : ''}${span.italic ? 'i' : ''}${span.captionLabel ? 'c' : ''}${span.smallCaps ? 's' : ''}|${script.script ?? ''}|${font}|${script.baselineShift ?? ''}${marksKey(marks, span.inserted)}${lang !== undefined ? `|lang:${lang}` : ''}`,
|
|
134
167
|
bold: span.bold,
|
|
135
168
|
italic: span.italic,
|
|
136
169
|
...(span.captionLabel ? { captionLabel: true } : {}),
|
|
@@ -139,6 +172,7 @@ function styleOf(span, fonts, vertical = false) {
|
|
|
139
172
|
font,
|
|
140
173
|
...(marks ? { marks } : {}),
|
|
141
174
|
...(span.inserted ? { inserted: true } : {}),
|
|
175
|
+
...(lang !== undefined ? { lang } : {}),
|
|
142
176
|
};
|
|
143
177
|
}
|
|
144
178
|
/** A grapheme set as a Chinese character here: a CJK grapheme, unless it
|
|
@@ -244,6 +278,7 @@ function rubyBaseUnit(span, fonts, letterSpacingPx, vertical) {
|
|
|
244
278
|
let position = ruby.position ?? (isZhuyin(ruby.text) ? 'right' : 'over');
|
|
245
279
|
if (vertical && position === 'right')
|
|
246
280
|
position = 'over';
|
|
281
|
+
const jis = isJisRuby(ruby, position);
|
|
247
282
|
return {
|
|
248
283
|
kind: 'text',
|
|
249
284
|
text: span.text,
|
|
@@ -255,9 +290,198 @@ function rubyBaseUnit(span, fonts, letterSpacingPx, vertical) {
|
|
|
255
290
|
lastCjk: isCjkGrapheme(lastG),
|
|
256
291
|
style,
|
|
257
292
|
at: 0,
|
|
258
|
-
ruby: { span: ruby, position },
|
|
293
|
+
ruby: { span: ruby, position, ...(jis ? { base: width } : {}) },
|
|
259
294
|
};
|
|
260
295
|
}
|
|
296
|
+
/** Whether a ruby is set by the Japanese rules (#422, `rubyJis.ts`): one
|
|
297
|
+
* over or under its base, not zhuyin, that has an alignment, an overhang
|
|
298
|
+
* rule, a jukugo word, a `mode=mono` or full-size kana (in Japan every
|
|
299
|
+
* reading has the first two). Others keep the clreq geometry. */
|
|
300
|
+
function isJisRuby(ruby, position) {
|
|
301
|
+
if (position === 'right' || isZhuyin(ruby.text))
|
|
302
|
+
return false;
|
|
303
|
+
return !!(ruby.align || ruby.overhang || ruby.jukugo || ruby.mono || ruby.fullKana);
|
|
304
|
+
}
|
|
305
|
+
/** Hiragana, katakana (small kana, half-width forms and ー too): what a
|
|
306
|
+
* reading may pass onto under `cjk.ruby.overhang: 'kana'`. */
|
|
307
|
+
const KANA_RE = /^[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u31F0-\u31FF\uFF66-\uFF9F]/u;
|
|
308
|
+
/** How far a Japanese reading may pass its base (or its jukugo word)
|
|
309
|
+
* onto the unit `v` beside it (`before`: `v` precedes it), px (JLReq
|
|
310
|
+
* §3.3.8, see `CjkRubyOverhang`); 0 at the paragraph's edge. `prev` is
|
|
311
|
+
* the unit before `v`, whose reading may already run onto it. */
|
|
312
|
+
function rubyOverhangOnto(v, before, prev, mode, rubyEm) {
|
|
313
|
+
if (!v || mode === 'none')
|
|
314
|
+
return 0;
|
|
315
|
+
if (mode === undefined) {
|
|
316
|
+
// clreq: a quarter of the ruby em onto a neighbour without a reading,
|
|
317
|
+
// as the other rubies of the paragraph (`sizeRubies`).
|
|
318
|
+
if (v.kind !== 'text' || v.note)
|
|
319
|
+
return 0;
|
|
320
|
+
if (!v.ruby)
|
|
321
|
+
return rubyEm / 4;
|
|
322
|
+
const theirs = v.ruby.geometry?.rtWidth ?? readingAdvance(v.ruby.span.text, v.ruby.span.fontString ?? v.style.font, v.ruby.position);
|
|
323
|
+
return Math.max(0, Math.min(rubyEm / 4, (v.width - theirs) / 2 - rubyEm / 4));
|
|
324
|
+
}
|
|
325
|
+
if (v.kind === 'atomic' || v.note || v.warichu)
|
|
326
|
+
return 0;
|
|
327
|
+
// A space: its width; never the space between Japanese and Latin text,
|
|
328
|
+
// whose other side is a Western letter.
|
|
329
|
+
if (v.kind === 'space')
|
|
330
|
+
return v.auto ? 0 : Math.min(mode === 'any' ? rubyEm / 2 : rubyEm, v.width);
|
|
331
|
+
if (v.ruby) {
|
|
332
|
+
// Another base: only its reading's free room, and none in kana mode.
|
|
333
|
+
if (mode !== 'any')
|
|
334
|
+
return 0;
|
|
335
|
+
const theirs = v.ruby.chars ? v.ruby.chars.reduce((w, c) => w + c.width, 0) : readingAdvance(v.ruby.span.text, v.ruby.span.fontString ?? v.style.font, v.ruby.position);
|
|
336
|
+
return Math.max(0, Math.min(rubyEm / 2, (v.width - theirs) / 2));
|
|
337
|
+
}
|
|
338
|
+
if (mode === 'any')
|
|
339
|
+
return rubyEm / 2;
|
|
340
|
+
// `kana`: two readings never share a kana from both sides; the later
|
|
341
|
+
// one keeps to its base (JLReq §3.3.8 note).
|
|
342
|
+
if (before && prev?.ruby?.geometry && prev.ruby.jis) {
|
|
343
|
+
const g = prev.ruby.geometry;
|
|
344
|
+
const reach = g.runs.reduce((hi, r) => Math.max(hi, r.dx + flowTextWidth(r.text, r.fontString)), -Infinity);
|
|
345
|
+
if (reach > g.width + 1e-6)
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
const box = v.punct;
|
|
349
|
+
const cls = before ? v.last : v.first;
|
|
350
|
+
if (box) {
|
|
351
|
+
// A mark: its blank on the reading's side, never its glyph; at most
|
|
352
|
+
// half a ruby character onto an opening bracket. …… and ―― take a
|
|
353
|
+
// ruby character.
|
|
354
|
+
if (box.cls === 'opening')
|
|
355
|
+
return rubyEm / 2;
|
|
356
|
+
if (box.cls === 'dash' || box.cls === 'ellipsis')
|
|
357
|
+
return rubyEm;
|
|
358
|
+
return Math.min(rubyEm, before ? blankEnd(box) : blankStart(box));
|
|
359
|
+
}
|
|
360
|
+
if (cls === 'opening')
|
|
361
|
+
return rubyEm / 2;
|
|
362
|
+
if (cls === 'ideoSpace' || cls === 'dash' || cls === 'ellipsis')
|
|
363
|
+
return rubyEm;
|
|
364
|
+
// Kana, ー; never kanji, nor a Western run.
|
|
365
|
+
return !v.run && v.graphemes === 1 && KANA_RE.test(v.text) ? rubyEm : 0;
|
|
366
|
+
}
|
|
367
|
+
/** The bases of a Japanese ruby's units (see {@link JisRubyBase}). */
|
|
368
|
+
function jisBases(us) {
|
|
369
|
+
return us.map((u) => ({ width: u.ruby.base, graphemes: u.graphemes, reading: u.ruby.chars }));
|
|
370
|
+
}
|
|
371
|
+
/** Lay out the bases of one Japanese ruby word (or one base). */
|
|
372
|
+
function layoutJisWord(word, bases, allow) {
|
|
373
|
+
return word.jukugo ? layoutJukugo(bases, word.align, word.rubyEm, allow) : [layoutJisRuby(bases[0], word.align, word.rubyEm, allow)];
|
|
374
|
+
}
|
|
375
|
+
/** `u` with the box `box` of its Japanese ruby: its geometry and width. */
|
|
376
|
+
function withJisBox(u, box, word) {
|
|
377
|
+
const ruby = u.ruby;
|
|
378
|
+
const font = ruby.span.fontString ?? withFontSize(u.style.font, emOfFont(u.style.font) / 2);
|
|
379
|
+
const dy = readingBaseline(ruby.span.text, font, ruby.position === 'under' ? 'under' : 'over', emOfFont(u.style.font));
|
|
380
|
+
const geometry = {
|
|
381
|
+
width: box.width,
|
|
382
|
+
inset: box.inset,
|
|
383
|
+
rtWidth: ruby.chars.reduce((w, c) => w + c.width, 0),
|
|
384
|
+
runs: box.pieces.map((p) => ({ text: p.text, dx: p.dx, dy, fontString: font })),
|
|
385
|
+
allowLeft: word.allow.start,
|
|
386
|
+
allowRight: word.allow.end,
|
|
387
|
+
...(box.tracking > 0 ? { tracking: box.tracking } : {}),
|
|
388
|
+
};
|
|
389
|
+
return { ...u, width: box.width, ruby: { ...ruby, geometry } };
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Lay out the Japanese rubies of a paragraph (#422, `rubyJis.ts`): each
|
|
393
|
+
* jukugo word, or each base, with what its neighbours let its reading pass
|
|
394
|
+
* onto, in order (a reading that runs onto a kana keeps the next one off
|
|
395
|
+
* it). Each base also learns how much wider it gets at a line edge
|
|
396
|
+
* (`rubyGrow`), for the breaker. Units are replaced in place.
|
|
397
|
+
*/
|
|
398
|
+
function sizeJisRubies(units) {
|
|
399
|
+
const n = units.length;
|
|
400
|
+
for (let k = 0; k < n; k++) {
|
|
401
|
+
const u = units[k];
|
|
402
|
+
if (!u.ruby || u.ruby.base === undefined || u.ruby.geometry)
|
|
403
|
+
continue;
|
|
404
|
+
const span = u.ruby.span;
|
|
405
|
+
let e = k;
|
|
406
|
+
if (span.jukugo) {
|
|
407
|
+
while (e + 1 < n) {
|
|
408
|
+
const v = units[e + 1].ruby;
|
|
409
|
+
if (!v || v.base === undefined || !v.span.jukugo || v.span.id !== span.id || v.position !== u.ruby.position)
|
|
410
|
+
break;
|
|
411
|
+
e++;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const font = span.fontString ?? withFontSize(u.style.font, emOfFont(u.style.font) / 2);
|
|
415
|
+
const rubyEm = fontEm(font);
|
|
416
|
+
for (let q = k; q <= e; q++) {
|
|
417
|
+
const r = units[q].ruby;
|
|
418
|
+
const painted = r.span.fullKana ? fullSizeKana(r.span.text) : r.span.text;
|
|
419
|
+
r.chars = graphemesOf(painted).map((g) => ({ text: g, width: flowTextWidth(g, font) }));
|
|
420
|
+
}
|
|
421
|
+
const word = {
|
|
422
|
+
jukugo: e > k,
|
|
423
|
+
align: span.align ?? 'center',
|
|
424
|
+
rubyEm,
|
|
425
|
+
allow: {
|
|
426
|
+
start: rubyOverhangOnto(units[k - 1], true, units[k - 2], span.overhang, rubyEm),
|
|
427
|
+
end: rubyOverhangOnto(units[e + 1], false, undefined, span.overhang, rubyEm),
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
const members = units.slice(k, e + 1);
|
|
431
|
+
const bases = jisBases(members);
|
|
432
|
+
const boxes = layoutJisWord(word, bases, word.allow);
|
|
433
|
+
for (let q = k; q <= e; q++) {
|
|
434
|
+
units[q] = withJisBox(units[q], boxes[q - k], word);
|
|
435
|
+
units[q].ruby.jis = word;
|
|
436
|
+
}
|
|
437
|
+
// What the line gains when the word (or the base) opens it from that
|
|
438
|
+
// unit on, or closes it with that unit: the reading flush with the
|
|
439
|
+
// edge, the part of a word laid out alone.
|
|
440
|
+
for (let i = 0; i < boxes.length; i++) {
|
|
441
|
+
const head = boxesWidth(boxes.slice(0, i + 1));
|
|
442
|
+
const tail = boxesWidth(boxes.slice(i));
|
|
443
|
+
units[k + i].rubyGrow = {
|
|
444
|
+
start: boxesWidth(layoutJisWord(word, bases.slice(i), { start: 0, end: word.allow.end })) - tail,
|
|
445
|
+
end: boxesWidth(layoutJisWord(word, bases.slice(0, i + 1), { start: word.allow.start, end: 0 })) - head,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
k = e;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* A line's Japanese rubies at its edges (#422, JLReq §3.3.8): a word or
|
|
453
|
+
* base that opens or closes the line is laid out again with nothing to
|
|
454
|
+
* pass onto on that side, so a longer reading is set flush with the edge
|
|
455
|
+
* and its base moves in; a jukugo word the line breaks inside is laid
|
|
456
|
+
* out again with the characters the line holds, each keeping its reading.
|
|
457
|
+
* The breaker counted what this adds (`rubyGrow`). Units that change are
|
|
458
|
+
* replaced by copies in `us`.
|
|
459
|
+
*/
|
|
460
|
+
function relayJisEdges(us) {
|
|
461
|
+
for (let j = 0; j < us.length; j++) {
|
|
462
|
+
const word = us[j].ruby?.jis;
|
|
463
|
+
if (!word)
|
|
464
|
+
continue;
|
|
465
|
+
let e = j;
|
|
466
|
+
while (e + 1 < us.length && us[e + 1].ruby?.jis === word)
|
|
467
|
+
e++;
|
|
468
|
+
// A word with kanbun marks (#430) keeps its layout: the marks were set
|
|
469
|
+
// against its readings (`sizeKunten`).
|
|
470
|
+
if (us.slice(j, e + 1).some((u) => u.kunten?.vdt)) {
|
|
471
|
+
j = e;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const atStart = j === 0;
|
|
475
|
+
const atEnd = e === us.length - 1;
|
|
476
|
+
if (atStart || atEnd) {
|
|
477
|
+
const allow = { start: atStart ? 0 : word.allow.start, end: atEnd ? 0 : word.allow.end };
|
|
478
|
+
const boxes = layoutJisWord(word, jisBases(us.slice(j, e + 1)), allow);
|
|
479
|
+
for (let q = j; q <= e; q++)
|
|
480
|
+
us[q] = withJisBox(us[q], boxes[q - j], word);
|
|
481
|
+
}
|
|
482
|
+
j = e;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
261
485
|
/**
|
|
262
486
|
* Lay out each ruby base's reading (see `cjkAnnotate.ts`): its box grows
|
|
263
487
|
* to the reading less what the reading may pass it by — a quarter of the
|
|
@@ -266,6 +490,9 @@ function rubyBaseUnit(span, fonts, letterSpacingPx, vertical) {
|
|
|
266
490
|
* the symbols' em). Run once the units are final.
|
|
267
491
|
*/
|
|
268
492
|
function sizeRubies(units) {
|
|
493
|
+
// Japanese rubies first (#422): the others read their geometry.
|
|
494
|
+
if (units.some((u) => u.ruby?.base !== undefined))
|
|
495
|
+
sizeJisRubies(units);
|
|
269
496
|
for (let k = 0; k < units.length; k++) {
|
|
270
497
|
const u = units[k];
|
|
271
498
|
if (!u.ruby || u.ruby.geometry)
|
|
@@ -395,8 +622,11 @@ function buildUnits(spans, fonts, letterSpacingPx, vertical = false, route = fal
|
|
|
395
622
|
// Which units open a span and hold all of it (the candidates for stacked
|
|
396
623
|
// scripts), by index.
|
|
397
624
|
const wholeSpan = new Set();
|
|
625
|
+
// Where each span's units start, for the kanbun marks (#430).
|
|
626
|
+
const spanStarts = [];
|
|
398
627
|
for (let si = 0; si < spans.length; si++) {
|
|
399
628
|
const span = spans[si];
|
|
629
|
+
spanStarts.push(units.length);
|
|
400
630
|
// A ruby base: one unit, sized with its reading once its neighbours
|
|
401
631
|
// are known (`sizeRubies`).
|
|
402
632
|
if (span.ruby && span.text.length > 0) {
|
|
@@ -598,6 +828,8 @@ function buildUnits(spans, fonts, letterSpacingPx, vertical = false, route = fal
|
|
|
598
828
|
if (units.length === spanFirst + 1 && units[spanFirst].kind === 'text')
|
|
599
829
|
wholeSpan.add(spanFirst);
|
|
600
830
|
}
|
|
831
|
+
if (spans.some((s) => s.kunten))
|
|
832
|
+
attachKunten(spans, spanStarts, units);
|
|
601
833
|
// A subscript and a superscript that touch are set one over the other
|
|
602
834
|
// (EF-80): the first advances nothing, the second the pair's advance, and
|
|
603
835
|
// the two never part.
|
|
@@ -619,10 +851,146 @@ function buildUnits(spans, fonts, letterSpacingPx, vertical = false, route = fal
|
|
|
619
851
|
b.glueBefore = true;
|
|
620
852
|
}
|
|
621
853
|
}
|
|
854
|
+
if (vertical && getMeasureRegion() === 'japan')
|
|
855
|
+
combineMarkPairs(units, letterSpacingPx);
|
|
622
856
|
if (route)
|
|
623
857
|
routeSharedMarks(units, letterSpacingPx, vertical);
|
|
624
858
|
return units;
|
|
625
859
|
}
|
|
860
|
+
/** Give each kanbun mark (#430) to the last unit its spans made (a space
|
|
861
|
+
* aside): the marks go with the base's last character. `starts[i]` is
|
|
862
|
+
* where span `i`'s units start. */
|
|
863
|
+
function attachKunten(spans, starts, units) {
|
|
864
|
+
const last = new Map();
|
|
865
|
+
spans.forEach((span, i) => {
|
|
866
|
+
if (!span.kunten)
|
|
867
|
+
return;
|
|
868
|
+
const end = i + 1 < starts.length ? starts[i + 1] : units.length;
|
|
869
|
+
for (let k = end - 1; k >= starts[i]; k--) {
|
|
870
|
+
if (units[k].kind === 'space')
|
|
871
|
+
continue;
|
|
872
|
+
last.set(span.kunten, k);
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
for (const [mark, k] of last)
|
|
877
|
+
units[k].kunten = { mark };
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Lay out the kanbun marks of each unit that carries some (see
|
|
881
|
+
* `kuntenGeometry`): their runs, and the advance they add after the
|
|
882
|
+
* character, which joins the unit's width. Run once the units are final
|
|
883
|
+
* and the ruby readings sized (a 送り仮名 follows a reading on its side).
|
|
884
|
+
* A character inside a warichu note keeps no marks.
|
|
885
|
+
*/
|
|
886
|
+
function sizeKunten(units, letterSpacingPx) {
|
|
887
|
+
for (const u of units) {
|
|
888
|
+
if (!u.kunten || u.kunten.vdt || u.note || u.kind !== 'text')
|
|
889
|
+
continue;
|
|
890
|
+
const mark = u.kunten.mark;
|
|
891
|
+
if (!mark.kaeri && !mark.okuri && !mark.tate)
|
|
892
|
+
continue;
|
|
893
|
+
const em = emOfFont(u.style.font);
|
|
894
|
+
const g = u.ruby?.geometry;
|
|
895
|
+
// Where the character ends: the base inside a ruby box (a Japanese
|
|
896
|
+
// one, #422, spread by `tracking` after each character but its last),
|
|
897
|
+
// else the unit less the tracking after its last character.
|
|
898
|
+
const baseEnd = !g ? u.width - letterSpacingPx
|
|
899
|
+
: u.ruby.base !== undefined ? g.inset + u.ruby.base + (g.tracking ?? 0) * (u.graphemes - 1)
|
|
900
|
+
: u.ruby.position === 'right' ? u.width - g.rtWidth : u.width - g.inset;
|
|
901
|
+
let readingEnd;
|
|
902
|
+
if (g && u.ruby.position !== 'right') {
|
|
903
|
+
for (const r of g.runs)
|
|
904
|
+
readingEnd = Math.max(readingEnd ?? -Infinity, r.dx + flowTextWidth(r.text, r.fontString));
|
|
905
|
+
}
|
|
906
|
+
const over = u.ruby && u.ruby.position !== 'under';
|
|
907
|
+
const { extra, kunten } = kuntenGeometry({
|
|
908
|
+
...(mark.kaeri ? { kaeri: mark.kaeri } : {}),
|
|
909
|
+
...(mark.okuri ? { okuri: mark.okuri } : {}),
|
|
910
|
+
...(mark.tate ? { tate: true } : {}),
|
|
911
|
+
fontString: mark.fontString ?? withFontSize(u.style.font, em / 2),
|
|
912
|
+
...(mark.color ? { color: mark.color } : {}),
|
|
913
|
+
placement: mark.placement ?? 'inline',
|
|
914
|
+
em,
|
|
915
|
+
baseEnd,
|
|
916
|
+
width: u.width,
|
|
917
|
+
...(readingEnd !== undefined && over ? { readingOverEnd: readingEnd } : {}),
|
|
918
|
+
...(readingEnd !== undefined && !over ? { readingUnderEnd: readingEnd } : {}),
|
|
919
|
+
});
|
|
920
|
+
u.kunten = { mark, extra, vdt: kunten };
|
|
921
|
+
u.width += extra;
|
|
922
|
+
// Its Japanese ruby word is not laid out again at a line edge
|
|
923
|
+
// (`relayJisEdges`), so it widens no line there.
|
|
924
|
+
const word = u.ruby?.jis;
|
|
925
|
+
if (word)
|
|
926
|
+
for (const v of units)
|
|
927
|
+
if (v.ruby?.jis === word)
|
|
928
|
+
delete v.rubyGrow;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
/** A unit's characters (a mark pair's possible neighbour). */
|
|
932
|
+
function isPlainTextUnit(u) {
|
|
933
|
+
return u !== undefined && u.kind === 'text' && !u.token && !u.orient && !u.ruby && !u.kunten && !u.note && !u.stacked && !u.place;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Japanese vertical text: each pair of exclamation and question marks
|
|
937
|
+
* (!! !? ?! ?? and !! !? ?! ??, JLReq §3.1.10) becomes one upright cell
|
|
938
|
+
* of one em, as an author's `:tcy[…]` (`Unit.orient`), which breaks like a
|
|
939
|
+
* single ! and keeps a segment of its own (`VDTLineSegment.tcy`). The rule
|
|
940
|
+
* is `isUprightMarkPair`, read over what a segment would hold: a Western
|
|
941
|
+
* run that is the pair alone (`Wow!!` and `(!?)` stay one sideways run),
|
|
942
|
+
* or two full-width marks of one style and link, a mark beside them of the
|
|
943
|
+
* same style and link leaving all three as they are. The painters then
|
|
944
|
+
* read the segment as the measurer did.
|
|
945
|
+
*/
|
|
946
|
+
function combineMarkPairs(units, letterSpacingPx) {
|
|
947
|
+
const fullwidth = (g) => String.fromCodePoint(g.codePointAt(0) + 0xFEE0);
|
|
948
|
+
const pairUnit = (u, text, cls) => {
|
|
949
|
+
const out = {
|
|
950
|
+
...u,
|
|
951
|
+
text,
|
|
952
|
+
width: fontEm(u.style.font) + (letterSpacingPx === 0 ? 0 : letterSpacingPx),
|
|
953
|
+
graphemes: 1,
|
|
954
|
+
first: cls,
|
|
955
|
+
last: cls,
|
|
956
|
+
firstCjk: true,
|
|
957
|
+
lastCjk: true,
|
|
958
|
+
orient: 'tcy',
|
|
959
|
+
};
|
|
960
|
+
delete out.run;
|
|
961
|
+
delete out.cellStart;
|
|
962
|
+
delete out.cellEnd;
|
|
963
|
+
delete out.url;
|
|
964
|
+
return out;
|
|
965
|
+
};
|
|
966
|
+
const sameSegment = (a, b) => isPlainTextUnit(b) && !b.run && b.style.key === a.style.key && b.link === a.link;
|
|
967
|
+
for (let k = 0; k < units.length; k++) {
|
|
968
|
+
const u = units[k];
|
|
969
|
+
if (!isPlainTextUnit(u))
|
|
970
|
+
continue;
|
|
971
|
+
if (u.run) {
|
|
972
|
+
// A Western run that is the pair alone: its own segment.
|
|
973
|
+
if (u.graphemes === 2 && /^[!?]{2}$/.test(u.text) && isUprightMarkPair([...u.text], 0, 'japan')) {
|
|
974
|
+
units[k] = pairUnit(u, u.text, cjkClassOf(fullwidth(u.text[0])));
|
|
975
|
+
}
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
const v = units[k + 1];
|
|
979
|
+
if (u.graphemes !== 1 || !sameSegment(u, v) || v.graphemes !== 1)
|
|
980
|
+
continue;
|
|
981
|
+
const prev = units[k - 1];
|
|
982
|
+
const next = units[k + 2];
|
|
983
|
+
const context = [
|
|
984
|
+
...(sameSegment(u, prev) ? [lastGrapheme(prev.text)] : []),
|
|
985
|
+
u.text,
|
|
986
|
+
v.text,
|
|
987
|
+
...(sameSegment(u, next) ? [graphemesOf(next.text)[0]] : []),
|
|
988
|
+
];
|
|
989
|
+
if (!isUprightMarkPair(context, sameSegment(u, prev) ? 1 : 0, 'japan'))
|
|
990
|
+
continue;
|
|
991
|
+
units.splice(k, 2, pairUnit(u, u.text + v.text, cjkClassOf(u.text)));
|
|
992
|
+
}
|
|
993
|
+
}
|
|
626
994
|
/** The marks Latin text shares with Chinese (East Asian Width ambiguous):
|
|
627
995
|
* quotes, the ellipsis, the em dash and horizontal bar, interpuncts. */
|
|
628
996
|
const SHARED_MARKS = new Set(['\u201C', '\u201D', '\u2018', '\u2019', '\u2026', '\u22EF', '\u2014', '\u2015', '\u00B7', '\u2027']);
|
|
@@ -807,6 +1175,17 @@ function isMarkUnit(u) {
|
|
|
807
1175
|
return false;
|
|
808
1176
|
}
|
|
809
1177
|
}
|
|
1178
|
+
/** Kanji numerals a 、 or ・ is set solid between (二、三日 "two or three
|
|
1179
|
+
* days", 三・一四 "3.14", JLReq §3.1.3). */
|
|
1180
|
+
const KANJI_DIGITS = new Set('〇一二三四五六七八九十');
|
|
1181
|
+
/** ? and ! (and their doubles) as Japanese sets them: full-width marks
|
|
1182
|
+
* with a full-width space after them inside a paragraph. */
|
|
1183
|
+
const QUESTION_MARKS = new Set(['?', '!', '‼', '⁇', '⁈', '⁉']);
|
|
1184
|
+
/** A single character unit (not a run, a ruby base, a note…) whose text is
|
|
1185
|
+
* in `set`. */
|
|
1186
|
+
function charUnitIn(u, set) {
|
|
1187
|
+
return !!u && u.kind === 'text' && !u.run && u.graphemes === 1 && !u.ruby && !u.kunten && !u.note && !u.orient && !u.style.script && set.has(u.text);
|
|
1188
|
+
}
|
|
810
1189
|
/**
|
|
811
1190
|
* The punctuation widths and Han–Latin spaces of a paragraph's units, as
|
|
812
1191
|
* the composition sets them (see `cjkPunctuation.ts`): each full-width mark
|
|
@@ -817,7 +1196,9 @@ function isMarkUnit(u) {
|
|
|
817
1196
|
* edges (and the reduction a line makes to take one more character) are
|
|
818
1197
|
* the breaker's and the line's business. A plain composition changes
|
|
819
1198
|
* nothing but the mainland interpunct, half an em under every style
|
|
820
|
-
* (`punctuationBox`), as its cell is in vertical text.
|
|
1199
|
+
* (`punctuationBox`), as its cell is in vertical text. In Japan, a 、 or
|
|
1200
|
+
* ・ between kanji numerals is set solid (`Unit.solid`), and with
|
|
1201
|
+
* `spaceAfterQuestion` a ? or ! takes its one-em space (`Unit.aki`).
|
|
821
1202
|
*/
|
|
822
1203
|
function prepareUnits(units, c, letterSpacingPx) {
|
|
823
1204
|
const plain = isPlainComposition(c);
|
|
@@ -826,7 +1207,7 @@ function prepareUnits(units, c, letterSpacingPx) {
|
|
|
826
1207
|
return units;
|
|
827
1208
|
const full = new Map();
|
|
828
1209
|
for (const u of units) {
|
|
829
|
-
if (u.kind !== 'text' || u.run || u.graphemes !== 1 || !u.firstCjk || u.style.script || u.stacked || u.orient || u.ruby || u.note)
|
|
1210
|
+
if (u.kind !== 'text' || u.run || u.graphemes !== 1 || !u.firstCjk || u.style.script || u.stacked || u.orient || u.ruby || u.kunten || u.note)
|
|
830
1211
|
continue;
|
|
831
1212
|
if (plain && u.first !== 'interpunct')
|
|
832
1213
|
continue;
|
|
@@ -839,24 +1220,43 @@ function prepareUnits(units, c, letterSpacingPx) {
|
|
|
839
1220
|
}
|
|
840
1221
|
if (plain)
|
|
841
1222
|
return units;
|
|
1223
|
+
if (c.region === 'japan') {
|
|
1224
|
+
// 二、三日, 三・一四: the mark between two kanji numerals is its glyph
|
|
1225
|
+
// alone (JLReq §3.1.3), and the number is not broken there.
|
|
1226
|
+
for (let k = 1; k + 1 < units.length; k++) {
|
|
1227
|
+
const u = units[k];
|
|
1228
|
+
if (!u.punct || (u.text !== '、' && u.text !== '・'))
|
|
1229
|
+
continue;
|
|
1230
|
+
if (!charUnitIn(units[k - 1], KANJI_DIGITS) || !charUnitIn(units[k + 1], KANJI_DIGITS) || units[k + 1].glueBefore)
|
|
1231
|
+
continue;
|
|
1232
|
+
shrinkPunctuation(u.punct, u.punct.blank);
|
|
1233
|
+
u.width = full.get(u) - boxCut(u.punct);
|
|
1234
|
+
u.solid = true;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
842
1237
|
// Kaiming sets a stop's blank after the closing mark that follows it (。”␣).
|
|
843
1238
|
const carry = c.punctuationWidth === 'kaiming';
|
|
1239
|
+
const jlreq = isJlreqSpacing(c);
|
|
844
1240
|
if ((c.compressAdjacent || carry) && full.size > 1) {
|
|
845
1241
|
for (let k = 1; k < units.length; k++) {
|
|
846
1242
|
const a = units[k - 1];
|
|
847
1243
|
const b = units[k];
|
|
848
|
-
if (!a.punct || !b.punct || b.zwspBefore)
|
|
1244
|
+
if (!a.punct || !b.punct || b.zwspBefore || a.solid || b.solid)
|
|
849
1245
|
continue;
|
|
850
1246
|
if (c.compressAdjacent)
|
|
851
|
-
compressPair(a.punct, b.punct);
|
|
1247
|
+
compressPair(a.punct, b.punct, jlreq);
|
|
852
1248
|
if (carry)
|
|
853
1249
|
carryStopBlank(a.punct, b.punct);
|
|
854
1250
|
a.width = full.get(a) - boxCut(a.punct);
|
|
855
1251
|
b.width = full.get(b) - boxCut(b.punct);
|
|
856
1252
|
}
|
|
857
1253
|
}
|
|
858
|
-
|
|
859
|
-
|
|
1254
|
+
const spaced = (c.latinSpacing.px ?? c.latinSpacing.em ?? 0) > 0 ? latinSpaces(units, c) : units;
|
|
1255
|
+
return c.spaceAfterQuestion ? questionSpaces(spaced) : spaced;
|
|
1256
|
+
}
|
|
1257
|
+
/** `units` with the Han–Latin spaces of `cjk.latinSpacing` (see
|
|
1258
|
+
* {@link prepareUnits}). */
|
|
1259
|
+
function latinSpaces(units, c) {
|
|
860
1260
|
const out = [];
|
|
861
1261
|
const han = (u, edge) => !!u && u.kind === 'text' && !u.run && !u.note && u.firstCjk && u[edge] === 'ideograph' && !u.style.script && isLatinSpacingHan(u.text);
|
|
862
1262
|
// A run whose edge is a number set in one upright cell (vertical text)
|
|
@@ -899,6 +1299,62 @@ function prepareUnits(units, c, letterSpacingPx) {
|
|
|
899
1299
|
}
|
|
900
1300
|
return out;
|
|
901
1301
|
}
|
|
1302
|
+
/**
|
|
1303
|
+
* `units` with the one-em space JLReq §3.1.6 sets after a ? or ! that
|
|
1304
|
+
* ends a sentence inside the paragraph (`cjk.spaceAfterQuestion`,
|
|
1305
|
+
* `Unit.aki`): after the mark and what keeps to it (a note marker), unless
|
|
1306
|
+
* the paragraph ends there or a closing bracket or another ?! follows. A
|
|
1307
|
+
* space the author typed there (U+3000, or a run of word spaces) becomes
|
|
1308
|
+
* the aki, keeping its text. A line may break after it, and it goes at the
|
|
1309
|
+
* line end, as any space does.
|
|
1310
|
+
*/
|
|
1311
|
+
function questionSpaces(units) {
|
|
1312
|
+
const out = [];
|
|
1313
|
+
for (let k = 0; k < units.length; k++) {
|
|
1314
|
+
const u = units[k];
|
|
1315
|
+
out.push(u);
|
|
1316
|
+
if (!charUnitIn(u, QUESTION_MARKS))
|
|
1317
|
+
continue;
|
|
1318
|
+
// A note marker or a superscript keeps to the mark: the space follows it.
|
|
1319
|
+
while (k + 1 < units.length && units[k + 1].glueBefore)
|
|
1320
|
+
out.push(units[++k]);
|
|
1321
|
+
const next = units[k + 1];
|
|
1322
|
+
if (!next || charUnitIn(next, QUESTION_MARKS))
|
|
1323
|
+
continue;
|
|
1324
|
+
if (next.kind === 'text' && next.first === 'closing')
|
|
1325
|
+
continue;
|
|
1326
|
+
const aki = {
|
|
1327
|
+
kind: 'space',
|
|
1328
|
+
text: '',
|
|
1329
|
+
width: emOfFont(u.style.font),
|
|
1330
|
+
graphemes: 0,
|
|
1331
|
+
first: 'western',
|
|
1332
|
+
last: 'western',
|
|
1333
|
+
firstCjk: false,
|
|
1334
|
+
lastCjk: false,
|
|
1335
|
+
style: u.style,
|
|
1336
|
+
at: u.at + u.text.length,
|
|
1337
|
+
aki: true,
|
|
1338
|
+
};
|
|
1339
|
+
if (next.kind === 'space' && !next.auto) {
|
|
1340
|
+
// A typed space is replaced: the aki keeps its text.
|
|
1341
|
+
if (units[k + 2] === undefined)
|
|
1342
|
+
continue;
|
|
1343
|
+
out.push({ ...aki, text: next.text, at: next.at });
|
|
1344
|
+
k++;
|
|
1345
|
+
}
|
|
1346
|
+
else if (next.kind === 'text' && next.first === 'ideoSpace' && next.graphemes === 1) {
|
|
1347
|
+
if (units[k + 2] === undefined)
|
|
1348
|
+
continue;
|
|
1349
|
+
out.push({ ...aki, text: next.text, at: next.at });
|
|
1350
|
+
k++;
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
out.push(aki);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return out;
|
|
1357
|
+
}
|
|
902
1358
|
const isDigitCode = (c) => (c >= 0x30 && c <= 0x39) || isFullwidthDigit(c);
|
|
903
1359
|
/** Whether a number keeps to the sign or unit next to it (`50 %`, `50%`,
|
|
904
1360
|
* `¥599`, `−3 ℃`): a unit ending in a digit before a unit sign, or a
|
|
@@ -931,10 +1387,29 @@ function fullwidthGlue(units, k) {
|
|
|
931
1387
|
return true;
|
|
932
1388
|
return fullwidthUnit(a, isFullwidthNumberJoin) && fullwidthUnit(b, isFullwidthDigit) && fullwidthUnit(units[k - 2], isFullwidthDigit);
|
|
933
1389
|
}
|
|
1390
|
+
/** The grapheme a unit opens (`first`) or ends (`last`) with, which a
|
|
1391
|
+
* Japanese level reads the unit's class from (small kana, ー, hyphens…:
|
|
1392
|
+
* `breakClassOf`); undefined under a Chinese level, and for an inline box
|
|
1393
|
+
* (a chip, a formula, a marker), whose class stands as it is. */
|
|
1394
|
+
function edgeGrapheme(u, edge, level) {
|
|
1395
|
+
if (u.kind !== 'text' || u.text === '' || !isJapaneseLineBreak(level))
|
|
1396
|
+
return undefined;
|
|
1397
|
+
return edge === 'first' ? String.fromCodePoint(u.text.codePointAt(0)) : lastGrapheme(u.text);
|
|
1398
|
+
}
|
|
1399
|
+
/** Whether unit `b` may not open a line at `level` (see
|
|
1400
|
+
* {@link edgeGrapheme}). */
|
|
1401
|
+
function startProhibited(b, level) {
|
|
1402
|
+
return isLineStartProhibited(b.first, level, edgeGrapheme(b, 'first', level));
|
|
1403
|
+
}
|
|
1404
|
+
/** Whether unit `a` may not close a line at `level`. */
|
|
1405
|
+
function endProhibited(a, level) {
|
|
1406
|
+
return isLineEndProhibited(a.last, level, edgeGrapheme(a, 'last', level));
|
|
1407
|
+
}
|
|
934
1408
|
/** Whether a line may break before each unit (index 0 is never a break).
|
|
935
1409
|
* A word space is a break unless the unit after it may not open a line,
|
|
936
1410
|
* the last one before it may not close one, or they are a number and its
|
|
937
|
-
* sign; a zero-width space is a break at every level.
|
|
1411
|
+
* sign; a zero-width space is a break at every level. A kanji number
|
|
1412
|
+
* never breaks at the 、 or ・ set solid inside it (`Unit.solid`). */
|
|
938
1413
|
function breakOpportunities(units, level) {
|
|
939
1414
|
const out = new Uint8Array(units.length);
|
|
940
1415
|
// The last unit before `k` that is not a space.
|
|
@@ -955,7 +1430,7 @@ function breakOpportunities(units, level) {
|
|
|
955
1430
|
}
|
|
956
1431
|
if (a.kind === 'space') {
|
|
957
1432
|
const p = ink >= 0 ? units[ink] : undefined;
|
|
958
|
-
if (!p || (!numberGlue(p, b) && !
|
|
1433
|
+
if (!p || (!numberGlue(p, b) && !startProhibited(b, level) && !endProhibited(p, level)))
|
|
959
1434
|
out[k] = 1;
|
|
960
1435
|
continue;
|
|
961
1436
|
}
|
|
@@ -965,15 +1440,15 @@ function breakOpportunities(units, level) {
|
|
|
965
1440
|
while (q > 0 && units[q].noteSpace)
|
|
966
1441
|
q--;
|
|
967
1442
|
const p = units[q];
|
|
968
|
-
if (!p.noteSpace && !numberGlue(p, b) && !
|
|
1443
|
+
if (!p.noteSpace && !numberGlue(p, b) && !startProhibited(b, level) && !endProhibited(p, level))
|
|
969
1444
|
out[k] = 1;
|
|
970
1445
|
continue;
|
|
971
1446
|
}
|
|
972
|
-
if (b.glueBefore)
|
|
1447
|
+
if (b.glueBefore || a.solid || b.solid)
|
|
973
1448
|
continue;
|
|
974
1449
|
if (numberGlue(a, b) || fullwidthGlue(units, k))
|
|
975
1450
|
continue;
|
|
976
|
-
if (cjkBreakAllowed(a.last, a.lastCjk, b.first, b.firstCjk, level))
|
|
1451
|
+
if (cjkBreakAllowed(a.last, a.lastCjk, b.first, b.firstCjk, level, edgeGrapheme(a, 'last', level), edgeGrapheme(b, 'first', level)))
|
|
977
1452
|
out[k] = 1;
|
|
978
1453
|
}
|
|
979
1454
|
return out;
|
|
@@ -982,10 +1457,16 @@ function breakOpportunities(units, level) {
|
|
|
982
1457
|
* line: between characters, never inside a Western run or a 2-em mark
|
|
983
1458
|
* (those are single units), never between two Western runs, next to a
|
|
984
1459
|
* connector or a solidus, after an atomic box, or before a mark that
|
|
985
|
-
* keeps to the text before it. Spaces take their own share.
|
|
986
|
-
|
|
1460
|
+
* keeps to the text before it. Spaces take their own share. With `jlreq`
|
|
1461
|
+
* (Japanese full-width text) also JLReq §3.1.11's inseparable places:
|
|
1462
|
+
* never after an opening bracket or before a closing one (but before the
|
|
1463
|
+
* one and after the other), never next to 、,。.・:;?! or U+3000. */
|
|
1464
|
+
function gapStretches(a, b, jlreq = false) {
|
|
987
1465
|
if (a.kind !== 'text' || b.kind === 'space')
|
|
988
1466
|
return false;
|
|
1467
|
+
// Never inside a jukugo word (JLReq §3.3.7).
|
|
1468
|
+
if (a.ruby?.jis && a.ruby.jis === b.ruby?.jis)
|
|
1469
|
+
return false;
|
|
989
1470
|
if (b.glueBefore || a.stacked)
|
|
990
1471
|
return false;
|
|
991
1472
|
if (a.last === 'connector' || a.last === 'solidus' || b.first === 'connector' || b.first === 'solidus')
|
|
@@ -994,16 +1475,43 @@ function gapStretches(a, b) {
|
|
|
994
1475
|
return false;
|
|
995
1476
|
if (a.last === b.first && (a.last === 'dash' || a.last === 'ellipsis'))
|
|
996
1477
|
return false;
|
|
1478
|
+
if (jlreq && (a.last === 'opening' || b.first === 'closing' || jlreqFixed(a.last) || jlreqFixed(b.first)))
|
|
1479
|
+
return false;
|
|
997
1480
|
return true;
|
|
998
1481
|
}
|
|
1482
|
+
/** The classes JLReq never spreads next to (§3.1.11): pause and stop
|
|
1483
|
+
* marks, middle dots and the ideographic space. */
|
|
1484
|
+
function jlreqFixed(cls) {
|
|
1485
|
+
return cls === 'pause' || cls === 'stop' || cls === 'interpunct' || cls === 'ideoSpace';
|
|
1486
|
+
}
|
|
1487
|
+
/** Whether the gap after unit `j` of a line takes tracking. A footnote
|
|
1488
|
+
* marker in the line gap (`footnotes.markerPosition: 'side'`) takes no
|
|
1489
|
+
* room in the line: the gap between the characters around it is theirs,
|
|
1490
|
+
* and is set after the marker, which stays against the character it
|
|
1491
|
+
* marks. */
|
|
1492
|
+
function gapAfterStretches(us, j, jlreq = false) {
|
|
1493
|
+
const next = us[j + 1];
|
|
1494
|
+
if (isSideMarker(next))
|
|
1495
|
+
return false;
|
|
1496
|
+
const u = us[j];
|
|
1497
|
+
if (isSideMarker(u))
|
|
1498
|
+
return j > 0 && gapStretches(us[j - 1], next, jlreq);
|
|
1499
|
+
return gapStretches(u, next, jlreq);
|
|
1500
|
+
}
|
|
1501
|
+
function isSideMarker(u) {
|
|
1502
|
+
return u.token?.sideRuns !== undefined;
|
|
1503
|
+
}
|
|
999
1504
|
function lineFitOf(c) {
|
|
1000
1505
|
if (isPlainComposition(c))
|
|
1001
1506
|
return undefined;
|
|
1507
|
+
const jlreq = isJlreqSpacing(c);
|
|
1002
1508
|
return {
|
|
1003
1509
|
lead: (u) => (u.punct ? lineEdgeCut(u.punct, c, true, false) : 0),
|
|
1004
1510
|
tail: (u) => (u.punct ? lineEdgeCut(u.punct, c, false, true) : 0),
|
|
1005
1511
|
give(u, atStart, atEnd) {
|
|
1006
1512
|
if (u.kind === 'space') {
|
|
1513
|
+
if (u.aki)
|
|
1514
|
+
return 0;
|
|
1007
1515
|
const em = emOfFont(u.style.font);
|
|
1008
1516
|
return Math.max(0, u.width - (u.auto ? em / 8 : em / 4));
|
|
1009
1517
|
}
|
|
@@ -1013,18 +1521,21 @@ function lineFitOf(c) {
|
|
|
1013
1521
|
return punctuationShrink(u.punct, c);
|
|
1014
1522
|
const box = { ...u.punct };
|
|
1015
1523
|
applyLineEdges(box, c, atStart, atEnd);
|
|
1016
|
-
|
|
1524
|
+
// JLReq: a mark ending the line gives up the blank after its glyph
|
|
1525
|
+
// (and, a middle dot, nothing before it: §3.8.3 steps 2–3).
|
|
1526
|
+
return punctuationShrink(box, c, jlreq && atEnd);
|
|
1017
1527
|
},
|
|
1018
1528
|
edge: (u, start, end) => (u.punct ? applyLineEdges(u.punct, c, start, end) : 0),
|
|
1019
1529
|
hangs(units, k, unitAt) {
|
|
1020
1530
|
const u = unitAt(k);
|
|
1021
|
-
if (u.kind !== 'text' || u.run || u.graphemes !== 1 || u.note || u.ruby || !mayHang(u.text, u.first, c))
|
|
1531
|
+
if (u.kind !== 'text' || u.run || u.graphemes !== 1 || u.note || u.ruby || u.kunten || !mayHang(u.text, u.first, c))
|
|
1022
1532
|
return false;
|
|
1023
1533
|
if (k > 0 && isMarkUnit(unitAt(k - 1)))
|
|
1024
1534
|
return false;
|
|
1025
1535
|
return k + 1 >= units.length || !isMarkUnit(units[k + 1]);
|
|
1026
1536
|
},
|
|
1027
1537
|
hanging: c.hangingPunctuation,
|
|
1538
|
+
jlreq,
|
|
1028
1539
|
};
|
|
1029
1540
|
}
|
|
1030
1541
|
/** The last unit that must share a line with unit `k` when `k` ends one:
|
|
@@ -1158,11 +1669,15 @@ function divideRun(u, room, letterSpacingPx) {
|
|
|
1158
1669
|
* unit). A space never overflows: the line ends before it, when the line
|
|
1159
1670
|
* may break after it. A unit wider
|
|
1160
1671
|
* than the whole line is divided when it is a Western run (a web address
|
|
1161
|
-
* at a joint first), else set on a line of its own.
|
|
1672
|
+
* at a joint first), else set on a line of its own. `pull` names a line
|
|
1673
|
+
* that takes in the rest of the paragraph when the line can give up the
|
|
1674
|
+
* blank for it (push-in, to save a last line of one character).
|
|
1162
1675
|
*/
|
|
1163
1676
|
function breakUnits(units, breaks, measureOf, reserve, letterSpacingPx, fit, level = getCjkLineBreak(),
|
|
1164
1677
|
/** End line `line` before unit `at` (a break), whatever else fits. */
|
|
1165
|
-
stop
|
|
1678
|
+
stop,
|
|
1679
|
+
/** Line `pull` takes every unit left when it can give up the blank. */
|
|
1680
|
+
pull) {
|
|
1166
1681
|
const out = [];
|
|
1167
1682
|
const n = units.length;
|
|
1168
1683
|
let i = 0;
|
|
@@ -1176,15 +1691,23 @@ stop) {
|
|
|
1176
1691
|
let noteBase = 0;
|
|
1177
1692
|
const foldOf = (from, to) => {
|
|
1178
1693
|
const widths = [];
|
|
1179
|
-
const
|
|
1180
|
-
const
|
|
1694
|
+
const startNo = [];
|
|
1695
|
+
const endNo = [];
|
|
1181
1696
|
for (let q = from; q <= to; q++) {
|
|
1182
1697
|
const uq = unitAt(q);
|
|
1183
1698
|
widths.push(uq.width);
|
|
1184
|
-
|
|
1185
|
-
|
|
1699
|
+
startNo.push(noteRowStartProhibited(uq, level));
|
|
1700
|
+
endNo.push(endProhibited(uq, level));
|
|
1186
1701
|
}
|
|
1187
|
-
return foldWidth(widths, splitNote(widths,
|
|
1702
|
+
return foldWidth(widths, splitNote(widths, startNo, endNo));
|
|
1703
|
+
};
|
|
1704
|
+
// A Japanese ruby base at a line edge widens the line (`rubyGrow`): at
|
|
1705
|
+
// its start, and at its end when the line may end after it.
|
|
1706
|
+
const endGrow = (k) => {
|
|
1707
|
+
const g = unitAt(k).rubyGrow;
|
|
1708
|
+
if (!g || g.end <= 0)
|
|
1709
|
+
return 0;
|
|
1710
|
+
return k + 1 >= n || breaks[k + 1] || units[k + 1].kind === 'space' ? g.end : 0;
|
|
1188
1711
|
};
|
|
1189
1712
|
for (let li = 0;; li++) {
|
|
1190
1713
|
while (i < n && unitAt(i).kind === 'space')
|
|
@@ -1192,7 +1715,7 @@ stop) {
|
|
|
1192
1715
|
if (i >= n)
|
|
1193
1716
|
break;
|
|
1194
1717
|
const max = measureOf(li) - reserve;
|
|
1195
|
-
let w = 0;
|
|
1718
|
+
let w = Math.max(0, unitAt(i).rubyGrow?.start ?? 0);
|
|
1196
1719
|
let lastBreak = -1;
|
|
1197
1720
|
let end = -1;
|
|
1198
1721
|
let head;
|
|
@@ -1226,7 +1749,7 @@ stop) {
|
|
|
1226
1749
|
// line may end with was one that folded).
|
|
1227
1750
|
}
|
|
1228
1751
|
const lead = fit && k === i ? fit.lead(u) : 0;
|
|
1229
|
-
if (!u.note && w + u.width - lead - (fit ? fit.tail(u) : 0) <= max + FIT_EPS) {
|
|
1752
|
+
if (!u.note && w + u.width - lead - (fit ? fit.tail(u) : 0) + endGrow(k) <= max + FIT_EPS) {
|
|
1230
1753
|
w += u.width - lead;
|
|
1231
1754
|
if (fit)
|
|
1232
1755
|
give += fit.give(u, k === i, false);
|
|
@@ -1248,13 +1771,13 @@ stop) {
|
|
|
1248
1771
|
hang = true;
|
|
1249
1772
|
break;
|
|
1250
1773
|
}
|
|
1251
|
-
const m = breaks[k] ? -1 : groupEnd(units, breaks, k);
|
|
1774
|
+
const m = pull === li ? n - 1 : breaks[k] ? -1 : groupEnd(units, breaks, k);
|
|
1252
1775
|
if (m >= k) {
|
|
1253
1776
|
let width = w;
|
|
1254
1777
|
let room = give;
|
|
1255
1778
|
for (let q = k; q <= m; q++) {
|
|
1256
1779
|
const uq = unitAt(q);
|
|
1257
|
-
const t = q === m ? fit.tail(uq) : 0;
|
|
1780
|
+
const t = q === m ? fit.tail(uq) - endGrow(q) : 0;
|
|
1258
1781
|
width += uq.width - t;
|
|
1259
1782
|
room += fit.give(uq, false, q === m);
|
|
1260
1783
|
}
|
|
@@ -1342,9 +1865,10 @@ stop) {
|
|
|
1342
1865
|
return out;
|
|
1343
1866
|
}
|
|
1344
1867
|
/** Whether a note's unit may not open its lower row: a mark that may not
|
|
1345
|
-
* start a line
|
|
1868
|
+
* start a line (the small kana and the rest of the Japanese classes under
|
|
1869
|
+
* a Japanese level), or a word space (it ends the upper row instead). */
|
|
1346
1870
|
function noteRowStartProhibited(u, level) {
|
|
1347
|
-
return u.noteSpace === true ||
|
|
1871
|
+
return u.noteSpace === true || startProhibited(u, level);
|
|
1348
1872
|
}
|
|
1349
1873
|
/** Replace each run of a warichu note's characters on a line by the part
|
|
1350
1874
|
* folded into its two rows (see `cjkAnnotate.ts`). `us` is changed in
|
|
@@ -1372,7 +1896,7 @@ function foldNotes(us, level, em) {
|
|
|
1372
1896
|
function noteFragment(part, level, em) {
|
|
1373
1897
|
const note = part[0].note;
|
|
1374
1898
|
const widths = part.map((u) => u.width);
|
|
1375
|
-
const at = splitNote(widths, part.map((u) => noteRowStartProhibited(u, level)), part.map((u) =>
|
|
1899
|
+
const at = splitNote(widths, part.map((u) => noteRowStartProhibited(u, level)), part.map((u) => endProhibited(u, level)));
|
|
1376
1900
|
const noteFont = note.fontString ?? part[0].style.font;
|
|
1377
1901
|
const rows = noteRowBaselines(em, fontEm(noteFont));
|
|
1378
1902
|
const runs = [];
|
|
@@ -1435,7 +1959,9 @@ function alignEdgeRubies(us) {
|
|
|
1435
1959
|
const align = (j, start) => {
|
|
1436
1960
|
const u = us[j];
|
|
1437
1961
|
const g = u.ruby?.geometry;
|
|
1438
|
-
|
|
1962
|
+
// A base with kanbun marks keeps its reading where the marks were set
|
|
1963
|
+
// against it (#430).
|
|
1964
|
+
if (!g || u.ruby.position === 'right' || u.ruby.jis || g.runs.length === 0 || u.kunten)
|
|
1439
1965
|
return;
|
|
1440
1966
|
const base = u.width - 2 * g.inset;
|
|
1441
1967
|
const rt = g.rtWidth;
|
|
@@ -1471,7 +1997,8 @@ function clampReadings(segments) {
|
|
|
1471
1997
|
let x = 0;
|
|
1472
1998
|
for (const s of segments) {
|
|
1473
1999
|
const ruby = s.ruby;
|
|
1474
|
-
|
|
2000
|
+
// A Japanese reading (`id`) was set inside the line already (#422).
|
|
2001
|
+
if (ruby && ruby.position !== 'right' && ruby.id === undefined && ruby.runs.length > 0) {
|
|
1475
2002
|
const lo = ruby.runs[0].dx;
|
|
1476
2003
|
const hi = lo + ruby.rtWidth;
|
|
1477
2004
|
let shift = 0;
|
|
@@ -1508,9 +2035,16 @@ function spread(amount, caps) {
|
|
|
1508
2035
|
* character that may not open a line, clreq §6.2.2.3) gives up
|
|
1509
2036
|
* blank in clreq's order — word spaces to a quarter em, interpuncts,
|
|
1510
2037
|
* brackets, pause marks, Han–Latin spaces to an eighth of an em, stop marks
|
|
1511
|
-
* last — each step shared equally, until it fits.
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
2038
|
+
* last — each step shared equally, until it fits. Japanese full-width text
|
|
2039
|
+
* takes JLReq §3.8.3's order instead (`jlreqShrinkStep`): word spaces, the
|
|
2040
|
+
* half em after the line's last mark and then a middle dot's quarter (each
|
|
2041
|
+
* all or nothing: what it gives beyond the need is spread back when the
|
|
2042
|
+
* line is justified), the middle dots' quarters inside the line, the half
|
|
2043
|
+
* ems of brackets and 、, inside it, Han–Latin spaces; never the half em
|
|
2044
|
+
* after a 。 inside the line. A mark that hangs keeps its glyph alone. `us`
|
|
2045
|
+
* holds the line's units and is changed in place; a unit that changes is
|
|
2046
|
+
* replaced by a copy, since the paragraph's units serve every attempt at
|
|
2047
|
+
* breaking it.
|
|
1514
2048
|
*/
|
|
1515
2049
|
function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
1516
2050
|
const copy = (j) => {
|
|
@@ -1527,6 +2061,9 @@ function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
|
1527
2061
|
hang = copy(us.length - 1);
|
|
1528
2062
|
us.pop();
|
|
1529
2063
|
hang.width -= fit.edge(hang, false, true);
|
|
2064
|
+
// ぶら下げ: the hung mark is its glyph, past the measure.
|
|
2065
|
+
if (fit.jlreq && hang.punct)
|
|
2066
|
+
hang.width -= shrinkPunctuation(hang.punct, hang.punct.blank, true);
|
|
1530
2067
|
while (us.length > 0 && us[us.length - 1].kind === 'space')
|
|
1531
2068
|
us.pop();
|
|
1532
2069
|
}
|
|
@@ -1549,17 +2086,27 @@ function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
|
1549
2086
|
if (over <= FIT_EPS)
|
|
1550
2087
|
return hang;
|
|
1551
2088
|
// Steps of the reduction order: word spaces (1), interpuncts (2),
|
|
1552
|
-
// brackets (3), pause marks (4), Han–Latin spaces (5), stop marks (6)
|
|
1553
|
-
|
|
2089
|
+
// brackets (3), pause marks (4), Han–Latin spaces (5), stop marks (6);
|
|
2090
|
+
// in JLReq spacing, word spaces (1), the line end (2, 3), middle dots
|
|
2091
|
+
// (4), brackets and 、, (5), Han–Latin spaces (6).
|
|
2092
|
+
const last = us.length - 1;
|
|
2093
|
+
const atEnd = (j) => fit.jlreq && j === last;
|
|
2094
|
+
const stepOf = (u, j) => {
|
|
2095
|
+
if (u.kind === 'space')
|
|
2096
|
+
return u.aki ? 0 : u.auto ? (fit.jlreq ? 6 : 5) : 1;
|
|
2097
|
+
if (!u.punct)
|
|
2098
|
+
return 0;
|
|
2099
|
+
return fit.jlreq ? jlreqShrinkStep(u.punct, atEnd(j)) : shrinkStep(u.punct);
|
|
2100
|
+
};
|
|
1554
2101
|
for (let step = 1; step <= 6 && over > FIT_EPS; step++) {
|
|
1555
2102
|
const members = [];
|
|
1556
2103
|
const caps = [];
|
|
1557
2104
|
for (let j = 0; j < us.length; j++) {
|
|
1558
2105
|
const u = us[j];
|
|
1559
|
-
if (stepOf(u) !== step)
|
|
2106
|
+
if (stepOf(u, j) !== step)
|
|
1560
2107
|
continue;
|
|
1561
2108
|
// The marks at the edges already gave up their lead and tail.
|
|
1562
|
-
const cap = fit.give(u, false,
|
|
2109
|
+
const cap = fit.give(u, false, atEnd(j));
|
|
1563
2110
|
if (cap <= 0)
|
|
1564
2111
|
continue;
|
|
1565
2112
|
members.push(j);
|
|
@@ -1567,13 +2114,15 @@ function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
|
1567
2114
|
}
|
|
1568
2115
|
if (members.length === 0)
|
|
1569
2116
|
continue;
|
|
1570
|
-
|
|
2117
|
+
// The blank at a JLReq line end goes whole or not at all (§3.1.9).
|
|
2118
|
+
const takes = fit.jlreq && (step === 2 || step === 3) ? caps : spread(over, caps);
|
|
1571
2119
|
for (let m = 0; m < members.length; m++) {
|
|
1572
2120
|
const take = takes[m];
|
|
1573
2121
|
if (take <= 0)
|
|
1574
2122
|
continue;
|
|
1575
|
-
const
|
|
1576
|
-
const
|
|
2123
|
+
const j = members[m];
|
|
2124
|
+
const c = copy(j);
|
|
2125
|
+
const given = c.punct ? shrinkPunctuation(c.punct, take, atEnd(j)) : take;
|
|
1577
2126
|
c.width -= given;
|
|
1578
2127
|
over -= given;
|
|
1579
2128
|
}
|
|
@@ -1609,7 +2158,10 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1609
2158
|
// A warichu note's characters fold into their two rows (#195).
|
|
1610
2159
|
if (us.some((u) => u.note))
|
|
1611
2160
|
foldNotes(us, ctx.level, ctx.em);
|
|
1612
|
-
// A ruby base at either edge aligns to it with its reading (#194)
|
|
2161
|
+
// A ruby base at either edge aligns to it with its reading (#194); a
|
|
2162
|
+
// Japanese one is laid out again there (#422).
|
|
2163
|
+
if (us.some((u) => u.ruby?.jis))
|
|
2164
|
+
relayJisEdges(us);
|
|
1613
2165
|
alignEdgeRubies(us);
|
|
1614
2166
|
const hang = ctx.fit ? fitLine(us, units, range, lastK, isLast, measure, ctx.fit) : undefined;
|
|
1615
2167
|
const justify = ctx.textAlign === 'justify' && !isLast;
|
|
@@ -1628,9 +2180,15 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1628
2180
|
// Han–Latin spaces (`cjk.latinSpacing`): they flex apart from word
|
|
1629
2181
|
// spaces, and the renderers leave them as set.
|
|
1630
2182
|
const autos = [];
|
|
2183
|
+
const jlreq = ctx.fit?.jlreq === true;
|
|
1631
2184
|
for (let j = 0; j < us.length; j++) {
|
|
1632
2185
|
const u = us[j];
|
|
1633
2186
|
content += u.width;
|
|
2187
|
+
// The space after ?! keeps its em (JLReq §3.1.6).
|
|
2188
|
+
if (u.kind === 'space' && u.aki) {
|
|
2189
|
+
stretches.push(false);
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
1634
2192
|
if (u.kind === 'space' && u.auto)
|
|
1635
2193
|
autos.push(j);
|
|
1636
2194
|
else if (u.kind === 'space') {
|
|
@@ -1638,7 +2196,7 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1638
2196
|
natural += u.width;
|
|
1639
2197
|
widest = Math.max(widest, u.width);
|
|
1640
2198
|
}
|
|
1641
|
-
const s = j < us.length - 1 &&
|
|
2199
|
+
const s = j < us.length - 1 && gapAfterStretches(us, j, jlreq);
|
|
1642
2200
|
stretches.push(s);
|
|
1643
2201
|
if (s)
|
|
1644
2202
|
gaps++;
|
|
@@ -1727,6 +2285,7 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1727
2285
|
...(s.marks ? { cjkMarks: s.marks } : {}),
|
|
1728
2286
|
...(s.inserted ? { inserted: true } : {}),
|
|
1729
2287
|
...(s.color ? { color: s.color } : {}),
|
|
2288
|
+
...(s.lang !== undefined ? { lang: s.lang } : {}),
|
|
1730
2289
|
...(t !== undefined ? { tracking: t } : {}),
|
|
1731
2290
|
// Every mark that gave up blank carries its ink offset (0 when only
|
|
1732
2291
|
// the blank after its glyph went), and so does a shared mark set in
|
|
@@ -1738,9 +2297,38 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1738
2297
|
...(u.shift !== undefined ? { baselineShift: u.shift } : {}),
|
|
1739
2298
|
};
|
|
1740
2299
|
};
|
|
2300
|
+
/** The segment of a ruby base `width` px wide, `t` its tracking. */
|
|
2301
|
+
const rubySegment = (u, width, t) => {
|
|
2302
|
+
const ruby = u.ruby;
|
|
2303
|
+
const g = ruby.geometry;
|
|
2304
|
+
// The box less the advance kanbun marks add after it (#430).
|
|
2305
|
+
const box = u.width - (u.kunten?.extra ?? 0);
|
|
2306
|
+
// A Japanese ruby (#422) names its annotation (`id`), so renderers
|
|
2307
|
+
// can set one `<ruby>` for a word.
|
|
2308
|
+
const jis = ruby.jis !== undefined;
|
|
2309
|
+
return {
|
|
2310
|
+
...segmentOf(u, width, t),
|
|
2311
|
+
text: u.text,
|
|
2312
|
+
...(g.inset > 1e-9 ? { inkOffset: g.inset } : {}),
|
|
2313
|
+
// A base spread 1:2:1 under a longer Japanese reading.
|
|
2314
|
+
...(g.tracking ? { tracking: g.tracking } : {}),
|
|
2315
|
+
ruby: {
|
|
2316
|
+
text: ruby.span.text,
|
|
2317
|
+
fontString: ruby.span.fontString ?? g.runs[0]?.fontString ?? u.style.font,
|
|
2318
|
+
baseWidth: ruby.base ?? box - g.inset * 2 - (ruby.position === 'right' ? g.rtWidth : 0),
|
|
2319
|
+
rtWidth: g.rtWidth,
|
|
2320
|
+
position: ruby.position,
|
|
2321
|
+
...(ruby.span.group ? { group: true } : {}),
|
|
2322
|
+
...(ruby.span.color ? { color: ruby.span.color } : {}),
|
|
2323
|
+
...(jis ? { id: ruby.span.id } : {}),
|
|
2324
|
+
...(jis && ruby.span.jukugo ? { jukugo: true } : {}),
|
|
2325
|
+
runs: g.runs,
|
|
2326
|
+
},
|
|
2327
|
+
};
|
|
2328
|
+
};
|
|
1741
2329
|
for (let j = 0; j < us.length; j++) {
|
|
1742
2330
|
const u = us[j];
|
|
1743
|
-
if (u.kind === 'space' && u.auto) {
|
|
2331
|
+
if (u.kind === 'space' && (u.auto || u.aki)) {
|
|
1744
2332
|
pieces.push({ seg: { kind: 'space', text: u.text, width: u.width, autospace: true }, parts: [u.text], key: undefined });
|
|
1745
2333
|
continue;
|
|
1746
2334
|
}
|
|
@@ -1750,6 +2338,9 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1750
2338
|
}
|
|
1751
2339
|
if (u.kind === 'atomic') {
|
|
1752
2340
|
const seg = tokenSegment(u.token);
|
|
2341
|
+
// A side marker carries the gap after the character it marks.
|
|
2342
|
+
if (seg.sideMarker && tracking > 0 && stretches[j])
|
|
2343
|
+
seg.width += tracking;
|
|
1753
2344
|
pieces.push({ seg, parts: [seg.text], key: undefined });
|
|
1754
2345
|
continue;
|
|
1755
2346
|
}
|
|
@@ -1759,6 +2350,21 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1759
2350
|
pieces.push({ seg: { kind: 'text', text: u.text, width: u.width + gap, warichu: u.warichu }, parts: [u.text], key: undefined });
|
|
1760
2351
|
continue;
|
|
1761
2352
|
}
|
|
2353
|
+
if (u.kunten?.vdt) {
|
|
2354
|
+
// A character with kanbun marks (#430): a segment of its own, the
|
|
2355
|
+
// marks painted from its start; the room they take after it (inline
|
|
2356
|
+
// 返り点, a 竪点, 送り仮名 that run past it) and the gap follow as a
|
|
2357
|
+
// space of their own, so the character is painted at its natural
|
|
2358
|
+
// spacing (a ruby base in it centred under its reading).
|
|
2359
|
+
const extra = u.kunten.extra ?? 0;
|
|
2360
|
+
const own = u.width - extra;
|
|
2361
|
+
const seg = u.ruby?.geometry ? rubySegment(u, own, undefined) : { ...segmentOf(u, own, undefined), text: u.text };
|
|
2362
|
+
seg.kunten = u.kunten.vdt;
|
|
2363
|
+
pieces.push({ seg, parts: [u.text], key: undefined });
|
|
2364
|
+
if (extra + gap > 0)
|
|
2365
|
+
pieces.push({ seg: { kind: 'space', text: '', width: extra + gap, autospace: true }, parts: [''], key: undefined });
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
1762
2368
|
if (u.ruby?.geometry) {
|
|
1763
2369
|
// A ruby base: centred in its box, its reading placed from the box's
|
|
1764
2370
|
// start (#194). One character takes the gap after it as tracking; a
|
|
@@ -1766,23 +2372,8 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1766
2372
|
// reading, and the gap follows the box as a space of its own (its
|
|
1767
2373
|
// width final, as a Han–Latin space's is): tracking would be added
|
|
1768
2374
|
// after each of its characters.
|
|
1769
|
-
const g = u.ruby.geometry;
|
|
1770
2375
|
const gapAfter = gap > 0 && u.graphemes > 1;
|
|
1771
|
-
const seg =
|
|
1772
|
-
...segmentOf(u, gapAfter ? u.width : u.width + gap, gap > 0 && !gapAfter ? gap : undefined),
|
|
1773
|
-
text: u.text,
|
|
1774
|
-
...(g.inset > 1e-9 ? { inkOffset: g.inset } : {}),
|
|
1775
|
-
ruby: {
|
|
1776
|
-
text: u.ruby.span.text,
|
|
1777
|
-
fontString: u.ruby.span.fontString ?? g.runs[0]?.fontString ?? u.style.font,
|
|
1778
|
-
baseWidth: u.width - g.inset * 2 - (u.ruby.position === 'right' ? g.rtWidth : 0),
|
|
1779
|
-
rtWidth: g.rtWidth,
|
|
1780
|
-
position: u.ruby.position,
|
|
1781
|
-
...(u.ruby.span.group ? { group: true } : {}),
|
|
1782
|
-
...(u.ruby.span.color ? { color: u.ruby.span.color } : {}),
|
|
1783
|
-
runs: g.runs,
|
|
1784
|
-
},
|
|
1785
|
-
};
|
|
2376
|
+
const seg = rubySegment(u, gapAfter ? u.width : u.width + gap, gap > 0 && !gapAfter ? gap : undefined);
|
|
1786
2377
|
pieces.push({ seg, parts: [u.text], key: undefined });
|
|
1787
2378
|
if (gapAfter)
|
|
1788
2379
|
pieces.push({ seg: { kind: 'space', text: '', width: gap, autospace: true }, parts: [''], key: undefined });
|
|
@@ -1848,6 +2439,40 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1848
2439
|
cjkComposed: true,
|
|
1849
2440
|
};
|
|
1850
2441
|
}
|
|
2442
|
+
/**
|
|
2443
|
+
* The first line's indent of a paragraph that opens with an opening
|
|
2444
|
+
* bracket, as `cjk.paragraphStartBracket` sets it (JLReq §3.1.5), with the
|
|
2445
|
+
* bracket's blank before its glyph given up (it is `units`' own unit,
|
|
2446
|
+
* replaced): pattern ① (`indent`) keeps the indent, ③ (`half`) sets the
|
|
2447
|
+
* bracket's glyph inside the indent so the text after it starts where the
|
|
2448
|
+
* indent ends (the bracket and its half em of blank fill a one-em indent),
|
|
2449
|
+
* 天付き (`flush`) drops the indent. `indentPx` when the composition leaves
|
|
2450
|
+
* the bracket to the line-start rules, the paragraph has no indent or
|
|
2451
|
+
* does not open with a bracket.
|
|
2452
|
+
*/
|
|
2453
|
+
function paragraphStartIndent(units, indentPx, c) {
|
|
2454
|
+
const pattern = c.paragraphStartBracket;
|
|
2455
|
+
if (!pattern || indentPx <= 0)
|
|
2456
|
+
return indentPx;
|
|
2457
|
+
const f = units.findIndex((u) => u.kind !== 'space');
|
|
2458
|
+
const u = units[f];
|
|
2459
|
+
if (!u?.punct || u.first !== 'opening')
|
|
2460
|
+
return indentPx;
|
|
2461
|
+
// Its blank before the glyph, whatever `trimLineStart` says.
|
|
2462
|
+
const box = { ...u.punct };
|
|
2463
|
+
const lead = box.blank - box.cutStart;
|
|
2464
|
+
const trimmed = { ...u, punct: box };
|
|
2465
|
+
if (lead > 0 && box.side === 'start') {
|
|
2466
|
+
box.cutStart += lead;
|
|
2467
|
+
trimmed.width -= lead;
|
|
2468
|
+
}
|
|
2469
|
+
units[f] = trimmed;
|
|
2470
|
+
if (pattern === 'flush')
|
|
2471
|
+
return 0;
|
|
2472
|
+
if (pattern === 'half')
|
|
2473
|
+
return Math.max(0, indentPx - trimmed.width);
|
|
2474
|
+
return indentPx;
|
|
2475
|
+
}
|
|
1851
2476
|
/**
|
|
1852
2477
|
* Set a Chinese, Japanese or Korean paragraph (see the module comment):
|
|
1853
2478
|
* `options.cjkLineBreak` (else the document's level) decides where lines
|
|
@@ -1885,6 +2510,9 @@ export function composeCjkParagraph(spans, normalFont, boldFont, italicFont, bol
|
|
|
1885
2510
|
// Ruby readings (#194), laid out once their neighbours are known.
|
|
1886
2511
|
if (units.some((u) => u.ruby))
|
|
1887
2512
|
sizeRubies(units);
|
|
2513
|
+
// Kanbun marks (#430), beside the readings.
|
|
2514
|
+
if (units.some((u) => u.kunten))
|
|
2515
|
+
sizeKunten(units, letterSpacingPx);
|
|
1888
2516
|
if (!units.some((u) => u.kind !== 'space'))
|
|
1889
2517
|
return { lines: [], totalHeight: 0 };
|
|
1890
2518
|
const fit = lineFitOf(composition);
|
|
@@ -1892,7 +2520,8 @@ export function composeCjkParagraph(spans, normalFont, boldFont, italicFont, bol
|
|
|
1892
2520
|
const breaks = breakOpportunities(units, level);
|
|
1893
2521
|
const indentPx = options?.firstLineIndentPx ?? 0;
|
|
1894
2522
|
const hanging = options?.hangingIndent ?? false;
|
|
1895
|
-
const
|
|
2523
|
+
const firstIndent = hanging ? 0 : paragraphStartIndent(units, indentPx, composition);
|
|
2524
|
+
const indentOf = (li) => (indentPx > 0 ? (hanging ? (li === 0 ? 0 : indentPx) : (li === 0 ? firstIndent : 0)) : 0);
|
|
1896
2525
|
const measureOf = (li) => lineMeasure(maxWidthPx, options?.restWidths, li) - indentOf(li);
|
|
1897
2526
|
const sizeMatch = FONT_SIZE_RE.exec(normalFont);
|
|
1898
2527
|
const em = sizeMatch ? parseFloat(sizeMatch[1]) : 16;
|
|
@@ -1920,7 +2549,18 @@ export function composeCjkParagraph(spans, normalFont, boldFont, italicFont, bol
|
|
|
1920
2549
|
// gives it a runt penalty): a last line that holds one character, alone
|
|
1921
2550
|
// or with its closing marks, takes the last character the line above may
|
|
1922
2551
|
// give up (push-out), and that line is spread to the measure. When it
|
|
1923
|
-
// would need more than the tracking cap, the runt stays.
|
|
2552
|
+
// would need more than the tracking cap, the runt stays. Japanese text
|
|
2553
|
+
// (泣き別れ) first tries the other way JLReq prefers (§3.8.2): the line
|
|
2554
|
+
// above takes the character in by giving up blank (push-in), and the
|
|
2555
|
+
// paragraph is a line shorter.
|
|
2556
|
+
if ((options?.runtPenalty ?? 0) > 0 && endsOnOneCharacter(lines) && fit?.jlreq) {
|
|
2557
|
+
const pulled = breakUnits(units, breaks, measureOf, 0, letterSpacingPx, fit, level, undefined, ranges.length - 2);
|
|
2558
|
+
if (pulled.length === ranges.length - 1) {
|
|
2559
|
+
const set = compose(pulled);
|
|
2560
|
+
if (!set.some((l) => l.cjkLoose) && !endsOnOneCharacter(set))
|
|
2561
|
+
lines = set;
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
1924
2564
|
if ((options?.runtPenalty ?? 0) > 0 && endsOnOneCharacter(lines)) {
|
|
1925
2565
|
const li = ranges.length - 2;
|
|
1926
2566
|
const above = ranges[li];
|