postext 1.15.1 → 1.16.1
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/chipLatinSpacing.test.d.ts +2 -0
- package/dist/__tests__/cjk/chipLatinSpacing.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/chipLatinSpacing.test.js +137 -0
- package/dist/__tests__/cjk/chipLatinSpacing.test.js.map +1 -0
- 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 +56 -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/keepAll.test.d.ts +2 -0
- package/dist/__tests__/cjk/keepAll.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/keepAll.test.js +146 -0
- package/dist/__tests__/cjk/keepAll.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 +37 -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__/japaneseCaptions.test.d.ts +2 -0
- package/dist/__tests__/japaneseCaptions.test.d.ts.map +1 -0
- package/dist/__tests__/japaneseCaptions.test.js +79 -0
- package/dist/__tests__/japaneseCaptions.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 +364 -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/listNesting.test.d.ts +2 -0
- package/dist/__tests__/parse/listNesting.test.d.ts.map +1 -0
- package/dist/__tests__/parse/listNesting.test.js +142 -0
- package/dist/__tests__/parse/listNesting.test.js.map +1 -0
- 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/listNestingLayout.test.d.ts +2 -0
- package/dist/__tests__/pipeline/listNestingLayout.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/listNestingLayout.test.js +90 -0
- package/dist/__tests__/pipeline/listNestingLayout.test.js.map +1 -0
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.js +2 -2
- 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 +10 -4
- package/dist/configWarnings.js.map +1 -1
- package/dist/defaults/captionStyle.d.ts +19 -4
- package/dist/defaults/captionStyle.d.ts.map +1 -1
- package/dist/defaults/captionStyle.js +27 -8
- package/dist/defaults/captionStyle.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 +209 -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 +4 -3
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +14 -5
- 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 +8 -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 +20 -9
- 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 +834 -85
- package/dist/measure/cjkCompose.js.map +1 -1
- package/dist/measure/cjkPunctuation.d.ts +73 -14
- package/dist/measure/cjkPunctuation.d.ts.map +1 -1
- package/dist/measure/cjkPunctuation.js +97 -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/types.d.ts +6 -1
- package/dist/measure/types.d.ts.map +1 -1
- package/dist/measure/types.js.map +1 -1
- 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/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +12 -1
- package/dist/parse/blockParser.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 +42 -0
- 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/listNesting.d.ts +47 -0
- package/dist/parse/listNesting.d.ts.map +1 -0
- package/dist/parse/listNesting.js +65 -0
- package/dist/parse/listNesting.js.map +1 -0
- 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.d.ts +7 -0
- package/dist/pipeline/buildBlockKind.d.ts.map +1 -1
- package/dist/pipeline/buildBlockKind.js +25 -2
- 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 +4 -3
- 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 +57 -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 +93 -17
- 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 +16 -1
- package/dist/pipeline/styles.d.ts.map +1 -1
- package/dist/pipeline/styles.js +8 -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 +594 -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,8 +290,197 @@ 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 } : {}) },
|
|
294
|
+
};
|
|
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 } : {}),
|
|
259
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
|
+
}
|
|
260
484
|
}
|
|
261
485
|
/**
|
|
262
486
|
* Lay out each ruby base's reading (see `cjkAnnotate.ts`): its box grows
|
|
@@ -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,31 +1220,89 @@ 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
|
+
/** The character a chip's words open (`first`) or close (`last`) with, in
|
|
1258
|
+
* their logical order; undefined for a unit that is not a chip, or a chip
|
|
1259
|
+
* in a superscript or a subscript. */
|
|
1260
|
+
function chipEdgeGrapheme(u, edge) {
|
|
1261
|
+
const runs = u.kind === 'atomic' && !u.style.script ? u.token?.chip?.runs : undefined;
|
|
1262
|
+
if (!runs)
|
|
1263
|
+
return undefined;
|
|
1264
|
+
const text = runs.map((r) => r.text).join('');
|
|
1265
|
+
if (text === '')
|
|
1266
|
+
return undefined;
|
|
1267
|
+
return edge === 'first' ? String.fromCodePoint(text.codePointAt(0)) : lastGrapheme(text);
|
|
1268
|
+
}
|
|
1269
|
+
/** `units` with the Han–Latin spaces of `cjk.latinSpacing` (see
|
|
1270
|
+
* {@link prepareUnits}).
|
|
1271
|
+
*
|
|
1272
|
+
* A chip (`:chip[…]`) is a box of text: on each side it takes the space
|
|
1273
|
+
* its own words would (#462). `:chip[unicodedata]` between kana is Latin
|
|
1274
|
+
* on both sides and gets the quarter em JLReq §3.2.2 puts between
|
|
1275
|
+
* Japanese and Western text, as `unicodedata` set plain would;
|
|
1276
|
+
* `:chip[漢字]` is Han on both sides, so a Latin word touching it gets
|
|
1277
|
+
* the space and a kana does not. Its line-break class stays that of an
|
|
1278
|
+
* inline box. The rest follows from the rule for plain text: the space
|
|
1279
|
+
* goes at a line end and is dropped at a line start like any space, and
|
|
1280
|
+
* a bracket, a mark or a sign next to the chip is neither Han nor Latin,
|
|
1281
|
+
* so 「:chip[x]」 takes none. Down a vertical line the chip is set
|
|
1282
|
+
* sideways, as a Latin run is, and the same rule holds. */
|
|
1283
|
+
function latinSpaces(units, c) {
|
|
860
1284
|
const out = [];
|
|
861
|
-
const han = (u, edge) =>
|
|
1285
|
+
const han = (u, edge) => {
|
|
1286
|
+
if (!u)
|
|
1287
|
+
return false;
|
|
1288
|
+
const chipEdge = chipEdgeGrapheme(u, edge);
|
|
1289
|
+
if (chipEdge !== undefined)
|
|
1290
|
+
return isLatinSpacingHan(chipEdge);
|
|
1291
|
+
return u.kind === 'text' && !u.run && !u.note && u.firstCjk && u[edge] === 'ideograph' && !u.style.script && isLatinSpacingHan(u.text);
|
|
1292
|
+
};
|
|
862
1293
|
// A run whose edge is a number set in one upright cell (vertical text)
|
|
863
1294
|
// takes no Han–Latin space on that side; neither does a unit the author
|
|
864
|
-
// set upright or in one cell.
|
|
865
|
-
|
|
866
|
-
|
|
1295
|
+
// set upright or in one cell. A chip's edge is Latin when its character
|
|
1296
|
+
// is a letter or a digit that is not CJK (a full-width A is not).
|
|
1297
|
+
const latin = (u, edge) => {
|
|
1298
|
+
if (!u)
|
|
1299
|
+
return false;
|
|
1300
|
+
const chipEdge = chipEdgeGrapheme(u, edge);
|
|
1301
|
+
if (chipEdge !== undefined)
|
|
1302
|
+
return !isCjkGrapheme(chipEdge) && !isLatinSpacingHan(chipEdge) && isLatinSpacingLatin(chipEdge);
|
|
1303
|
+
return u.kind === 'text' && !!u.run && !u.note && !u.style.script && !(edge === 'first' ? u.cellStart : u.cellEnd)
|
|
1304
|
+
&& isLatinSpacingLatin(edge === 'first' ? String.fromCodePoint(u.text.codePointAt(0)) : lastGrapheme(u.text));
|
|
1305
|
+
};
|
|
867
1306
|
const spaceOf = (h) => latinSpacingPx(c, emOfFont(h.style.font));
|
|
868
1307
|
for (let k = 0; k < units.length; k++) {
|
|
869
1308
|
const u = units[k];
|
|
@@ -899,6 +1338,62 @@ function prepareUnits(units, c, letterSpacingPx) {
|
|
|
899
1338
|
}
|
|
900
1339
|
return out;
|
|
901
1340
|
}
|
|
1341
|
+
/**
|
|
1342
|
+
* `units` with the one-em space JLReq §3.1.6 sets after a ? or ! that
|
|
1343
|
+
* ends a sentence inside the paragraph (`cjk.spaceAfterQuestion`,
|
|
1344
|
+
* `Unit.aki`): after the mark and what keeps to it (a note marker), unless
|
|
1345
|
+
* the paragraph ends there or a closing bracket or another ?! follows. A
|
|
1346
|
+
* space the author typed there (U+3000, or a run of word spaces) becomes
|
|
1347
|
+
* the aki, keeping its text. A line may break after it, and it goes at the
|
|
1348
|
+
* line end, as any space does.
|
|
1349
|
+
*/
|
|
1350
|
+
function questionSpaces(units) {
|
|
1351
|
+
const out = [];
|
|
1352
|
+
for (let k = 0; k < units.length; k++) {
|
|
1353
|
+
const u = units[k];
|
|
1354
|
+
out.push(u);
|
|
1355
|
+
if (!charUnitIn(u, QUESTION_MARKS))
|
|
1356
|
+
continue;
|
|
1357
|
+
// A note marker or a superscript keeps to the mark: the space follows it.
|
|
1358
|
+
while (k + 1 < units.length && units[k + 1].glueBefore)
|
|
1359
|
+
out.push(units[++k]);
|
|
1360
|
+
const next = units[k + 1];
|
|
1361
|
+
if (!next || charUnitIn(next, QUESTION_MARKS))
|
|
1362
|
+
continue;
|
|
1363
|
+
if (next.kind === 'text' && next.first === 'closing')
|
|
1364
|
+
continue;
|
|
1365
|
+
const aki = {
|
|
1366
|
+
kind: 'space',
|
|
1367
|
+
text: '',
|
|
1368
|
+
width: emOfFont(u.style.font),
|
|
1369
|
+
graphemes: 0,
|
|
1370
|
+
first: 'western',
|
|
1371
|
+
last: 'western',
|
|
1372
|
+
firstCjk: false,
|
|
1373
|
+
lastCjk: false,
|
|
1374
|
+
style: u.style,
|
|
1375
|
+
at: u.at + u.text.length,
|
|
1376
|
+
aki: true,
|
|
1377
|
+
};
|
|
1378
|
+
if (next.kind === 'space' && !next.auto) {
|
|
1379
|
+
// A typed space is replaced: the aki keeps its text.
|
|
1380
|
+
if (units[k + 2] === undefined)
|
|
1381
|
+
continue;
|
|
1382
|
+
out.push({ ...aki, text: next.text, at: next.at });
|
|
1383
|
+
k++;
|
|
1384
|
+
}
|
|
1385
|
+
else if (next.kind === 'text' && next.first === 'ideoSpace' && next.graphemes === 1) {
|
|
1386
|
+
if (units[k + 2] === undefined)
|
|
1387
|
+
continue;
|
|
1388
|
+
out.push({ ...aki, text: next.text, at: next.at });
|
|
1389
|
+
k++;
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
out.push(aki);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return out;
|
|
1396
|
+
}
|
|
902
1397
|
const isDigitCode = (c) => (c >= 0x30 && c <= 0x39) || isFullwidthDigit(c);
|
|
903
1398
|
/** Whether a number keeps to the sign or unit next to it (`50 %`, `50%`,
|
|
904
1399
|
* `¥599`, `−3 ℃`): a unit ending in a digit before a unit sign, or a
|
|
@@ -931,10 +1426,29 @@ function fullwidthGlue(units, k) {
|
|
|
931
1426
|
return true;
|
|
932
1427
|
return fullwidthUnit(a, isFullwidthNumberJoin) && fullwidthUnit(b, isFullwidthDigit) && fullwidthUnit(units[k - 2], isFullwidthDigit);
|
|
933
1428
|
}
|
|
1429
|
+
/** The grapheme a unit opens (`first`) or ends (`last`) with, which a
|
|
1430
|
+
* Japanese level reads the unit's class from (small kana, ー, hyphens…:
|
|
1431
|
+
* `breakClassOf`); undefined under a Chinese level, and for an inline box
|
|
1432
|
+
* (a chip, a formula, a marker), whose class stands as it is. */
|
|
1433
|
+
function edgeGrapheme(u, edge, level) {
|
|
1434
|
+
if (u.kind !== 'text' || u.text === '' || !isJapaneseLineBreak(level))
|
|
1435
|
+
return undefined;
|
|
1436
|
+
return edge === 'first' ? String.fromCodePoint(u.text.codePointAt(0)) : lastGrapheme(u.text);
|
|
1437
|
+
}
|
|
1438
|
+
/** Whether unit `b` may not open a line at `level` (see
|
|
1439
|
+
* {@link edgeGrapheme}). */
|
|
1440
|
+
function startProhibited(b, level) {
|
|
1441
|
+
return isLineStartProhibited(b.first, level, edgeGrapheme(b, 'first', level));
|
|
1442
|
+
}
|
|
1443
|
+
/** Whether unit `a` may not close a line at `level`. */
|
|
1444
|
+
function endProhibited(a, level) {
|
|
1445
|
+
return isLineEndProhibited(a.last, level, edgeGrapheme(a, 'last', level));
|
|
1446
|
+
}
|
|
934
1447
|
/** Whether a line may break before each unit (index 0 is never a break).
|
|
935
1448
|
* A word space is a break unless the unit after it may not open a line,
|
|
936
1449
|
* 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.
|
|
1450
|
+
* sign; a zero-width space is a break at every level. A kanji number
|
|
1451
|
+
* never breaks at the 、 or ・ set solid inside it (`Unit.solid`). */
|
|
938
1452
|
function breakOpportunities(units, level) {
|
|
939
1453
|
const out = new Uint8Array(units.length);
|
|
940
1454
|
// The last unit before `k` that is not a space.
|
|
@@ -955,7 +1469,7 @@ function breakOpportunities(units, level) {
|
|
|
955
1469
|
}
|
|
956
1470
|
if (a.kind === 'space') {
|
|
957
1471
|
const p = ink >= 0 ? units[ink] : undefined;
|
|
958
|
-
if (!p || (!numberGlue(p, b) && !
|
|
1472
|
+
if (!p || (!numberGlue(p, b) && !startProhibited(b, level) && !endProhibited(p, level)))
|
|
959
1473
|
out[k] = 1;
|
|
960
1474
|
continue;
|
|
961
1475
|
}
|
|
@@ -965,27 +1479,72 @@ function breakOpportunities(units, level) {
|
|
|
965
1479
|
while (q > 0 && units[q].noteSpace)
|
|
966
1480
|
q--;
|
|
967
1481
|
const p = units[q];
|
|
968
|
-
if (!p.noteSpace && !numberGlue(p, b) && !
|
|
1482
|
+
if (!p.noteSpace && !numberGlue(p, b) && !startProhibited(b, level) && !endProhibited(p, level))
|
|
969
1483
|
out[k] = 1;
|
|
970
1484
|
continue;
|
|
971
1485
|
}
|
|
972
|
-
if (b.glueBefore)
|
|
1486
|
+
if (b.glueBefore || a.solid || b.solid)
|
|
973
1487
|
continue;
|
|
974
1488
|
if (numberGlue(a, b) || fullwidthGlue(units, k))
|
|
975
1489
|
continue;
|
|
976
|
-
if (cjkBreakAllowed(a.last, a.lastCjk, b.first, b.firstCjk, level))
|
|
1490
|
+
if (cjkBreakAllowed(a.last, a.lastCjk, b.first, b.firstCjk, level, edgeGrapheme(a, 'last', level), edgeGrapheme(b, 'first', level)))
|
|
977
1491
|
out[k] = 1;
|
|
978
1492
|
}
|
|
979
1493
|
return out;
|
|
980
1494
|
}
|
|
1495
|
+
/** Whether a unit opens (`first`) or ends (`last`) with a letter, which
|
|
1496
|
+
* `keep-all` never parts from a letter next to it: a CJK character (kanji,
|
|
1497
|
+
* kana, hangul, an iteration mark, ー), a Western run or a sign glued to
|
|
1498
|
+
* a number, an inline box. Punctuation, dashes, ellipses, connectors and
|
|
1499
|
+
* the ideographic space are not letters: lines still break next to them
|
|
1500
|
+
* where the level allows (after 、。」, before 「). */
|
|
1501
|
+
function isLetterEdge(u, edge) {
|
|
1502
|
+
if (u.kind === 'space')
|
|
1503
|
+
return false;
|
|
1504
|
+
const cls = edge === 'first' ? u.first : u.last;
|
|
1505
|
+
return cls === 'ideograph' || cls === 'iteration' || cls === 'western' || cls === 'prefix' || cls === 'postfix';
|
|
1506
|
+
}
|
|
1507
|
+
/** The breaks of `cjk.wordBreak: 'keep-all'` (CSS `word-break: keep-all`)
|
|
1508
|
+
* out of the paragraph's `breaks`: those after a space the author typed,
|
|
1509
|
+
* a warichu note's word space or before a zero-width space stay, and so
|
|
1510
|
+
* do those next to punctuation; none is left between two letters
|
|
1511
|
+
* ({@link isLetterEdge}), so a phrase of kana written between spaces
|
|
1512
|
+
* (分かち書き) is never divided while it fits a line. The Han–Latin space
|
|
1513
|
+
* the composition inserts (`Unit.auto`, no text) is no space between
|
|
1514
|
+
* phrases: `Tシャツ` stays whole. */
|
|
1515
|
+
function keepAllBreaks(units, breaks) {
|
|
1516
|
+
const out = breaks.slice();
|
|
1517
|
+
for (let k = 1; k < units.length; k++) {
|
|
1518
|
+
if (!out[k])
|
|
1519
|
+
continue;
|
|
1520
|
+
let a = units[k - 1];
|
|
1521
|
+
const b = units[k];
|
|
1522
|
+
if (a.noteSpace || b.zwspBefore)
|
|
1523
|
+
continue;
|
|
1524
|
+
if (a.kind === 'space') {
|
|
1525
|
+
if (!(a.auto && a.text === '') || k < 2)
|
|
1526
|
+
continue;
|
|
1527
|
+
a = units[k - 2];
|
|
1528
|
+
}
|
|
1529
|
+
if (isLetterEdge(a, 'last') && isLetterEdge(b, 'first'))
|
|
1530
|
+
out[k] = 0;
|
|
1531
|
+
}
|
|
1532
|
+
return out;
|
|
1533
|
+
}
|
|
981
1534
|
/** Whether justification may add space between two units that touch on a
|
|
982
1535
|
* line: between characters, never inside a Western run or a 2-em mark
|
|
983
1536
|
* (those are single units), never between two Western runs, next to a
|
|
984
1537
|
* 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
|
-
|
|
1538
|
+
* keeps to the text before it. Spaces take their own share. With `jlreq`
|
|
1539
|
+
* (Japanese full-width text) also JLReq §3.1.11's inseparable places:
|
|
1540
|
+
* never after an opening bracket or before a closing one (but before the
|
|
1541
|
+
* one and after the other), never next to 、,。.・:;?! or U+3000. */
|
|
1542
|
+
function gapStretches(a, b, jlreq = false) {
|
|
987
1543
|
if (a.kind !== 'text' || b.kind === 'space')
|
|
988
1544
|
return false;
|
|
1545
|
+
// Never inside a jukugo word (JLReq §3.3.7).
|
|
1546
|
+
if (a.ruby?.jis && a.ruby.jis === b.ruby?.jis)
|
|
1547
|
+
return false;
|
|
989
1548
|
if (b.glueBefore || a.stacked)
|
|
990
1549
|
return false;
|
|
991
1550
|
if (a.last === 'connector' || a.last === 'solidus' || b.first === 'connector' || b.first === 'solidus')
|
|
@@ -994,16 +1553,43 @@ function gapStretches(a, b) {
|
|
|
994
1553
|
return false;
|
|
995
1554
|
if (a.last === b.first && (a.last === 'dash' || a.last === 'ellipsis'))
|
|
996
1555
|
return false;
|
|
1556
|
+
if (jlreq && (a.last === 'opening' || b.first === 'closing' || jlreqFixed(a.last) || jlreqFixed(b.first)))
|
|
1557
|
+
return false;
|
|
997
1558
|
return true;
|
|
998
1559
|
}
|
|
1560
|
+
/** The classes JLReq never spreads next to (§3.1.11): pause and stop
|
|
1561
|
+
* marks, middle dots and the ideographic space. */
|
|
1562
|
+
function jlreqFixed(cls) {
|
|
1563
|
+
return cls === 'pause' || cls === 'stop' || cls === 'interpunct' || cls === 'ideoSpace';
|
|
1564
|
+
}
|
|
1565
|
+
/** Whether the gap after unit `j` of a line takes tracking. A footnote
|
|
1566
|
+
* marker in the line gap (`footnotes.markerPosition: 'side'`) takes no
|
|
1567
|
+
* room in the line: the gap between the characters around it is theirs,
|
|
1568
|
+
* and is set after the marker, which stays against the character it
|
|
1569
|
+
* marks. */
|
|
1570
|
+
function gapAfterStretches(us, j, jlreq = false) {
|
|
1571
|
+
const next = us[j + 1];
|
|
1572
|
+
if (isSideMarker(next))
|
|
1573
|
+
return false;
|
|
1574
|
+
const u = us[j];
|
|
1575
|
+
if (isSideMarker(u))
|
|
1576
|
+
return j > 0 && gapStretches(us[j - 1], next, jlreq);
|
|
1577
|
+
return gapStretches(u, next, jlreq);
|
|
1578
|
+
}
|
|
1579
|
+
function isSideMarker(u) {
|
|
1580
|
+
return u.token?.sideRuns !== undefined;
|
|
1581
|
+
}
|
|
999
1582
|
function lineFitOf(c) {
|
|
1000
1583
|
if (isPlainComposition(c))
|
|
1001
1584
|
return undefined;
|
|
1585
|
+
const jlreq = isJlreqSpacing(c);
|
|
1002
1586
|
return {
|
|
1003
1587
|
lead: (u) => (u.punct ? lineEdgeCut(u.punct, c, true, false) : 0),
|
|
1004
1588
|
tail: (u) => (u.punct ? lineEdgeCut(u.punct, c, false, true) : 0),
|
|
1005
1589
|
give(u, atStart, atEnd) {
|
|
1006
1590
|
if (u.kind === 'space') {
|
|
1591
|
+
if (u.aki)
|
|
1592
|
+
return 0;
|
|
1007
1593
|
const em = emOfFont(u.style.font);
|
|
1008
1594
|
return Math.max(0, u.width - (u.auto ? em / 8 : em / 4));
|
|
1009
1595
|
}
|
|
@@ -1013,18 +1599,21 @@ function lineFitOf(c) {
|
|
|
1013
1599
|
return punctuationShrink(u.punct, c);
|
|
1014
1600
|
const box = { ...u.punct };
|
|
1015
1601
|
applyLineEdges(box, c, atStart, atEnd);
|
|
1016
|
-
|
|
1602
|
+
// JLReq: a mark ending the line gives up the blank after its glyph
|
|
1603
|
+
// (and, a middle dot, nothing before it: §3.8.3 steps 2–3).
|
|
1604
|
+
return punctuationShrink(box, c, jlreq && atEnd);
|
|
1017
1605
|
},
|
|
1018
1606
|
edge: (u, start, end) => (u.punct ? applyLineEdges(u.punct, c, start, end) : 0),
|
|
1019
1607
|
hangs(units, k, unitAt) {
|
|
1020
1608
|
const u = unitAt(k);
|
|
1021
|
-
if (u.kind !== 'text' || u.run || u.graphemes !== 1 || u.note || u.ruby || !mayHang(u.text, u.first, c))
|
|
1609
|
+
if (u.kind !== 'text' || u.run || u.graphemes !== 1 || u.note || u.ruby || u.kunten || !mayHang(u.text, u.first, c))
|
|
1022
1610
|
return false;
|
|
1023
1611
|
if (k > 0 && isMarkUnit(unitAt(k - 1)))
|
|
1024
1612
|
return false;
|
|
1025
1613
|
return k + 1 >= units.length || !isMarkUnit(units[k + 1]);
|
|
1026
1614
|
},
|
|
1027
1615
|
hanging: c.hangingPunctuation,
|
|
1616
|
+
jlreq,
|
|
1028
1617
|
};
|
|
1029
1618
|
}
|
|
1030
1619
|
/** The last unit that must share a line with unit `k` when `k` ends one:
|
|
@@ -1158,11 +1747,19 @@ function divideRun(u, room, letterSpacingPx) {
|
|
|
1158
1747
|
* unit). A space never overflows: the line ends before it, when the line
|
|
1159
1748
|
* may break after it. A unit wider
|
|
1160
1749
|
* 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.
|
|
1750
|
+
* at a joint first), else set on a line of its own. `pull` names a line
|
|
1751
|
+
* that takes in the rest of the paragraph when the line can give up the
|
|
1752
|
+
* blank for it (push-in, to save a last line of one character).
|
|
1162
1753
|
*/
|
|
1163
1754
|
function breakUnits(units, breaks, measureOf, reserve, letterSpacingPx, fit, level = getCjkLineBreak(),
|
|
1164
1755
|
/** End line `line` before unit `at` (a break), whatever else fits. */
|
|
1165
|
-
stop
|
|
1756
|
+
stop,
|
|
1757
|
+
/** Line `pull` takes every unit left when it can give up the blank. */
|
|
1758
|
+
pull,
|
|
1759
|
+
/** Under `keep-all` (`breaks` holds only the breaks between phrases): the
|
|
1760
|
+
* breaks the level allows between any two characters, where a phrase
|
|
1761
|
+
* longer than the line is broken. */
|
|
1762
|
+
emergency) {
|
|
1166
1763
|
const out = [];
|
|
1167
1764
|
const n = units.length;
|
|
1168
1765
|
let i = 0;
|
|
@@ -1176,15 +1773,23 @@ stop) {
|
|
|
1176
1773
|
let noteBase = 0;
|
|
1177
1774
|
const foldOf = (from, to) => {
|
|
1178
1775
|
const widths = [];
|
|
1179
|
-
const
|
|
1180
|
-
const
|
|
1776
|
+
const startNo = [];
|
|
1777
|
+
const endNo = [];
|
|
1181
1778
|
for (let q = from; q <= to; q++) {
|
|
1182
1779
|
const uq = unitAt(q);
|
|
1183
1780
|
widths.push(uq.width);
|
|
1184
|
-
|
|
1185
|
-
|
|
1781
|
+
startNo.push(noteRowStartProhibited(uq, level));
|
|
1782
|
+
endNo.push(endProhibited(uq, level));
|
|
1186
1783
|
}
|
|
1187
|
-
return foldWidth(widths, splitNote(widths,
|
|
1784
|
+
return foldWidth(widths, splitNote(widths, startNo, endNo));
|
|
1785
|
+
};
|
|
1786
|
+
// A Japanese ruby base at a line edge widens the line (`rubyGrow`): at
|
|
1787
|
+
// its start, and at its end when the line may end after it.
|
|
1788
|
+
const endGrow = (k) => {
|
|
1789
|
+
const g = unitAt(k).rubyGrow;
|
|
1790
|
+
if (!g || g.end <= 0)
|
|
1791
|
+
return 0;
|
|
1792
|
+
return k + 1 >= n || breaks[k + 1] || units[k + 1].kind === 'space' ? g.end : 0;
|
|
1188
1793
|
};
|
|
1189
1794
|
for (let li = 0;; li++) {
|
|
1190
1795
|
while (i < n && unitAt(i).kind === 'space')
|
|
@@ -1192,7 +1797,7 @@ stop) {
|
|
|
1192
1797
|
if (i >= n)
|
|
1193
1798
|
break;
|
|
1194
1799
|
const max = measureOf(li) - reserve;
|
|
1195
|
-
let w = 0;
|
|
1800
|
+
let w = Math.max(0, unitAt(i).rubyGrow?.start ?? 0);
|
|
1196
1801
|
let lastBreak = -1;
|
|
1197
1802
|
let end = -1;
|
|
1198
1803
|
let head;
|
|
@@ -1226,7 +1831,7 @@ stop) {
|
|
|
1226
1831
|
// line may end with was one that folded).
|
|
1227
1832
|
}
|
|
1228
1833
|
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) {
|
|
1834
|
+
if (!u.note && w + u.width - lead - (fit ? fit.tail(u) : 0) + endGrow(k) <= max + FIT_EPS) {
|
|
1230
1835
|
w += u.width - lead;
|
|
1231
1836
|
if (fit)
|
|
1232
1837
|
give += fit.give(u, k === i, false);
|
|
@@ -1248,13 +1853,13 @@ stop) {
|
|
|
1248
1853
|
hang = true;
|
|
1249
1854
|
break;
|
|
1250
1855
|
}
|
|
1251
|
-
const m = breaks[k] ? -1 : groupEnd(units, breaks, k);
|
|
1856
|
+
const m = pull === li ? n - 1 : breaks[k] ? -1 : groupEnd(units, breaks, k);
|
|
1252
1857
|
if (m >= k) {
|
|
1253
1858
|
let width = w;
|
|
1254
1859
|
let room = give;
|
|
1255
1860
|
for (let q = k; q <= m; q++) {
|
|
1256
1861
|
const uq = unitAt(q);
|
|
1257
|
-
const t = q === m ? fit.tail(uq) : 0;
|
|
1862
|
+
const t = q === m ? fit.tail(uq) - endGrow(q) : 0;
|
|
1258
1863
|
width += uq.width - t;
|
|
1259
1864
|
room += fit.give(uq, false, q === m);
|
|
1260
1865
|
}
|
|
@@ -1314,6 +1919,17 @@ stop) {
|
|
|
1314
1919
|
end = k + 1;
|
|
1315
1920
|
break;
|
|
1316
1921
|
}
|
|
1922
|
+
if (emergency) {
|
|
1923
|
+
// A phrase longer than the line (keep-all): it is broken inside,
|
|
1924
|
+
// at the last place the level allows.
|
|
1925
|
+
let q = k;
|
|
1926
|
+
while (q > i && !emergency[q])
|
|
1927
|
+
q--;
|
|
1928
|
+
if (q > i) {
|
|
1929
|
+
end = q;
|
|
1930
|
+
break;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1317
1933
|
// Nothing on the line may end it: break before the unit anyway.
|
|
1318
1934
|
end = k;
|
|
1319
1935
|
break;
|
|
@@ -1342,9 +1958,10 @@ stop) {
|
|
|
1342
1958
|
return out;
|
|
1343
1959
|
}
|
|
1344
1960
|
/** Whether a note's unit may not open its lower row: a mark that may not
|
|
1345
|
-
* start a line
|
|
1961
|
+
* start a line (the small kana and the rest of the Japanese classes under
|
|
1962
|
+
* a Japanese level), or a word space (it ends the upper row instead). */
|
|
1346
1963
|
function noteRowStartProhibited(u, level) {
|
|
1347
|
-
return u.noteSpace === true ||
|
|
1964
|
+
return u.noteSpace === true || startProhibited(u, level);
|
|
1348
1965
|
}
|
|
1349
1966
|
/** Replace each run of a warichu note's characters on a line by the part
|
|
1350
1967
|
* folded into its two rows (see `cjkAnnotate.ts`). `us` is changed in
|
|
@@ -1372,7 +1989,7 @@ function foldNotes(us, level, em) {
|
|
|
1372
1989
|
function noteFragment(part, level, em) {
|
|
1373
1990
|
const note = part[0].note;
|
|
1374
1991
|
const widths = part.map((u) => u.width);
|
|
1375
|
-
const at = splitNote(widths, part.map((u) => noteRowStartProhibited(u, level)), part.map((u) =>
|
|
1992
|
+
const at = splitNote(widths, part.map((u) => noteRowStartProhibited(u, level)), part.map((u) => endProhibited(u, level)));
|
|
1376
1993
|
const noteFont = note.fontString ?? part[0].style.font;
|
|
1377
1994
|
const rows = noteRowBaselines(em, fontEm(noteFont));
|
|
1378
1995
|
const runs = [];
|
|
@@ -1435,7 +2052,9 @@ function alignEdgeRubies(us) {
|
|
|
1435
2052
|
const align = (j, start) => {
|
|
1436
2053
|
const u = us[j];
|
|
1437
2054
|
const g = u.ruby?.geometry;
|
|
1438
|
-
|
|
2055
|
+
// A base with kanbun marks keeps its reading where the marks were set
|
|
2056
|
+
// against it (#430).
|
|
2057
|
+
if (!g || u.ruby.position === 'right' || u.ruby.jis || g.runs.length === 0 || u.kunten)
|
|
1439
2058
|
return;
|
|
1440
2059
|
const base = u.width - 2 * g.inset;
|
|
1441
2060
|
const rt = g.rtWidth;
|
|
@@ -1471,7 +2090,8 @@ function clampReadings(segments) {
|
|
|
1471
2090
|
let x = 0;
|
|
1472
2091
|
for (const s of segments) {
|
|
1473
2092
|
const ruby = s.ruby;
|
|
1474
|
-
|
|
2093
|
+
// A Japanese reading (`id`) was set inside the line already (#422).
|
|
2094
|
+
if (ruby && ruby.position !== 'right' && ruby.id === undefined && ruby.runs.length > 0) {
|
|
1475
2095
|
const lo = ruby.runs[0].dx;
|
|
1476
2096
|
const hi = lo + ruby.rtWidth;
|
|
1477
2097
|
let shift = 0;
|
|
@@ -1508,9 +2128,16 @@ function spread(amount, caps) {
|
|
|
1508
2128
|
* character that may not open a line, clreq §6.2.2.3) gives up
|
|
1509
2129
|
* blank in clreq's order — word spaces to a quarter em, interpuncts,
|
|
1510
2130
|
* 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
|
-
*
|
|
2131
|
+
* last — each step shared equally, until it fits. Japanese full-width text
|
|
2132
|
+
* takes JLReq §3.8.3's order instead (`jlreqShrinkStep`): word spaces, the
|
|
2133
|
+
* half em after the line's last mark and then a middle dot's quarter (each
|
|
2134
|
+
* all or nothing: what it gives beyond the need is spread back when the
|
|
2135
|
+
* line is justified), the middle dots' quarters inside the line, the half
|
|
2136
|
+
* ems of brackets and 、, inside it, Han–Latin spaces; never the half em
|
|
2137
|
+
* after a 。 inside the line. A mark that hangs keeps its glyph alone. `us`
|
|
2138
|
+
* holds the line's units and is changed in place; a unit that changes is
|
|
2139
|
+
* replaced by a copy, since the paragraph's units serve every attempt at
|
|
2140
|
+
* breaking it.
|
|
1514
2141
|
*/
|
|
1515
2142
|
function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
1516
2143
|
const copy = (j) => {
|
|
@@ -1527,6 +2154,9 @@ function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
|
1527
2154
|
hang = copy(us.length - 1);
|
|
1528
2155
|
us.pop();
|
|
1529
2156
|
hang.width -= fit.edge(hang, false, true);
|
|
2157
|
+
// ぶら下げ: the hung mark is its glyph, past the measure.
|
|
2158
|
+
if (fit.jlreq && hang.punct)
|
|
2159
|
+
hang.width -= shrinkPunctuation(hang.punct, hang.punct.blank, true);
|
|
1530
2160
|
while (us.length > 0 && us[us.length - 1].kind === 'space')
|
|
1531
2161
|
us.pop();
|
|
1532
2162
|
}
|
|
@@ -1549,17 +2179,27 @@ function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
|
1549
2179
|
if (over <= FIT_EPS)
|
|
1550
2180
|
return hang;
|
|
1551
2181
|
// Steps of the reduction order: word spaces (1), interpuncts (2),
|
|
1552
|
-
// brackets (3), pause marks (4), Han–Latin spaces (5), stop marks (6)
|
|
1553
|
-
|
|
2182
|
+
// brackets (3), pause marks (4), Han–Latin spaces (5), stop marks (6);
|
|
2183
|
+
// in JLReq spacing, word spaces (1), the line end (2, 3), middle dots
|
|
2184
|
+
// (4), brackets and 、, (5), Han–Latin spaces (6).
|
|
2185
|
+
const last = us.length - 1;
|
|
2186
|
+
const atEnd = (j) => fit.jlreq && j === last;
|
|
2187
|
+
const stepOf = (u, j) => {
|
|
2188
|
+
if (u.kind === 'space')
|
|
2189
|
+
return u.aki ? 0 : u.auto ? (fit.jlreq ? 6 : 5) : 1;
|
|
2190
|
+
if (!u.punct)
|
|
2191
|
+
return 0;
|
|
2192
|
+
return fit.jlreq ? jlreqShrinkStep(u.punct, atEnd(j)) : shrinkStep(u.punct);
|
|
2193
|
+
};
|
|
1554
2194
|
for (let step = 1; step <= 6 && over > FIT_EPS; step++) {
|
|
1555
2195
|
const members = [];
|
|
1556
2196
|
const caps = [];
|
|
1557
2197
|
for (let j = 0; j < us.length; j++) {
|
|
1558
2198
|
const u = us[j];
|
|
1559
|
-
if (stepOf(u) !== step)
|
|
2199
|
+
if (stepOf(u, j) !== step)
|
|
1560
2200
|
continue;
|
|
1561
2201
|
// The marks at the edges already gave up their lead and tail.
|
|
1562
|
-
const cap = fit.give(u, false,
|
|
2202
|
+
const cap = fit.give(u, false, atEnd(j));
|
|
1563
2203
|
if (cap <= 0)
|
|
1564
2204
|
continue;
|
|
1565
2205
|
members.push(j);
|
|
@@ -1567,13 +2207,15 @@ function fitLine(us, units, range, lastK, isLast, measure, fit) {
|
|
|
1567
2207
|
}
|
|
1568
2208
|
if (members.length === 0)
|
|
1569
2209
|
continue;
|
|
1570
|
-
|
|
2210
|
+
// The blank at a JLReq line end goes whole or not at all (§3.1.9).
|
|
2211
|
+
const takes = fit.jlreq && (step === 2 || step === 3) ? caps : spread(over, caps);
|
|
1571
2212
|
for (let m = 0; m < members.length; m++) {
|
|
1572
2213
|
const take = takes[m];
|
|
1573
2214
|
if (take <= 0)
|
|
1574
2215
|
continue;
|
|
1575
|
-
const
|
|
1576
|
-
const
|
|
2216
|
+
const j = members[m];
|
|
2217
|
+
const c = copy(j);
|
|
2218
|
+
const given = c.punct ? shrinkPunctuation(c.punct, take, atEnd(j)) : take;
|
|
1577
2219
|
c.width -= given;
|
|
1578
2220
|
over -= given;
|
|
1579
2221
|
}
|
|
@@ -1609,7 +2251,10 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1609
2251
|
// A warichu note's characters fold into their two rows (#195).
|
|
1610
2252
|
if (us.some((u) => u.note))
|
|
1611
2253
|
foldNotes(us, ctx.level, ctx.em);
|
|
1612
|
-
// A ruby base at either edge aligns to it with its reading (#194)
|
|
2254
|
+
// A ruby base at either edge aligns to it with its reading (#194); a
|
|
2255
|
+
// Japanese one is laid out again there (#422).
|
|
2256
|
+
if (us.some((u) => u.ruby?.jis))
|
|
2257
|
+
relayJisEdges(us);
|
|
1613
2258
|
alignEdgeRubies(us);
|
|
1614
2259
|
const hang = ctx.fit ? fitLine(us, units, range, lastK, isLast, measure, ctx.fit) : undefined;
|
|
1615
2260
|
const justify = ctx.textAlign === 'justify' && !isLast;
|
|
@@ -1628,9 +2273,15 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1628
2273
|
// Han–Latin spaces (`cjk.latinSpacing`): they flex apart from word
|
|
1629
2274
|
// spaces, and the renderers leave them as set.
|
|
1630
2275
|
const autos = [];
|
|
2276
|
+
const jlreq = ctx.fit?.jlreq === true;
|
|
1631
2277
|
for (let j = 0; j < us.length; j++) {
|
|
1632
2278
|
const u = us[j];
|
|
1633
2279
|
content += u.width;
|
|
2280
|
+
// The space after ?! keeps its em (JLReq §3.1.6).
|
|
2281
|
+
if (u.kind === 'space' && u.aki) {
|
|
2282
|
+
stretches.push(false);
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
1634
2285
|
if (u.kind === 'space' && u.auto)
|
|
1635
2286
|
autos.push(j);
|
|
1636
2287
|
else if (u.kind === 'space') {
|
|
@@ -1638,7 +2289,7 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1638
2289
|
natural += u.width;
|
|
1639
2290
|
widest = Math.max(widest, u.width);
|
|
1640
2291
|
}
|
|
1641
|
-
const s = j < us.length - 1 &&
|
|
2292
|
+
const s = j < us.length - 1 && gapAfterStretches(us, j, jlreq);
|
|
1642
2293
|
stretches.push(s);
|
|
1643
2294
|
if (s)
|
|
1644
2295
|
gaps++;
|
|
@@ -1676,9 +2327,20 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1676
2327
|
us[j] = { ...us[j], width: us[j].width + tracking };
|
|
1677
2328
|
}
|
|
1678
2329
|
}
|
|
2330
|
+
else if (spaces > 0 && us.some((u) => u.kind === 'text' && (u.firstCjk || u.lastCjk))) {
|
|
2331
|
+
// CJK characters with no gap between them that may spread (phrases
|
|
2332
|
+
// glued by word joiners, marks JLReq never spreads next to), and
|
|
2333
|
+
// word spaces (#463): the spaces take it all and the line ends on
|
|
2334
|
+
// the measure. Their final width is the segments', as every gap of
|
|
2335
|
+
// a composed line is; a Latin word space's stretch limit
|
|
2336
|
+
// (`justifiedSpaceRatio`, which sets a looser line ragged) is not
|
|
2337
|
+
// the rule of a CJK line, so the line reports none.
|
|
2338
|
+
spaceWidth = (natural + slack) / spaces;
|
|
2339
|
+
}
|
|
1679
2340
|
else if (spaces > 0) {
|
|
1680
|
-
//
|
|
1681
|
-
// line (the renderers stretch
|
|
2341
|
+
// Latin words only (an English sentence in a CJK paragraph): the
|
|
2342
|
+
// spaces take it all, as in a Latin line (the renderers stretch
|
|
2343
|
+
// them, and a line past the ratio is set ragged).
|
|
1682
2344
|
if (ctx.normalSpace > 0)
|
|
1683
2345
|
spaceRatio = (natural + slack) / spaces / ctx.normalSpace;
|
|
1684
2346
|
}
|
|
@@ -1727,6 +2389,7 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1727
2389
|
...(s.marks ? { cjkMarks: s.marks } : {}),
|
|
1728
2390
|
...(s.inserted ? { inserted: true } : {}),
|
|
1729
2391
|
...(s.color ? { color: s.color } : {}),
|
|
2392
|
+
...(s.lang !== undefined ? { lang: s.lang } : {}),
|
|
1730
2393
|
...(t !== undefined ? { tracking: t } : {}),
|
|
1731
2394
|
// Every mark that gave up blank carries its ink offset (0 when only
|
|
1732
2395
|
// the blank after its glyph went), and so does a shared mark set in
|
|
@@ -1738,9 +2401,38 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1738
2401
|
...(u.shift !== undefined ? { baselineShift: u.shift } : {}),
|
|
1739
2402
|
};
|
|
1740
2403
|
};
|
|
2404
|
+
/** The segment of a ruby base `width` px wide, `t` its tracking. */
|
|
2405
|
+
const rubySegment = (u, width, t) => {
|
|
2406
|
+
const ruby = u.ruby;
|
|
2407
|
+
const g = ruby.geometry;
|
|
2408
|
+
// The box less the advance kanbun marks add after it (#430).
|
|
2409
|
+
const box = u.width - (u.kunten?.extra ?? 0);
|
|
2410
|
+
// A Japanese ruby (#422) names its annotation (`id`), so renderers
|
|
2411
|
+
// can set one `<ruby>` for a word.
|
|
2412
|
+
const jis = ruby.jis !== undefined;
|
|
2413
|
+
return {
|
|
2414
|
+
...segmentOf(u, width, t),
|
|
2415
|
+
text: u.text,
|
|
2416
|
+
...(g.inset > 1e-9 ? { inkOffset: g.inset } : {}),
|
|
2417
|
+
// A base spread 1:2:1 under a longer Japanese reading.
|
|
2418
|
+
...(g.tracking ? { tracking: g.tracking } : {}),
|
|
2419
|
+
ruby: {
|
|
2420
|
+
text: ruby.span.text,
|
|
2421
|
+
fontString: ruby.span.fontString ?? g.runs[0]?.fontString ?? u.style.font,
|
|
2422
|
+
baseWidth: ruby.base ?? box - g.inset * 2 - (ruby.position === 'right' ? g.rtWidth : 0),
|
|
2423
|
+
rtWidth: g.rtWidth,
|
|
2424
|
+
position: ruby.position,
|
|
2425
|
+
...(ruby.span.group ? { group: true } : {}),
|
|
2426
|
+
...(ruby.span.color ? { color: ruby.span.color } : {}),
|
|
2427
|
+
...(jis ? { id: ruby.span.id } : {}),
|
|
2428
|
+
...(jis && ruby.span.jukugo ? { jukugo: true } : {}),
|
|
2429
|
+
runs: g.runs,
|
|
2430
|
+
},
|
|
2431
|
+
};
|
|
2432
|
+
};
|
|
1741
2433
|
for (let j = 0; j < us.length; j++) {
|
|
1742
2434
|
const u = us[j];
|
|
1743
|
-
if (u.kind === 'space' && u.auto) {
|
|
2435
|
+
if (u.kind === 'space' && (u.auto || u.aki)) {
|
|
1744
2436
|
pieces.push({ seg: { kind: 'space', text: u.text, width: u.width, autospace: true }, parts: [u.text], key: undefined });
|
|
1745
2437
|
continue;
|
|
1746
2438
|
}
|
|
@@ -1750,6 +2442,9 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1750
2442
|
}
|
|
1751
2443
|
if (u.kind === 'atomic') {
|
|
1752
2444
|
const seg = tokenSegment(u.token);
|
|
2445
|
+
// A side marker carries the gap after the character it marks.
|
|
2446
|
+
if (seg.sideMarker && tracking > 0 && stretches[j])
|
|
2447
|
+
seg.width += tracking;
|
|
1753
2448
|
pieces.push({ seg, parts: [seg.text], key: undefined });
|
|
1754
2449
|
continue;
|
|
1755
2450
|
}
|
|
@@ -1759,6 +2454,21 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1759
2454
|
pieces.push({ seg: { kind: 'text', text: u.text, width: u.width + gap, warichu: u.warichu }, parts: [u.text], key: undefined });
|
|
1760
2455
|
continue;
|
|
1761
2456
|
}
|
|
2457
|
+
if (u.kunten?.vdt) {
|
|
2458
|
+
// A character with kanbun marks (#430): a segment of its own, the
|
|
2459
|
+
// marks painted from its start; the room they take after it (inline
|
|
2460
|
+
// 返り点, a 竪点, 送り仮名 that run past it) and the gap follow as a
|
|
2461
|
+
// space of their own, so the character is painted at its natural
|
|
2462
|
+
// spacing (a ruby base in it centred under its reading).
|
|
2463
|
+
const extra = u.kunten.extra ?? 0;
|
|
2464
|
+
const own = u.width - extra;
|
|
2465
|
+
const seg = u.ruby?.geometry ? rubySegment(u, own, undefined) : { ...segmentOf(u, own, undefined), text: u.text };
|
|
2466
|
+
seg.kunten = u.kunten.vdt;
|
|
2467
|
+
pieces.push({ seg, parts: [u.text], key: undefined });
|
|
2468
|
+
if (extra + gap > 0)
|
|
2469
|
+
pieces.push({ seg: { kind: 'space', text: '', width: extra + gap, autospace: true }, parts: [''], key: undefined });
|
|
2470
|
+
continue;
|
|
2471
|
+
}
|
|
1762
2472
|
if (u.ruby?.geometry) {
|
|
1763
2473
|
// A ruby base: centred in its box, its reading placed from the box's
|
|
1764
2474
|
// start (#194). One character takes the gap after it as tracking; a
|
|
@@ -1766,23 +2476,8 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1766
2476
|
// reading, and the gap follows the box as a space of its own (its
|
|
1767
2477
|
// width final, as a Han–Latin space's is): tracking would be added
|
|
1768
2478
|
// after each of its characters.
|
|
1769
|
-
const g = u.ruby.geometry;
|
|
1770
2479
|
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
|
-
};
|
|
2480
|
+
const seg = rubySegment(u, gapAfter ? u.width : u.width + gap, gap > 0 && !gapAfter ? gap : undefined);
|
|
1786
2481
|
pieces.push({ seg, parts: [u.text], key: undefined });
|
|
1787
2482
|
if (gapAfter)
|
|
1788
2483
|
pieces.push({ seg: { kind: 'space', text: '', width: gap, autospace: true }, parts: [''], key: undefined });
|
|
@@ -1848,6 +2543,40 @@ function composeLine(units, range, li, isLast, ctx) {
|
|
|
1848
2543
|
cjkComposed: true,
|
|
1849
2544
|
};
|
|
1850
2545
|
}
|
|
2546
|
+
/**
|
|
2547
|
+
* The first line's indent of a paragraph that opens with an opening
|
|
2548
|
+
* bracket, as `cjk.paragraphStartBracket` sets it (JLReq §3.1.5), with the
|
|
2549
|
+
* bracket's blank before its glyph given up (it is `units`' own unit,
|
|
2550
|
+
* replaced): pattern ① (`indent`) keeps the indent, ③ (`half`) sets the
|
|
2551
|
+
* bracket's glyph inside the indent so the text after it starts where the
|
|
2552
|
+
* indent ends (the bracket and its half em of blank fill a one-em indent),
|
|
2553
|
+
* 天付き (`flush`) drops the indent. `indentPx` when the composition leaves
|
|
2554
|
+
* the bracket to the line-start rules, the paragraph has no indent or
|
|
2555
|
+
* does not open with a bracket.
|
|
2556
|
+
*/
|
|
2557
|
+
function paragraphStartIndent(units, indentPx, c) {
|
|
2558
|
+
const pattern = c.paragraphStartBracket;
|
|
2559
|
+
if (!pattern || indentPx <= 0)
|
|
2560
|
+
return indentPx;
|
|
2561
|
+
const f = units.findIndex((u) => u.kind !== 'space');
|
|
2562
|
+
const u = units[f];
|
|
2563
|
+
if (!u?.punct || u.first !== 'opening')
|
|
2564
|
+
return indentPx;
|
|
2565
|
+
// Its blank before the glyph, whatever `trimLineStart` says.
|
|
2566
|
+
const box = { ...u.punct };
|
|
2567
|
+
const lead = box.blank - box.cutStart;
|
|
2568
|
+
const trimmed = { ...u, punct: box };
|
|
2569
|
+
if (lead > 0 && box.side === 'start') {
|
|
2570
|
+
box.cutStart += lead;
|
|
2571
|
+
trimmed.width -= lead;
|
|
2572
|
+
}
|
|
2573
|
+
units[f] = trimmed;
|
|
2574
|
+
if (pattern === 'flush')
|
|
2575
|
+
return 0;
|
|
2576
|
+
if (pattern === 'half')
|
|
2577
|
+
return Math.max(0, indentPx - trimmed.width);
|
|
2578
|
+
return indentPx;
|
|
2579
|
+
}
|
|
1851
2580
|
/**
|
|
1852
2581
|
* Set a Chinese, Japanese or Korean paragraph (see the module comment):
|
|
1853
2582
|
* `options.cjkLineBreak` (else the document's level) decides where lines
|
|
@@ -1885,14 +2614,23 @@ export function composeCjkParagraph(spans, normalFont, boldFont, italicFont, bol
|
|
|
1885
2614
|
// Ruby readings (#194), laid out once their neighbours are known.
|
|
1886
2615
|
if (units.some((u) => u.ruby))
|
|
1887
2616
|
sizeRubies(units);
|
|
2617
|
+
// Kanbun marks (#430), beside the readings.
|
|
2618
|
+
if (units.some((u) => u.kunten))
|
|
2619
|
+
sizeKunten(units, letterSpacingPx);
|
|
1888
2620
|
if (!units.some((u) => u.kind !== 'space'))
|
|
1889
2621
|
return { lines: [], totalHeight: 0 };
|
|
1890
2622
|
const fit = lineFitOf(composition);
|
|
1891
2623
|
const level = options?.cjkLineBreak ?? getCjkLineBreak();
|
|
1892
|
-
|
|
2624
|
+
// `keep-all` (the paragraph's own, else the document's): lines break
|
|
2625
|
+
// between phrases, and inside one only when it is longer than the line.
|
|
2626
|
+
const keepAll = options?.cjkWordBreak !== undefined ? options.cjkWordBreak === 'keep-all' : composition.keepAll === true;
|
|
2627
|
+
const levelBreaks = breakOpportunities(units, level);
|
|
2628
|
+
const breaks = keepAll ? keepAllBreaks(units, levelBreaks) : levelBreaks;
|
|
2629
|
+
const emergency = keepAll ? levelBreaks : undefined;
|
|
1893
2630
|
const indentPx = options?.firstLineIndentPx ?? 0;
|
|
1894
2631
|
const hanging = options?.hangingIndent ?? false;
|
|
1895
|
-
const
|
|
2632
|
+
const firstIndent = hanging ? 0 : paragraphStartIndent(units, indentPx, composition);
|
|
2633
|
+
const indentOf = (li) => (indentPx > 0 ? (hanging ? (li === 0 ? 0 : indentPx) : (li === 0 ? firstIndent : 0)) : 0);
|
|
1896
2634
|
const measureOf = (li) => lineMeasure(maxWidthPx, options?.restWidths, li) - indentOf(li);
|
|
1897
2635
|
const sizeMatch = FONT_SIZE_RE.exec(normalFont);
|
|
1898
2636
|
const em = sizeMatch ? parseFloat(sizeMatch[1]) : 16;
|
|
@@ -1914,13 +2652,24 @@ export function composeCjkParagraph(spans, normalFont, boldFont, italicFont, bol
|
|
|
1914
2652
|
level,
|
|
1915
2653
|
};
|
|
1916
2654
|
const compose = (ranges) => ranges.map((r, li) => composeLine(units, r, li, li === ranges.length - 1, ctx));
|
|
1917
|
-
const ranges = breakUnits(units, breaks, measureOf, 0, letterSpacingPx, fit, level);
|
|
2655
|
+
const ranges = breakUnits(units, breaks, measureOf, 0, letterSpacingPx, fit, level, undefined, undefined, emergency);
|
|
1918
2656
|
let lines = compose(ranges);
|
|
1919
2657
|
// 孤字 (clreq §7.2), where the paragraph avoids runts (`bodyText.avoidRunts`
|
|
1920
2658
|
// gives it a runt penalty): a last line that holds one character, alone
|
|
1921
2659
|
// or with its closing marks, takes the last character the line above may
|
|
1922
2660
|
// 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.
|
|
2661
|
+
// would need more than the tracking cap, the runt stays. Japanese text
|
|
2662
|
+
// (泣き別れ) first tries the other way JLReq prefers (§3.8.2): the line
|
|
2663
|
+
// above takes the character in by giving up blank (push-in), and the
|
|
2664
|
+
// paragraph is a line shorter.
|
|
2665
|
+
if ((options?.runtPenalty ?? 0) > 0 && endsOnOneCharacter(lines) && fit?.jlreq) {
|
|
2666
|
+
const pulled = breakUnits(units, breaks, measureOf, 0, letterSpacingPx, fit, level, undefined, ranges.length - 2, emergency);
|
|
2667
|
+
if (pulled.length === ranges.length - 1) {
|
|
2668
|
+
const set = compose(pulled);
|
|
2669
|
+
if (!set.some((l) => l.cjkLoose) && !endsOnOneCharacter(set))
|
|
2670
|
+
lines = set;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
1924
2673
|
if ((options?.runtPenalty ?? 0) > 0 && endsOnOneCharacter(lines)) {
|
|
1925
2674
|
const li = ranges.length - 2;
|
|
1926
2675
|
const above = ranges[li];
|
|
@@ -1928,7 +2677,7 @@ export function composeCjkParagraph(spans, normalFont, boldFont, italicFont, bol
|
|
|
1928
2677
|
while (at > above.start && !breaks[at])
|
|
1929
2678
|
at--;
|
|
1930
2679
|
if (!above.head && !above.hyphenated && !ranges[li + 1].first && at > above.start) {
|
|
1931
|
-
const pushed = breakUnits(units, breaks, measureOf, 0, letterSpacingPx, fit, level, { line: li, at });
|
|
2680
|
+
const pushed = breakUnits(units, breaks, measureOf, 0, letterSpacingPx, fit, level, { line: li, at }, undefined, emergency);
|
|
1932
2681
|
if (pushed.length === ranges.length) {
|
|
1933
2682
|
const set = compose(pushed);
|
|
1934
2683
|
if (!set[li].cjkLoose && !endsOnOneCharacter(set))
|
|
@@ -1946,7 +2695,7 @@ export function composeCjkParagraph(spans, normalFont, boldFont, italicFont, bol
|
|
|
1946
2695
|
if (looseness > 0) {
|
|
1947
2696
|
const target = lines.length + looseness;
|
|
1948
2697
|
for (let step = 1; step <= 16; step++) {
|
|
1949
|
-
const ranges = breakUnits(units, breaks, measureOf, (step * em) / 8, letterSpacingPx, fit, level);
|
|
2698
|
+
const ranges = breakUnits(units, breaks, measureOf, (step * em) / 8, letterSpacingPx, fit, level, undefined, undefined, emergency);
|
|
1950
2699
|
if (ranges.length < target)
|
|
1951
2700
|
continue;
|
|
1952
2701
|
if (ranges.length > target)
|