postext 1.29.2 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/arabicLocale.test.js +5 -5
- package/dist/__tests__/arabicLocale.test.js.map +1 -1
- package/dist/__tests__/bundle/balloonPlacementPin.test.js +5 -4
- package/dist/__tests__/bundle/balloonPlacementPin.test.js.map +1 -1
- package/dist/__tests__/bundle/cjkRulesPin.test.js +8 -6
- package/dist/__tests__/bundle/cjkRulesPin.test.js.map +1 -1
- package/dist/__tests__/bundle/designOverflowPin.test.js +1 -1
- package/dist/__tests__/bundle/flowColumnsPin.test.js +4 -3
- package/dist/__tests__/bundle/flowColumnsPin.test.js.map +1 -1
- package/dist/__tests__/bundle/gridBalancingPin.test.js +5 -4
- package/dist/__tests__/bundle/gridBalancingPin.test.js.map +1 -1
- package/dist/__tests__/bundle/gridBreakRulesPin.test.js +14 -10
- package/dist/__tests__/bundle/gridBreakRulesPin.test.js.map +1 -1
- package/dist/__tests__/bundle/indexGroupSplitPin.test.js +4 -3
- package/dist/__tests__/bundle/indexGroupSplitPin.test.js.map +1 -1
- package/dist/__tests__/bundle/looseOrderPin.test.js +5 -4
- package/dist/__tests__/bundle/looseOrderPin.test.js.map +1 -1
- package/dist/__tests__/bundle/refLabelBreaksPin.test.js +5 -4
- package/dist/__tests__/bundle/refLabelBreaksPin.test.js.map +1 -1
- package/dist/__tests__/bundle/scriptContextPin.test.js +6 -5
- package/dist/__tests__/bundle/scriptContextPin.test.js.map +1 -1
- package/dist/__tests__/bundle/shapingLanguagePin.test.js +142 -46
- package/dist/__tests__/bundle/shapingLanguagePin.test.js.map +1 -1
- package/dist/__tests__/bundle/taskBoxPin.test.js +1 -1
- package/dist/__tests__/bundle/trailingStepPin.test.js +4 -3
- package/dist/__tests__/bundle/trailingStepPin.test.js.map +1 -1
- package/dist/__tests__/bundle/wideFloatStretchPin.test.js +4 -3
- package/dist/__tests__/bundle/wideFloatStretchPin.test.js.map +1 -1
- package/dist/__tests__/bundle.test.js +1 -1
- package/dist/__tests__/cellText.test.d.ts +2 -0
- package/dist/__tests__/cellText.test.d.ts.map +1 -0
- package/dist/__tests__/cellText.test.js +281 -0
- package/dist/__tests__/cellText.test.js.map +1 -0
- package/dist/__tests__/chineseLocale.test.js +13 -11
- package/dist/__tests__/chineseLocale.test.js.map +1 -1
- package/dist/__tests__/cjk/japaneseHtml.test.js +3 -3
- package/dist/__tests__/cjk/japaneseHtml.test.js.map +1 -1
- package/dist/__tests__/cjk/japaneseRuby.test.js +1 -1
- package/dist/__tests__/cjk/japaneseRuby.test.js.map +1 -1
- package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js +3 -3
- package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js.map +1 -1
- package/dist/__tests__/codeBlocks.test.js +1 -1
- package/dist/__tests__/contextText.test.d.ts +2 -0
- package/dist/__tests__/contextText.test.d.ts.map +1 -0
- package/dist/__tests__/contextText.test.js +371 -0
- package/dist/__tests__/contextText.test.js.map +1 -0
- package/dist/__tests__/design/designTextRender.test.js +8 -1
- package/dist/__tests__/design/designTextRender.test.js.map +1 -1
- package/dist/__tests__/exports.test.js +6 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/fontUsage.test.js +40 -0
- package/dist/__tests__/fontUsage.test.js.map +1 -1
- package/dist/__tests__/hardBreaks.test.js +1 -1
- package/dist/__tests__/headingInlineMarks.test.js +1 -1
- package/dist/__tests__/html-backend.test.js +6 -2
- package/dist/__tests__/html-backend.test.js.map +1 -1
- package/dist/__tests__/japaneseNotes.test.js +4 -2
- package/dist/__tests__/japaneseNotes.test.js.map +1 -1
- package/dist/__tests__/lineEndPieces.test.d.ts +2 -0
- package/dist/__tests__/lineEndPieces.test.d.ts.map +1 -0
- package/dist/__tests__/lineEndPieces.test.js +374 -0
- package/dist/__tests__/lineEndPieces.test.js.map +1 -0
- package/dist/__tests__/mathSizeMigration.test.js +2 -2
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
- package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
- package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
- package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
- package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
- package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
- package/dist/__tests__/plainGeminateHead.test.d.ts +2 -0
- package/dist/__tests__/plainGeminateHead.test.d.ts.map +1 -0
- package/dist/__tests__/plainGeminateHead.test.js +92 -0
- package/dist/__tests__/plainGeminateHead.test.js.map +1 -0
- package/dist/__tests__/presets/showcaseShapingLanguage.test.d.ts +2 -0
- package/dist/__tests__/presets/showcaseShapingLanguage.test.d.ts.map +1 -0
- package/dist/__tests__/presets/showcaseShapingLanguage.test.js +52 -0
- package/dist/__tests__/presets/showcaseShapingLanguage.test.js.map +1 -0
- package/dist/__tests__/reshapedTracking.test.d.ts +2 -0
- package/dist/__tests__/reshapedTracking.test.d.ts.map +1 -0
- package/dist/__tests__/reshapedTracking.test.js +358 -0
- package/dist/__tests__/reshapedTracking.test.js.map +1 -0
- package/dist/__tests__/rtl/designRunsPainted.test.d.ts +2 -0
- package/dist/__tests__/rtl/designRunsPainted.test.d.ts.map +1 -0
- package/dist/__tests__/rtl/designRunsPainted.test.js +184 -0
- package/dist/__tests__/rtl/designRunsPainted.test.js.map +1 -0
- package/dist/__tests__/rtl/designTextPaint.test.js +5 -1
- package/dist/__tests__/rtl/designTextPaint.test.js.map +1 -1
- package/dist/__tests__/rtl/htmlRtl.test.js +3 -3
- package/dist/__tests__/rtl/htmlRtl.test.js.map +1 -1
- package/dist/__tests__/rtl/listMarkerDirection.test.js +28 -28
- package/dist/__tests__/rtl/listMarkerDirection.test.js.map +1 -1
- package/dist/__tests__/rtl/tocNumberPainted.test.d.ts +2 -0
- package/dist/__tests__/rtl/tocNumberPainted.test.d.ts.map +1 -0
- package/dist/__tests__/rtl/tocNumberPainted.test.js +113 -0
- package/dist/__tests__/rtl/tocNumberPainted.test.js.map +1 -0
- package/dist/__tests__/scriptContext.test.js +141 -1
- package/dist/__tests__/scriptContext.test.js.map +1 -1
- package/dist/__tests__/shapingLanguage.test.js +54 -41
- package/dist/__tests__/shapingLanguage.test.js.map +1 -1
- package/dist/__tests__/trackedLetters.test.d.ts +2 -0
- package/dist/__tests__/trackedLetters.test.d.ts.map +1 -0
- package/dist/__tests__/trackedLetters.test.js +380 -0
- package/dist/__tests__/trackedLetters.test.js.map +1 -0
- package/dist/__tests__/trackedLigatures.test.js +17 -5
- package/dist/__tests__/trackedLigatures.test.js.map +1 -1
- package/dist/__tests__/verseLines.test.js +1 -1
- package/dist/__tests__/wordCacheOrder.test.d.ts +2 -0
- package/dist/__tests__/wordCacheOrder.test.d.ts.map +1 -0
- package/dist/__tests__/wordCacheOrder.test.js +352 -0
- package/dist/__tests__/wordCacheOrder.test.js.map +1 -0
- package/dist/__tests__/wordPieces.test.d.ts +2 -0
- package/dist/__tests__/wordPieces.test.d.ts.map +1 -0
- package/dist/__tests__/wordPieces.test.js +762 -0
- package/dist/__tests__/wordPieces.test.js.map +1 -0
- package/dist/bidi.d.ts +12 -0
- package/dist/bidi.d.ts.map +1 -1
- package/dist/bidi.js +23 -0
- package/dist/bidi.js.map +1 -1
- package/dist/bundle/configVersion.d.ts +49 -23
- package/dist/bundle/configVersion.d.ts.map +1 -1
- package/dist/bundle/configVersion.js +54 -25
- package/dist/bundle/configVersion.js.map +1 -1
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +58 -27
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/cellText.d.ts +12 -0
- package/dist/canvas-backend/cellText.d.ts.map +1 -0
- package/dist/canvas-backend/cellText.js +114 -0
- package/dist/canvas-backend/cellText.js.map +1 -0
- package/dist/canvas-backend/chip.d.ts.map +1 -1
- package/dist/canvas-backend/chip.js +2 -1
- package/dist/canvas-backend/chip.js.map +1 -1
- package/dist/canvas-backend/contextState.d.ts +7 -0
- package/dist/canvas-backend/contextState.d.ts.map +1 -1
- package/dist/canvas-backend/contextState.js +7 -0
- package/dist/canvas-backend/contextState.js.map +1 -1
- package/dist/canvas-backend/contextText.d.ts +76 -0
- package/dist/canvas-backend/contextText.d.ts.map +1 -0
- package/dist/canvas-backend/contextText.js +102 -0
- package/dist/canvas-backend/contextText.js.map +1 -0
- package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
- package/dist/canvas-backend/headerFooter.js +8 -1
- package/dist/canvas-backend/headerFooter.js.map +1 -1
- package/dist/canvas-backend/index.d.ts.map +1 -1
- package/dist/canvas-backend/index.js +19 -13
- package/dist/canvas-backend/index.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +12 -6
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/canvas-backend/scriptText.d.ts.map +1 -1
- package/dist/canvas-backend/scriptText.js +3 -2
- package/dist/canvas-backend/scriptText.js.map +1 -1
- package/dist/canvas-backend/segmentText.d.ts.map +1 -1
- package/dist/canvas-backend/segmentText.js +8 -2
- package/dist/canvas-backend/segmentText.js.map +1 -1
- package/dist/canvas-backend/trackedText.d.ts +75 -1
- package/dist/canvas-backend/trackedText.d.ts.map +1 -1
- package/dist/canvas-backend/trackedText.js +185 -22
- package/dist/canvas-backend/trackedText.js.map +1 -1
- package/dist/canvas-backend/verticalText.d.ts +9 -0
- package/dist/canvas-backend/verticalText.d.ts.map +1 -1
- package/dist/canvas-backend/verticalText.js +29 -7
- package/dist/canvas-backend/verticalText.js.map +1 -1
- package/dist/canvas-backend/wordPieces.d.ts +31 -0
- package/dist/canvas-backend/wordPieces.d.ts.map +1 -0
- package/dist/canvas-backend/wordPieces.js +132 -0
- package/dist/canvas-backend/wordPieces.js.map +1 -0
- package/dist/canvas-backend/wordRuns.d.ts +3 -1
- package/dist/canvas-backend/wordRuns.d.ts.map +1 -1
- package/dist/canvas-backend/wordRuns.js +10 -6
- package/dist/canvas-backend/wordRuns.js.map +1 -1
- package/dist/comics/lettering/__tests__/fitAsPainted.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/fitAsPainted.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/fitAsPainted.test.js +97 -0
- package/dist/comics/lettering/__tests__/fitAsPainted.test.js.map +1 -0
- package/dist/comics/lettering/shape-text.d.ts.map +1 -1
- package/dist/comics/lettering/shape-text.js +39 -11
- package/dist/comics/lettering/shape-text.js.map +1 -1
- package/dist/defaults/bodyText.d.ts.map +1 -1
- package/dist/defaults/bodyText.js +6 -5
- package/dist/defaults/bodyText.js.map +1 -1
- package/dist/design/bidiText.d.ts +45 -6
- package/dist/design/bidiText.d.ts.map +1 -1
- package/dist/design/bidiText.js +78 -10
- package/dist/design/bidiText.js.map +1 -1
- package/dist/design/cjkText.d.ts +3 -0
- package/dist/design/cjkText.d.ts.map +1 -1
- package/dist/design/cjkText.js +1 -1
- package/dist/design/cjkText.js.map +1 -1
- package/dist/design/layout.d.ts.map +1 -1
- package/dist/design/layout.js +36 -5
- package/dist/design/layout.js.map +1 -1
- package/dist/design/richText.d.ts +9 -1
- package/dist/design/richText.d.ts.map +1 -1
- package/dist/design/richText.js +8 -2
- package/dist/design/richText.js.map +1 -1
- package/dist/fonts/usage.d.ts +2 -0
- package/dist/fonts/usage.d.ts.map +1 -1
- package/dist/fonts/usage.js +18 -0
- package/dist/fonts/usage.js.map +1 -1
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +664 -84
- package/dist/html-backend.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/knuthPlass/breakpoints.d.ts.map +1 -1
- package/dist/knuthPlass/breakpoints.js +4 -2
- package/dist/knuthPlass/breakpoints.js.map +1 -1
- package/dist/knuthPlass/pretextAdapter.d.ts +6 -3
- package/dist/knuthPlass/pretextAdapter.d.ts.map +1 -1
- package/dist/knuthPlass/pretextAdapter.js +11 -3
- package/dist/knuthPlass/pretextAdapter.js.map +1 -1
- package/dist/knuthPlass/richAdapter.d.ts +13 -1
- package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
- package/dist/knuthPlass/richAdapter.js +53 -5
- package/dist/knuthPlass/richAdapter.js.map +1 -1
- package/dist/knuthPlass/tracking.d.ts.map +1 -1
- package/dist/knuthPlass/tracking.js +3 -2
- package/dist/knuthPlass/tracking.js.map +1 -1
- package/dist/knuthPlass/types.d.ts +6 -0
- package/dist/knuthPlass/types.d.ts.map +1 -1
- package/dist/locale.d.ts +21 -17
- package/dist/locale.d.ts.map +1 -1
- package/dist/locale.js +23 -19
- package/dist/locale.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 +165 -85
- package/dist/measure/canvas.js.map +1 -1
- package/dist/measure/cjkCompose.d.ts.map +1 -1
- package/dist/measure/cjkCompose.js +31 -5
- package/dist/measure/cjkCompose.js.map +1 -1
- package/dist/measure/plain.d.ts.map +1 -1
- package/dist/measure/plain.js +31 -6
- package/dist/measure/plain.js.map +1 -1
- package/dist/measure/pretextLanguage.d.ts.map +1 -1
- package/dist/measure/pretextLanguage.js +49 -7
- package/dist/measure/pretextLanguage.js.map +1 -1
- package/dist/measure/rich.d.ts +18 -1
- package/dist/measure/rich.d.ts.map +1 -1
- package/dist/measure/rich.js +200 -29
- package/dist/measure/rich.js.map +1 -1
- package/dist/measure/script.d.ts +17 -1
- package/dist/measure/script.d.ts.map +1 -1
- package/dist/measure/script.js +17 -1
- package/dist/measure/script.js.map +1 -1
- package/dist/measure/trackedLigatures.d.ts +59 -0
- package/dist/measure/trackedLigatures.d.ts.map +1 -0
- package/dist/measure/trackedLigatures.js +144 -0
- package/dist/measure/trackedLigatures.js.map +1 -0
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +4 -3
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/calloutLayout.d.ts.map +1 -1
- package/dist/pipeline/calloutLayout.js +26 -7
- package/dist/pipeline/calloutLayout.js.map +1 -1
- package/dist/pipeline/lists.d.ts +13 -4
- package/dist/pipeline/lists.d.ts.map +1 -1
- package/dist/pipeline/lists.js +49 -12
- package/dist/pipeline/lists.js.map +1 -1
- package/dist/pipeline/toc.d.ts.map +1 -1
- package/dist/pipeline/toc.js +33 -9
- package/dist/pipeline/toc.js.map +1 -1
- package/dist/types.d.ts +35 -26
- package/dist/types.d.ts.map +1 -1
- package/dist/wordPieces.d.ts +120 -0
- package/dist/wordPieces.d.ts.map +1 -0
- package/dist/wordPieces.js +319 -0
- package/dist/wordPieces.js.map +1 -0
- package/package.json +1 -1
package/dist/html-backend.js
CHANGED
|
@@ -7,14 +7,17 @@ import { registeredFontSyncProvider } from './svg/fontRegistry';
|
|
|
7
7
|
import { svgPictureFontWarning } from './svg/image';
|
|
8
8
|
import { lineInkExtent, lineTrailingTracking } from './lineInk';
|
|
9
9
|
import { CHARACTER_GRID_COLOR, cjkGridCells } from './pipeline/cjkGrid';
|
|
10
|
-
import { isJapaneseLanguage, renderLangOf, shapingLanguageOf } from './locale';
|
|
10
|
+
import { isJapaneseLanguage, renderLangOf, shapingLanguageOf, shapingTagOf } from './locale';
|
|
11
11
|
import { hasCJK } from './measure/cjk';
|
|
12
|
-
import {
|
|
12
|
+
import { arabicMarkNeeded, needsBidi } from './bidi';
|
|
13
13
|
import { joiningScriptIn } from './measure/joining';
|
|
14
|
-
import { scriptContextUntracked, scriptMark } from './measure/script';
|
|
14
|
+
import { faceReadsScript, isLetterless, rtlTextWidth, scriptContextUntracked, scriptMark, scriptTextWidth } from './measure/script';
|
|
15
15
|
import { DEFAULT_CENTRAL_BASELINE, verticalFlowOf } from './vdt';
|
|
16
16
|
import { holdsTurnedMark, segmentOrientation, verticalRuns } from './writingMode';
|
|
17
|
-
import { graphemesOf } from './measure/graphemes';
|
|
17
|
+
import { graphemeCount, graphemesOf } from './measure/graphemes';
|
|
18
|
+
import { measureSpacedWidth, measureTextWidth, withMeasureLanguage } from './measure/canvas';
|
|
19
|
+
import { trackedLigaturePieces } from './measure/trackedLigatures';
|
|
20
|
+
import { cellsOf, lineWordSegmentsInOrder, sameSetWidth } from './wordPieces';
|
|
18
21
|
import { fontFamilyOf } from './measure/vertical';
|
|
19
22
|
import { lineMarksHtml, rubyHtml, verticalLineMarksHtml, warichuHtml, sideMarkerHtml, RT_OPEN, RUBY_OPEN, rubyGroupOf, kuntenHtml, verticalKuntenTateHtml } from './htmlAnnotations';
|
|
20
23
|
import { playMarkTriangle, qrModuleRuns } from './pipeline/videoOverlay';
|
|
@@ -67,8 +70,53 @@ const SCRIPT_MARK_DECL = 'font-size:0;-webkit-user-select:none;user-select:none;
|
|
|
67
70
|
const CJK_TRACKED_TEXT_DECL = "text-spacing-trim:space-all;text-autospace:no-autospace;font-feature-settings:'chws' 0,'halt' 0,'vchw' 0,'liga' 1,'clig' 1;";
|
|
68
71
|
/** {@link CJK_FEATURES_DECL} for a box of a tracked text. */
|
|
69
72
|
const CJK_TRACKED_FEATURES_DECL = "font-feature-settings:'chws' 0,'halt' 0,'vchw' 0,'liga' 1,'clig' 1;";
|
|
73
|
+
/**
|
|
74
|
+
* Whether the document being written is shaped in its own language
|
|
75
|
+
* (`bodyText.shapingLanguage: 'document'`, #770) and something measures:
|
|
76
|
+
* the rules below that follow its measure apply, and a document that
|
|
77
|
+
* keeps its host's language is written as it was. Set by
|
|
78
|
+
* {@link renderToHtmlIndexed} while it writes its pages.
|
|
79
|
+
*
|
|
80
|
+
* The first of them: a ligature of a tracked text takes the tracking of
|
|
81
|
+
* each of its letters. The layout counts the tracking once a letter. With
|
|
82
|
+
* the ligatures asked for as features ({@link TRACKED_LIGATURES_DECL}) the
|
|
83
|
+
* browser gives a ligature one tracking, as one glyph, and the word ends
|
|
84
|
+
* a tracking short of its box for each letter joined; in a face whose
|
|
85
|
+
* ligature is as wide as its letters the canvas sets the letters apart,
|
|
86
|
+
* each tracked, and the box asked for the ligature. Chrome 154 at 40 px
|
|
87
|
+
* with 0.4 px a letter, against the width measured: `office` in Source
|
|
88
|
+
* Serif 4 −0.8 px, `first` in Geist −0.4 px (on the canvas 0). So such a
|
|
89
|
+
* document asks for no feature: its tracked text is shaped as the canvas
|
|
90
|
+
* shapes it under the letter-spacing, and a word that would lose a
|
|
91
|
+
* ligature to it is written in the canvas's pieces
|
|
92
|
+
* ({@link trackedWordHtml}): −0.003 and +0.007 px for the same two words,
|
|
93
|
+
* 0.063 px at most over 154 words of seven faces.
|
|
94
|
+
*/
|
|
95
|
+
let ownShaping = false;
|
|
96
|
+
/** {@link TRACKED_LIGATURES_DECL} where a tracked box asks for its
|
|
97
|
+
* ligatures, nothing where its words are written in pieces
|
|
98
|
+
* ({@link ownShaping}). */
|
|
99
|
+
const ligaturesDecl = () => (ownShaping ? '' : TRACKED_LIGATURES_DECL);
|
|
70
100
|
/** {@link CJK_TEXT_DECL} of a box, with its ligatures when it is `tracked`. */
|
|
71
|
-
const cjkTextDecl = (tracked) => (tracked ? CJK_TRACKED_TEXT_DECL : CJK_TEXT_DECL);
|
|
101
|
+
const cjkTextDecl = (tracked) => (tracked && !ownShaping ? CJK_TRACKED_TEXT_DECL : CJK_TEXT_DECL);
|
|
102
|
+
/** {@link CJK_FEATURES_DECL} of a box in another face, with its ligatures
|
|
103
|
+
* when it is `tracked`. */
|
|
104
|
+
const cjkFeaturesDecl = (tracked) => (tracked && !ownShaping ? CJK_TRACKED_FEATURES_DECL : CJK_FEATURES_DECL);
|
|
105
|
+
/**
|
|
106
|
+
* The box of the cells of a line of the CJK composer (#770). The composer
|
|
107
|
+
* measures each Chinese or Japanese character alone and a line carries
|
|
108
|
+
* the cells of one style as one segment (`cellsOf`); set in one box, the
|
|
109
|
+
* browser kerns inside it: a kana against the mark after it, a kana
|
|
110
|
+
* against a kana. Chrome 154, thousandths of the em under `lang="ja"`:
|
|
111
|
+
* Noto Sans JP `す。` −100, `れ、` +11; Noto Serif JP `す。` −130; and
|
|
112
|
+
* it kerns more pairs than the canvas (21 of 41 texts against 13 in Noto
|
|
113
|
+
* Sans JP). With the kerning off the box is as wide as its cells (41 texts,
|
|
114
|
+
* the two faces, two languages, at 7.5 and 10 px: none differs), and its
|
|
115
|
+
* text stays one run for a selection, a ruby or a link. After the `font`
|
|
116
|
+
* shorthand, which resets it. A Western run was measured with its kerning
|
|
117
|
+
* and keeps it.
|
|
118
|
+
*/
|
|
119
|
+
const CELLS_DECL = 'font-kerning:none;';
|
|
72
120
|
/** Whether a line box carries {@link CJK_TEXT_DECL}: a line of the CJK
|
|
73
121
|
* composer, which it measured character by character, or a line with no
|
|
74
122
|
* segments (one span) holding CJK text. A word-by-word line holding CJK
|
|
@@ -104,10 +152,13 @@ function segmentBox(seg, inherited, rootLang, tracking, font, color, paint) {
|
|
|
104
152
|
const decl = tracking !== 0 && (joiningScriptIn(seg.text) || scriptContextUntracked(seg.scriptContext)) ? 'letter-spacing:0;' : '';
|
|
105
153
|
const html = seg.runs && seg.runs.length > 0 ? wordRunsHtml(seg, font, color, paint)
|
|
106
154
|
: seg.kashida && seg.kashida.length > 0 ? kashidaTextHtml(seg.text, seg.kashida)
|
|
107
|
-
: seg.scriptContext !== undefined ? scriptTextHtml(seg.text, seg.scriptContext, seg.rtl === true) : undefined;
|
|
108
|
-
|
|
155
|
+
: seg.scriptContext !== undefined && !setsAlone(seg, font, tracking) ? scriptTextHtml(seg.text, seg.scriptContext, seg.rtl === true) : undefined;
|
|
156
|
+
// A tracked word is written in the canvas's pieces where it holds a
|
|
157
|
+
// ligature (#770); `font` is the CSS value, which the canvas takes too.
|
|
158
|
+
const tracked = ownShaping && tracking !== 0 && !decl && html === undefined ? { font, tracking, ...(seg.lang !== undefined ? { lang: seg.lang } : {}) } : undefined;
|
|
159
|
+
if (!attrs && !decl && html === undefined && !tracked)
|
|
109
160
|
return PLAIN_BOX;
|
|
110
|
-
return html
|
|
161
|
+
return { attrs, decl, ...(html !== undefined ? { html } : {}), ...(tracked ? { tracked } : {}) };
|
|
111
162
|
}
|
|
112
163
|
/**
|
|
113
164
|
* A word with no letter set in the script the layout measured it in
|
|
@@ -126,6 +177,166 @@ function scriptTextHtml(text, script, rtl) {
|
|
|
126
177
|
const hidden = (mark) => (mark === '' ? '' : `<span aria-hidden="true" style="${SCRIPT_MARK_DECL}">${esc(mark)}</span>`);
|
|
127
178
|
return hidden(lead) + esc(text) + hidden(trail);
|
|
128
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Whether a doubled dash or ellipsis given the forms of Han text
|
|
182
|
+
* (`scriptContext: 'Hani'`) is set with no mark beside it (#770): where
|
|
183
|
+
* the canvas that measured it set it alone as wide as its place.
|
|
184
|
+
*
|
|
185
|
+
* The mark of the Han script is an ideograph, and Chrome's canvas cuts a
|
|
186
|
+
* string before a Han character that follows another one: the word
|
|
187
|
+
* beside the mark is shaped alone there, in no script, and the mark
|
|
188
|
+
* changes nothing. The browser's layout shapes the two as one run of Han
|
|
189
|
+
* text, where the face may answer with the form of the document's
|
|
190
|
+
* language. Chrome 154, Noto Serif TC at 14.58 px under `zh-Hant`: `——`
|
|
191
|
+
* is 24.67 px on the canvas with the mark or without, 24.67 px alone in a
|
|
192
|
+
* box, and 29.17 px in a box with the mark (the full-width dash of the
|
|
193
|
+
* face's `locl`). So the box takes the word alone whenever the measurer
|
|
194
|
+
* says that is what the layout holds; asked in the language the text is
|
|
195
|
+
* shaped in ({@link shapedText}), and the mark stays where that is not
|
|
196
|
+
* known.
|
|
197
|
+
*/
|
|
198
|
+
function setsAlone(seg, font, tracking) {
|
|
199
|
+
const shaped = shapedText;
|
|
200
|
+
if (!shaped || seg.scriptContext !== 'Hani')
|
|
201
|
+
return false;
|
|
202
|
+
const alone = withMeasureLanguage(seg.lang !== undefined ? shapingTagOf(seg.lang) : shaped.lang, () => measureTextWidth(seg.text, font));
|
|
203
|
+
return sameSetWidth(alone + (tracking === 0 ? 0 : tracking * graphemeCount(seg.text)), seg.width);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* A run of design text with no letter, set in the forms the canvas
|
|
207
|
+
* painted it in (#770), or undefined when it is written as it is.
|
|
208
|
+
*
|
|
209
|
+
* A word of body text with no letter carries the script it was measured
|
|
210
|
+
* in (`scriptContext`, #725). A design text carries none: its runs are
|
|
211
|
+
* measured as the canvas paints them, each alone, and a current Chrome
|
|
212
|
+
* gives such a run the face's Latin forms there, while the browser's
|
|
213
|
+
* layout gives a run with no letter the default ones. Chrome 154, Amiri
|
|
214
|
+
* at 25 px: the `(` that opens a date set right to left is 6.38 px on the
|
|
215
|
+
* canvas and 11.45 px in a box, alone or beside the Arabic text; with a
|
|
216
|
+
* Latin mark of no size after it the box is 6.38 px wide. So where the
|
|
217
|
+
* face draws such characters by script and the run is as wide as the
|
|
218
|
+
* canvas sets it in Latin forms (`width` px, `tracking` after each
|
|
219
|
+
* character), it is written beside that mark, in a box of its own
|
|
220
|
+
* direction so the mark's letter orders nothing around it. One word
|
|
221
|
+
* only: in a text of several the mark would reach the others. Asked in
|
|
222
|
+
* the language the text is shaped in ({@link shapedText}); nothing
|
|
223
|
+
* changes where that is not known.
|
|
224
|
+
*/
|
|
225
|
+
function letterlessRunHtml(text, font, rtl, width, tracking) {
|
|
226
|
+
const shaped = shapedText;
|
|
227
|
+
if (!shaped || text.indexOf(' ') >= 0 || !isLetterless(text))
|
|
228
|
+
return undefined;
|
|
229
|
+
return withMeasureLanguage(shaped.lang, () => {
|
|
230
|
+
if (!faceReadsScript(font))
|
|
231
|
+
return undefined;
|
|
232
|
+
const set = scriptTextWidth(text, font, 'Latn', rtl) + (tracking === 0 ? 0 : tracking * graphemeCount(text));
|
|
233
|
+
return sameSetWidth(set, width) ? scriptTextHtml(text, 'Latn', rtl) : undefined;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* {@link CELLS_DECL} for a line or a run of design text set at the
|
|
238
|
+
* glyphs' own advances (#770): when its characters are cells and spaces
|
|
239
|
+
* and the layout holds it `width` px wide, the sum of their advances
|
|
240
|
+
* (`tracking` after each). The canvas, which measured it, kerns fewer
|
|
241
|
+
* pairs of kana than the browser's layout does: the running head
|
|
242
|
+
* `日本語テキスト処理` in Noto Sans JP at 15.6 px is 140.58 px on the
|
|
243
|
+
* canvas, its nine cells, and 140.13 px in a box. Asked in the language
|
|
244
|
+
* the text is shaped in ({@link shapedText}); nothing where that is not
|
|
245
|
+
* known, or where the canvas kerned the text itself.
|
|
246
|
+
*/
|
|
247
|
+
function designCellsDecl(text, font, width, tracking) {
|
|
248
|
+
const shaped = shapedText;
|
|
249
|
+
if (!shaped || !hasCJK(text))
|
|
250
|
+
return '';
|
|
251
|
+
const cells = cellsOf(text.replace(/[ \u3000]+/g, ''));
|
|
252
|
+
if (!cells)
|
|
253
|
+
return '';
|
|
254
|
+
const graphemes = graphemesOf(text);
|
|
255
|
+
const sum = withMeasureLanguage(shaped.lang, () => graphemes.reduce((w, g) => w + measureTextWidth(g, font), 0));
|
|
256
|
+
return sameSetWidth(sum + tracking * graphemes.length, width) ? CELLS_DECL : '';
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* A line of design text that carries directions (`VDTDesignTextLine.order`),
|
|
260
|
+
* as the markup inside its line box (#770): each run in a box of its own,
|
|
261
|
+
* at the place the layout gave it along the line and in its own direction.
|
|
262
|
+
*
|
|
263
|
+
* The canvas paints such a line run by run, each run a text of its own
|
|
264
|
+
* shaped right to left or left to right, and since #770 the layout
|
|
265
|
+
* measures each run so. Set inline in one line box, the browser shapes
|
|
266
|
+
* across the runs: a bracket takes the script of the run beside it and of
|
|
267
|
+
* the bracket it closes (the date `(٧٣٢–٨٠٨هـ)` in Amiri at 25 px is 122.5
|
|
268
|
+
* px run by run and 127.6 px as one line), and the browser orders the
|
|
269
|
+
* runs itself. A box per run is shaped alone, as the canvas shapes it;
|
|
270
|
+
* the markup stays in logical order, so the text copies as written. The
|
|
271
|
+
* boxes carry `white-space:pre` and their line box does not: a mirrored
|
|
272
|
+
* flow turns each of them back about itself (`MIRRORED_FLOW_STYLE`).
|
|
273
|
+
*/
|
|
274
|
+
function directedDesignRunsHtml(line, block, font, tracked) {
|
|
275
|
+
const runs = line.runs;
|
|
276
|
+
const at = new Array(runs.length).fill(0);
|
|
277
|
+
let x = 0;
|
|
278
|
+
// The order the canvas advances in: the line's when it lists every run,
|
|
279
|
+
// else the runs' own (a mirrored flow may leave it out).
|
|
280
|
+
const order = line.order && line.order.length === runs.length ? line.order : runs.map((_, i) => i);
|
|
281
|
+
for (const i of order) {
|
|
282
|
+
at[i] = x;
|
|
283
|
+
x += runs[i].width;
|
|
284
|
+
}
|
|
285
|
+
return runs.map((run, i) => {
|
|
286
|
+
const runFont = quoteFontString(run.fontString);
|
|
287
|
+
const cjkDecl = hasCJK(run.text) ? cjkTextDecl(tracked) : '';
|
|
288
|
+
// A run of several words in the line's font: a box per word, in a run
|
|
289
|
+
// box as wide as the run (see `runWordBoxesHtml`).
|
|
290
|
+
const words = runFont === font && !cjkDecl && !tracked ? runWordBoxesHtml(run.text, run.fontString, run.rtl === true, run.width) : undefined;
|
|
291
|
+
const text = words ?? letterlessRunHtml(run.text, run.fontString, run.rtl === true, run.width, block.letterSpacingPx ?? 0) ?? wordsHtml(run.text, trackedIn(run.fontString, block.letterSpacingPx));
|
|
292
|
+
const top = run.baselineShift ? `${run.baselineShift.toFixed(3)}px` : '0';
|
|
293
|
+
const pos = `position:absolute;left:${at[i].toFixed(3)}px;top:${top};white-space:pre;${words !== undefined ? `width:${run.width.toFixed(3)}px;` : ''}${cjkDecl}`;
|
|
294
|
+
// A run in another face sits on the baseline of the line's own.
|
|
295
|
+
const inner = runFont !== font
|
|
296
|
+
? `<span style="font:${runFont};line-height:0;${cjkDecl ? cjkFeaturesDecl(tracked) : tracked ? ligaturesDecl() : ''}">${text}</span>`
|
|
297
|
+
: text;
|
|
298
|
+
return `<span dir="${run.rtl ? 'rtl' : 'ltr'}" style="${pos}">${inner}</span>`;
|
|
299
|
+
}).join('');
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* A run of a directed design line as a box per word and per run of
|
|
303
|
+
* spaces, each at the place the canvas gives it (#770), or undefined
|
|
304
|
+
* where {@link wordsHtml} sets it.
|
|
305
|
+
*
|
|
306
|
+
* {@link wordsHtml} puts each run of spaces in an inline box, and a box
|
|
307
|
+
* in a line is as wide as a whole number of layout units: the opener
|
|
308
|
+
* `الإخراج مجموعة صفحات، …` of the Arabic guide, 17 words in Amiri at
|
|
309
|
+
* 47.92 px, has 16 spaces of 13.98 px, each 14 px in its box, and the
|
|
310
|
+
* run ended 0.30 px past its 1699.42 px. With every word at its own
|
|
311
|
+
* place nothing adds up ({@link wordBoxesHtml}, which sets a line with no
|
|
312
|
+
* runs so). The places are the canvas's, which shapes a run word by
|
|
313
|
+
* word: each word alone in the run's direction, each run of spaces
|
|
314
|
+
* alone, from the run's start side (its right for a right-to-left run);
|
|
315
|
+
* the caller gives the run's box its width. Only where the words set so
|
|
316
|
+
* add up to the run as the layout measured it (0.01 px), so a run
|
|
317
|
+
* measured otherwise (a document that keeps its host's language, a
|
|
318
|
+
* tracked text) is written as it was; not for a run with a word with no
|
|
319
|
+
* letter, which alone takes other forms than in its text, nor where the
|
|
320
|
+
* language is not known.
|
|
321
|
+
*/
|
|
322
|
+
function runWordBoxesHtml(text, font, rtl, width) {
|
|
323
|
+
const shaped = shapedText;
|
|
324
|
+
if (!shaped || text.indexOf(' ') < 0 || text.trim() === '')
|
|
325
|
+
return undefined;
|
|
326
|
+
const parts = text.split(/( +)/).filter((part) => part !== '');
|
|
327
|
+
if (parts.some((part) => part[0] !== ' ' && isLetterless(part)))
|
|
328
|
+
return undefined;
|
|
329
|
+
return withMeasureLanguage(shaped.lang, () => {
|
|
330
|
+
const side = rtl ? 'right' : 'left';
|
|
331
|
+
let x = 0;
|
|
332
|
+
let out = '';
|
|
333
|
+
for (const part of parts) {
|
|
334
|
+
out += `<span style="position:absolute;${side}:${x.toFixed(3)}px;top:0;">${esc(part)}</span>`;
|
|
335
|
+
x += part[0] !== ' ' && rtl ? rtlTextWidth(part, font) : measureTextWidth(part, font);
|
|
336
|
+
}
|
|
337
|
+
return Math.abs(x - width) <= 0.01 ? out : undefined;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
129
340
|
/**
|
|
130
341
|
* A word whose letters change style inside it (`VDTLineSegment.runs`: a
|
|
131
342
|
* bold letter, a coloured haraka) as one run of text with inline spans for
|
|
@@ -331,6 +542,216 @@ const HTML_ESCAPE = {
|
|
|
331
542
|
function esc(s) {
|
|
332
543
|
return s.replace(/[&<>"']/g, (c) => HTML_ESCAPE[c] ?? c);
|
|
333
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* `text` escaped, each run of spaces inside it in a box of its own, and
|
|
547
|
+
* each word with no letter of a text that holds a character past U+00FF
|
|
548
|
+
* (#770).
|
|
549
|
+
*
|
|
550
|
+
* The canvas, which measured the text, shapes a string word by word: a
|
|
551
|
+
* space is a word of its own and nothing kerns across it. The browser
|
|
552
|
+
* shapes the text of a box as one run and kerns a letter against the
|
|
553
|
+
* space beside it. Chrome 154, STIX Two Text at 20.83 px: `Section V` is
|
|
554
|
+
* 83.72 px on the canvas and 83.31 px in one box, `To AV Ty` 80.22 px and
|
|
555
|
+
* 79.39 px; in Hedvig Letters Serif at 40 px a space beside a capital is
|
|
556
|
+
* 1.3 to 3.1 px narrower in a box. In Arabic text the browser also takes
|
|
557
|
+
* another space: Aref Ruqaa's is 0.146 em between two words of a run and
|
|
558
|
+
* 0.333 em alone, 34 px over a title of three words at 92 px. An inline
|
|
559
|
+
* block is shaped apart from the text around it, so with its spaces in
|
|
560
|
+
* such boxes the text is as wide as the canvas made it (83.75 and
|
|
561
|
+
* 80.27 px: the boxes' edges fall on a 64th of a pixel), and it reads and
|
|
562
|
+
* copies as written.
|
|
563
|
+
*
|
|
564
|
+
* A word with no letter (`(1332–1406),`) has no script of its own. In a
|
|
565
|
+
* string with a character past U+00FF the canvas draws it by the face's
|
|
566
|
+
* default rules (`canvas-backend/contextText.ts`); in a box the browser
|
|
567
|
+
* gives it the script of the words beside it, across those inline blocks
|
|
568
|
+
* too: Amiri's brackets are 0.255 em in Latin text and 0.458 em by
|
|
569
|
+
* default, 7.5 px over that word at 18.75 px. In a box of its own it is
|
|
570
|
+
* drawn by the default rules.
|
|
571
|
+
*
|
|
572
|
+
* For the text of one box that holds several words: a reference's label,
|
|
573
|
+
* a line with no segments, a line or a run of a design text.
|
|
574
|
+
*/
|
|
575
|
+
function wordsHtml(text, tracked) {
|
|
576
|
+
if (tracked && ownShaping && tracked.tracking !== 0)
|
|
577
|
+
return trackedWordsHtml(text, tracked);
|
|
578
|
+
const escaped = esc(text);
|
|
579
|
+
if (text.indexOf(' ') < 0 || text.trim() === '')
|
|
580
|
+
return escaped;
|
|
581
|
+
if (!WIDE_TEXT_RE.test(text))
|
|
582
|
+
return escaped.replace(/ +/g, (spaces) => `${APART_OPEN}${spaces}</span>`);
|
|
583
|
+
return text.split(/( +)/).map((part) => (part === '' ? ''
|
|
584
|
+
: part[0] === ' ' || isLetterless(part) ? `${APART_OPEN}${esc(part)}</span>` : esc(part))).join('');
|
|
585
|
+
}
|
|
586
|
+
/** {@link TrackedText} of a text in `font` on a box tracked `tracking` px
|
|
587
|
+
* a letter, undefined for an untracked one. */
|
|
588
|
+
function trackedIn(font, tracking) {
|
|
589
|
+
return tracking ? { font, tracking } : undefined;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* A word of a tracked text, escaped, in the pieces the canvas paints it
|
|
593
|
+
* in (#663, #770): each ligature in a box of its own under no
|
|
594
|
+
* letter-spacing, so the browser sets it, with the tracking of each of
|
|
595
|
+
* its letters after it as a margin; the letters between them as they
|
|
596
|
+
* are, tracked by the box they stand in. The kerning the natural shaping
|
|
597
|
+
* has across a cut, which the browser does not apply between two boxes
|
|
598
|
+
* of other spacing, is a margin before the piece (`pipefitters` in Source
|
|
599
|
+
* Serif 4 at 40 px: 0.39 px between `pipe` and `fi`). A word with no
|
|
600
|
+
* ligature the letter-spacing dissolves, a word of a joining or a
|
|
601
|
+
* right-to-left script, and any word where nothing measures, is its text.
|
|
602
|
+
*
|
|
603
|
+
* A letter-spacing takes the contextual alternates of a face away too
|
|
604
|
+
* (see `canvas-backend/trackedText.ts`): a word whose pieces do not end
|
|
605
|
+
* where the word was measured to end, or that the letter-spacing reshapes
|
|
606
|
+
* with no ligature to keep, is set whole in a box at the letter-spacing
|
|
607
|
+
* that ends it there, as the canvas paints it. `TEA-PARTY` in Cormorant
|
|
608
|
+
* SC at 17.71 px, tracked 2.5 px: 2.66 px a letter, the 1.47 px its
|
|
609
|
+
* hyphen is narrower by under a letter-spacing shared among its nine
|
|
610
|
+
* letters.
|
|
611
|
+
*/
|
|
612
|
+
function trackedWordHtml(word, tracked) {
|
|
613
|
+
const shaped = shapedText;
|
|
614
|
+
if (!shaped || word.length < 2)
|
|
615
|
+
return esc(word);
|
|
616
|
+
const set = withMeasureLanguage(tracked.lang !== undefined ? shapingTagOf(tracked.lang) : shaped.lang, () => {
|
|
617
|
+
const cut = trackedLigaturePieces(word, tracked.font);
|
|
618
|
+
if (cut && piecesFit(cut, word, tracked.font))
|
|
619
|
+
return { pieces: cut };
|
|
620
|
+
return { pieces: cut, spacing: fittedLetterSpacing(word, tracked) };
|
|
621
|
+
});
|
|
622
|
+
if (set.spacing !== undefined)
|
|
623
|
+
return `<span style="letter-spacing:${set.spacing.toFixed(4)}px;">${esc(word)}</span>`;
|
|
624
|
+
const pieces = set.pieces;
|
|
625
|
+
if (!pieces)
|
|
626
|
+
return esc(word);
|
|
627
|
+
let out = '';
|
|
628
|
+
// Where the piece before ends in the natural shaping.
|
|
629
|
+
let end = 0;
|
|
630
|
+
for (const piece of pieces) {
|
|
631
|
+
const kern = piece.offset - end;
|
|
632
|
+
end = piece.offset + piece.width;
|
|
633
|
+
const lead = Math.abs(kern) >= 0.005 ? `margin-inline-start:${kern.toFixed(3)}px;` : '';
|
|
634
|
+
if (piece.ligature)
|
|
635
|
+
out += `<span style="letter-spacing:0;${lead}margin-inline-end:${(piece.letters * tracked.tracking).toFixed(3)}px;">${esc(piece.text)}</span>`;
|
|
636
|
+
else
|
|
637
|
+
out += lead ? `<span style="${lead}">${esc(piece.text)}</span>` : esc(piece.text);
|
|
638
|
+
}
|
|
639
|
+
return out;
|
|
640
|
+
}
|
|
641
|
+
/** The width of `text` in `font` under a letter-spacing, that spacing
|
|
642
|
+
* taken off, and what one px of letter-spacing adds to it; undefined
|
|
643
|
+
* where the measurer takes no letter-spacing. */
|
|
644
|
+
function spacedTextWidth(text, font) {
|
|
645
|
+
const one = measureSpacedWidth(text, font, 1);
|
|
646
|
+
const two = measureSpacedWidth(text, font, 2);
|
|
647
|
+
if (one === undefined || two === undefined || !(two - one > 0.5))
|
|
648
|
+
return undefined;
|
|
649
|
+
return { width: one - (two - one), step: two - one };
|
|
650
|
+
}
|
|
651
|
+
/** Least difference, px, that counts as another shaping
|
|
652
|
+
* (`measure/trackedLigatures.ts`). */
|
|
653
|
+
const RESHAPED_PX = 0.05;
|
|
654
|
+
/** Whether the pieces of `word`, each set as it is alone, end where the
|
|
655
|
+
* natural shaping ends the word (`TrackedWord.fits` of the canvas). */
|
|
656
|
+
function piecesFit(pieces, word, font) {
|
|
657
|
+
const last = pieces[pieces.length - 1];
|
|
658
|
+
const width = last.ligature ? last.width : spacedTextWidth(last.text, font)?.width;
|
|
659
|
+
return width !== undefined && Math.abs(last.offset + width - measureTextWidth(word, font)) <= RESHAPED_PX;
|
|
660
|
+
}
|
|
661
|
+
/** The letter-spacing, px, at which `word`, as the browser shapes it
|
|
662
|
+
* under one, is as wide as it was measured: its natural width and the
|
|
663
|
+
* tracking after each letter. Undefined for a word the letter-spacing
|
|
664
|
+
* does not reshape, a word of a joining or a right-to-left script, and
|
|
665
|
+
* where nothing measures a letter-spacing. Nor for a text with a CJK
|
|
666
|
+
* character, which is not measured so: the composer and a vertical line
|
|
667
|
+
* count cells, and two marks that meet are as wide set whole as the
|
|
668
|
+
* browser trims them, under no letter-spacing only (the lines of the
|
|
669
|
+
* Chinese guide set down the page stood 0.4 to 1.3 px off their cells). */
|
|
670
|
+
function fittedLetterSpacing(word, tracked) {
|
|
671
|
+
if (joiningScriptIn(word) || needsBidi(word) || hasCJK(word))
|
|
672
|
+
return undefined;
|
|
673
|
+
const letters = graphemeCount(word);
|
|
674
|
+
if (letters < 2)
|
|
675
|
+
return undefined;
|
|
676
|
+
const under = spacedTextWidth(word, tracked.font);
|
|
677
|
+
if (!under)
|
|
678
|
+
return undefined;
|
|
679
|
+
const natural = measureTextWidth(word, tracked.font);
|
|
680
|
+
if (Math.abs(under.width - natural) <= RESHAPED_PX)
|
|
681
|
+
return undefined;
|
|
682
|
+
const spacing = (natural + tracked.tracking * letters - under.width) / under.step;
|
|
683
|
+
// At no letter-spacing the browser would shape it naturally again.
|
|
684
|
+
return Math.abs(spacing) < 1e-6 ? undefined : spacing;
|
|
685
|
+
}
|
|
686
|
+
/** {@link wordsHtml} of a tracked text whose ligatures take the tracking
|
|
687
|
+
* of each letter: its spaces and its letterless words in boxes of their
|
|
688
|
+
* own as there, every other word through {@link trackedWordHtml}. */
|
|
689
|
+
function trackedWordsHtml(text, tracked) {
|
|
690
|
+
if (text.indexOf(' ') < 0 || text.trim() === '')
|
|
691
|
+
return trackedWordHtml(text, tracked);
|
|
692
|
+
const wide = WIDE_TEXT_RE.test(text);
|
|
693
|
+
return text.split(/( +)/).map((part) => (part === '' ? ''
|
|
694
|
+
: part[0] === ' ' || (wide && isLetterless(part)) ? `${APART_OPEN}${esc(part)}</span>` : trackedWordHtml(part, tracked))).join('');
|
|
695
|
+
}
|
|
696
|
+
/** A text set down a vertical line or with no box per space, escaped,
|
|
697
|
+
* each word of a tracked one through {@link trackedWordHtml}. */
|
|
698
|
+
function trackedTextHtml(text, tracked) {
|
|
699
|
+
if (!tracked || !ownShaping || tracked.tracking === 0)
|
|
700
|
+
return esc(text);
|
|
701
|
+
if (text.indexOf(' ') < 0)
|
|
702
|
+
return trackedWordHtml(text, tracked);
|
|
703
|
+
return text.split(/( +)/).map((part) => (part === '' ? '' : part[0] === ' ' ? part : trackedWordHtml(part, tracked))).join('');
|
|
704
|
+
}
|
|
705
|
+
/** A box shaped apart from the text around it. */
|
|
706
|
+
const APART_OPEN = '<span style="display:inline-block;">';
|
|
707
|
+
/**
|
|
708
|
+
* A line of several words as a box per word and per run of spaces, each
|
|
709
|
+
* at the place the canvas gives it (#770), or undefined where
|
|
710
|
+
* {@link wordsHtml} sets it.
|
|
711
|
+
*
|
|
712
|
+
* Boxes inside a line each round their width to a 64th of a pixel, and
|
|
713
|
+
* over a long line the rounding adds up: a colophon of 26 spaces set with
|
|
714
|
+
* {@link wordsHtml} ends 0.14 to 0.64 px past the canvas's width (four
|
|
715
|
+
* faces, three sizes), where in one box it stood 1.9 to 16 px short of
|
|
716
|
+
* it. With every word at its own place, as the words of a body line
|
|
717
|
+
* are, nothing adds up. The places are the canvas's: the advance of each
|
|
718
|
+
* word alone, in the language the text is shaped in ({@link shapedText}),
|
|
719
|
+
* `tracking` after each character and `wordSpacing` on each space. For a
|
|
720
|
+
* text that reads left to right, written straight into a box that keeps
|
|
721
|
+
* the line's font; the caller gives that box the line's width, since its
|
|
722
|
+
* words no longer make it. Not where the language is not known. In a
|
|
723
|
+
* face that draws by script, a word with no letter may take other forms
|
|
724
|
+
* measured alone than in a line that holds a character past U+00FF: such
|
|
725
|
+
* a line is set so only where its words alone are as wide together as
|
|
726
|
+
* the line measured whole, 0.01 px apart at most (the colophon `Higuchi
|
|
727
|
+
* Ichiyō, たけくらべ (Child’s Play, 1895–96), …` in Noto Sans JP at
|
|
728
|
+
* 11.46 px: 578.2357 px both ways; with {@link wordsHtml} its 16 spaces
|
|
729
|
+
* and its number, each in a box, end it at 578.53 px).
|
|
730
|
+
*/
|
|
731
|
+
function wordBoxesHtml(text, font, tracking, wordSpacing) {
|
|
732
|
+
const shaped = shapedText;
|
|
733
|
+
if (!shaped || text.indexOf(' ') < 0 || text.trim() === '' || needsBidi(text))
|
|
734
|
+
return undefined;
|
|
735
|
+
const parts = text.split(/( +)/).filter((part) => part !== '');
|
|
736
|
+
return withMeasureLanguage(shaped.lang, () => {
|
|
737
|
+
const byScript = WIDE_TEXT_RE.test(text) && parts.some((part) => part[0] !== ' ' && isLetterless(part)) && faceReadsScript(font);
|
|
738
|
+
let x = 0;
|
|
739
|
+
// The words alone, with no tracking and no wider space.
|
|
740
|
+
let alone = 0;
|
|
741
|
+
let out = '';
|
|
742
|
+
for (const part of parts) {
|
|
743
|
+
out += `<span style="position:absolute;left:${x.toFixed(3)}px;top:0;">${part[0] === ' ' ? esc(part) : trackedTextHtml(part, trackedIn(font, tracking))}</span>`;
|
|
744
|
+
const width = measureTextWidth(part, font);
|
|
745
|
+
alone += width;
|
|
746
|
+
x += width + (tracking === 0 ? 0 : tracking * graphemeCount(part)) + (part[0] === ' ' ? wordSpacing * part.length : 0);
|
|
747
|
+
}
|
|
748
|
+
if (byScript && Math.abs(alone - measureTextWidth(text, font)) > 0.01)
|
|
749
|
+
return undefined;
|
|
750
|
+
return out;
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
/** A character past Latin-1 (see `canvas-backend/contextText.ts`). */
|
|
754
|
+
const WIDE_TEXT_RE = /[^\u0000-\u00FF]/;
|
|
334
755
|
/** Element id for a resource embed's in-document anchor. `:ref` segments link
|
|
335
756
|
* to it with `#<id>` so references navigate in standalone HTML output. */
|
|
336
757
|
function resourceAnchorId(resourceId) {
|
|
@@ -405,6 +826,50 @@ function pickSegmentFont(seg, block) {
|
|
|
405
826
|
return block.italicFontString;
|
|
406
827
|
return block.fontString;
|
|
407
828
|
}
|
|
829
|
+
/**
|
|
830
|
+
* The language the browser shapes the text of the document being written
|
|
831
|
+
* in, as far as the writer knows it (#770): `lang` is the one its root
|
|
832
|
+
* declares (the document's own, or none: the empty tag), or undefined for
|
|
833
|
+
* the language of the page that writes it, which shows it too. Null where
|
|
834
|
+
* nobody can tell: a document in the host's language written in a worker,
|
|
835
|
+
* or where nothing measures. Set by {@link renderToHtmlIndexed} while it
|
|
836
|
+
* writes its pages, null again after them.
|
|
837
|
+
*/
|
|
838
|
+
let shapedText = null;
|
|
839
|
+
/** {@link shapedText} of a document whose root declares `declared`
|
|
840
|
+
* (undefined: it declares none). */
|
|
841
|
+
function shapedTextOf(declared) {
|
|
842
|
+
const page = typeof document !== 'undefined';
|
|
843
|
+
if (!page && typeof OffscreenCanvas === 'undefined')
|
|
844
|
+
return null;
|
|
845
|
+
if (declared !== undefined)
|
|
846
|
+
return { lang: declared };
|
|
847
|
+
return page ? { lang: undefined } : null;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* The segments a line set word by word is written from (#770): the line's
|
|
851
|
+
* own, the pieces of a word joined into one box where the word was
|
|
852
|
+
* measured whole and the hyphen of a line end in a box of its own where
|
|
853
|
+
* it was measured apart (`lineWordSegments`, the rule the canvas and the
|
|
854
|
+
* PDF apply). The browser sets each box's text as one run, so a joined
|
|
855
|
+
* word has the glyphs the layout measured: the `ffi` of `of|fice`, the
|
|
856
|
+
* dot of a Catalan `l·l`. Asked of the engine's measurer, in the language
|
|
857
|
+
* the text is shaped in ({@link shapedText}, a segment's own where it
|
|
858
|
+
* names one), at the line's `tracking`; `fontOf` gives a segment's font
|
|
859
|
+
* string. With them the order they advance in on a line that carries one
|
|
860
|
+
* (`VDTLine.order`): the pieces of a left-to-right word are joined there
|
|
861
|
+
* too, and the order is the one of the segments that result. The line's
|
|
862
|
+
* own segments and order where that language is not known.
|
|
863
|
+
*/
|
|
864
|
+
function wordSegmentsOf(line, tracking, fontOf) {
|
|
865
|
+
const shaped = shapedText;
|
|
866
|
+
if (!shaped)
|
|
867
|
+
return { segments: line.segments, order: line.order };
|
|
868
|
+
const set = lineWordSegmentsInOrder(line, false, (seg, text) => withMeasureLanguage(seg.lang !== undefined ? shapingTagOf(seg.lang) : shaped.lang, () => measureTextWidth(text, fontOf(seg)))
|
|
869
|
+
+ (tracking === 0 ? 0 : tracking * graphemeCount(text)));
|
|
870
|
+
// Nothing joined: the line's order as it is, whatever it counts.
|
|
871
|
+
return set.segments === line.segments ? { segments: line.segments, order: line.order } : set;
|
|
872
|
+
}
|
|
408
873
|
function pickSegmentColor(seg, block) {
|
|
409
874
|
if (seg.color)
|
|
410
875
|
return seg.color;
|
|
@@ -458,15 +923,17 @@ box = PLAIN_BOX) {
|
|
|
458
923
|
// A compressed CJK mark is painted before its box (`inkOffset`).
|
|
459
924
|
const left = seg.inkOffset !== undefined ? x + seg.inkOffset : x;
|
|
460
925
|
const scaleDecl = seg.inkScale !== undefined ? inkScaleDecl(seg.inkScale) : '';
|
|
461
|
-
|
|
926
|
+
// The cells of a line of the composer, each where it was measured.
|
|
927
|
+
const cellsDecl = cjk === 'line' && cellsOf(seg.text) ? CELLS_DECL : '';
|
|
928
|
+
const pos = `position:absolute;left:${left.toFixed(3)}px;top:${top};white-space:pre;${spacingDecl}${cjk === 'own' ? cjkTextDecl(tracking !== 0) : ''}${scaleDecl}${fontDecl ? '' : cellsDecl}${box.decl}`;
|
|
462
929
|
// Text set with emphasis dots is emphasis (#193); the dots are the
|
|
463
930
|
// line's marks.
|
|
464
|
-
const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : box.html ??
|
|
931
|
+
const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : box.html ?? wordsHtml(seg.text, box.tracked);
|
|
465
932
|
// A run in another face of a tracked line asks for its ligatures again
|
|
466
933
|
// after its `font` (#703), with the CJK features when it takes those.
|
|
467
|
-
const featuresDecl = !fontDecl ? '' : scaleDecl ? DASH_FEATURES_DECL
|
|
468
|
-
: cjk ? (tracking !== 0
|
|
469
|
-
: tracking !== 0 && !fontDecl.includes(TRACKED_LIGATURES_DECL) ?
|
|
934
|
+
const featuresDecl = !fontDecl ? '' : (scaleDecl ? DASH_FEATURES_DECL
|
|
935
|
+
: cjk ? cjkFeaturesDecl(tracking !== 0)
|
|
936
|
+
: tracking !== 0 && !fontDecl.includes(TRACKED_LIGATURES_DECL) ? ligaturesDecl() : '') + cellsDecl;
|
|
470
937
|
if (seg.refResourceId !== undefined) {
|
|
471
938
|
// Anchors carry an explicit color so the UA link blue never leaks in.
|
|
472
939
|
const inner = `<a href="${refAnchorHref(refKey(seg))}" style="text-decoration:none;${fontDecl}${featuresDecl}${fontDecl ? 'line-height:0;' : ''}color:${color};">${text}</a>`;
|
|
@@ -492,7 +959,7 @@ box = PLAIN_BOX) {
|
|
|
492
959
|
if (cjk || seg.cjkMarks)
|
|
493
960
|
return renderMarkedWordSegment(seg, x, top, fontDecl, colorDecl, color, cjk, box);
|
|
494
961
|
const pos = `position:absolute;left:${x.toFixed(3)}px;top:${top};white-space:pre;${box.decl}`;
|
|
495
|
-
const text = box.html ??
|
|
962
|
+
const text = box.html ?? wordsHtml(seg.text, box.tracked);
|
|
496
963
|
if (seg.refResourceId !== undefined) {
|
|
497
964
|
// Anchors carry an explicit color so the UA link blue never leaks in.
|
|
498
965
|
const inner = `<a href="${refAnchorHref(refKey(seg))}" style="text-decoration:none;${fontDecl}${fontDecl ? 'line-height:0;' : ''}color:${color};">${text}</a>`;
|
|
@@ -509,7 +976,7 @@ function renderMarkedWordSegment(seg, x, top, fontDecl, colorDecl, color, cjk, b
|
|
|
509
976
|
const pos = `position:absolute;left:${x.toFixed(3)}px;top:${top};white-space:pre;${cjk ? CJK_TEXT_DECL : ''}${box.decl}`;
|
|
510
977
|
// Text set with emphasis dots is emphasis (#193); the dots are the
|
|
511
978
|
// line's marks.
|
|
512
|
-
const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : box.html ??
|
|
979
|
+
const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : box.html ?? wordsHtml(seg.text, box.tracked);
|
|
513
980
|
const featuresDecl = fontDecl && cjk ? CJK_FEATURES_DECL : '';
|
|
514
981
|
if (seg.refResourceId !== undefined) {
|
|
515
982
|
// Anchors carry an explicit color so the UA link blue never leaks in.
|
|
@@ -729,7 +1196,7 @@ end = '') {
|
|
|
729
1196
|
? Math.max(0, (room - plainWidth) / 2)
|
|
730
1197
|
: 0);
|
|
731
1198
|
const plainDir = dirAttr(block.direction ?? rootDir, rootDir);
|
|
732
|
-
return `<span${plainDir} style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${
|
|
1199
|
+
return `<span${plainDir} style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${wordsHtml(line.text)}</span>` + lineEndHtml(end, plainLeft + plainWidth);
|
|
733
1200
|
}
|
|
734
1201
|
// Match canvas justification: stretch inter-word spaces to fill effective width.
|
|
735
1202
|
const lineIndent = span ? span.x - block.bbox.x : line.bbox.x - block.bbox.x;
|
|
@@ -759,17 +1226,19 @@ end = '') {
|
|
|
759
1226
|
const parts = [];
|
|
760
1227
|
const links = linkRuns();
|
|
761
1228
|
let x = leadingGap;
|
|
762
|
-
|
|
763
|
-
|
|
1229
|
+
// The pieces of a word the layout measured whole are one box (#770).
|
|
1230
|
+
const set = wordSegmentsOf(line, tracking, (seg) => pickSegmentFont(seg, block));
|
|
1231
|
+
const segs = set.segments;
|
|
1232
|
+
// Segments advance along the line in its order when it has one (a
|
|
764
1233
|
// line with right-to-left runs, or any line of a mirrored page): their
|
|
765
1234
|
// x are taken in that order; the markup stays in logical order, so the
|
|
766
1235
|
// text copies as written.
|
|
767
|
-
const at =
|
|
1236
|
+
const at = set.order ? orderedOffsets(segs, set.order, leadingGap, useJustify ? justifiedSpaceWidth : undefined) : undefined;
|
|
768
1237
|
const cjk = block.tocEntry !== undefined || hasCJK(line.text) || (line.tabbed === true && segs.some((sg) => sg.leader === 'text'));
|
|
769
1238
|
const paintText = (seg, at, inLink = false) => {
|
|
770
1239
|
const font = quoteFontString(pickSegmentFont(seg, block));
|
|
771
1240
|
const color = pickSegmentColor(seg, block);
|
|
772
|
-
const fontDecl = font !== quoteFontString(block.fontString) ? `font:${font};${tracking !== 0 ?
|
|
1241
|
+
const fontDecl = font !== quoteFontString(block.fontString) ? `font:${font};${tracking !== 0 ? ligaturesDecl() : ''}` : '';
|
|
773
1242
|
const colorDecl = color !== block.color ? `color:${color};` : '';
|
|
774
1243
|
const top = seg.baselineShift ? `${seg.baselineShift.toFixed(3)}px` : '0';
|
|
775
1244
|
return renderWordTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, color, cjk && hasCJK(seg.text), segmentBox(seg, rootDir, rootLang, tracking, font, color, (probe) => ({ font: quoteFontString(pickSegmentFont(probe, block)), color: pickSegmentColor(probe, block) })));
|
|
@@ -868,7 +1337,7 @@ function renderComposedSegments(line, block, targets, rootDir = 'ltr', end = '')
|
|
|
868
1337
|
: block.textAlign === 'center'
|
|
869
1338
|
? Math.max(0, (plainRoom - plainWidth) / 2)
|
|
870
1339
|
: 0;
|
|
871
|
-
return `<span style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${
|
|
1340
|
+
return `<span style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${wordsHtml(line.text)}</span>` + lineEndHtml(end, plainLeft + plainWidth);
|
|
872
1341
|
}
|
|
873
1342
|
// Match canvas justification: stretch inter-word spaces to fill effective width.
|
|
874
1343
|
const lineIndent = line.bbox.x - block.bbox.x;
|
|
@@ -913,7 +1382,12 @@ function renderComposedSegments(line, block, targets, rootDir = 'ltr', end = '')
|
|
|
913
1382
|
// The composer sets left-to-right text: in a right-to-left document
|
|
914
1383
|
// its boxes say so (#379).
|
|
915
1384
|
const dir = dirAttr(seg.rtl ? 'rtl' : 'ltr', rootDir);
|
|
916
|
-
|
|
1385
|
+
// A Western word of a tracked line in the canvas's pieces (#770): the
|
|
1386
|
+
// cells of the line hold no ligature and are asked nothing.
|
|
1387
|
+
const segTracking = lineTracking + (seg.tracking ?? 0);
|
|
1388
|
+
const tracked = ownShaping && segTracking !== 0 && !cellsOf(seg.text) && !joiningScriptIn(seg.text)
|
|
1389
|
+
? { font, tracking: segTracking, ...(seg.lang !== undefined ? { lang: seg.lang } : {}) } : undefined;
|
|
1390
|
+
return renderTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, color, lineTracking, segmentCjk(seg, lineDecl), dir || tracked ? { attrs: dir, decl: '', ...(tracked ? { tracked } : {}) } : PLAIN_BOX);
|
|
917
1391
|
};
|
|
918
1392
|
// The `<ruby>` open across the bases of one annotation (#428).
|
|
919
1393
|
const ruby = rubyRuns();
|
|
@@ -1012,10 +1486,23 @@ function leaderHtml(seg, x, baselineOffset, block) {
|
|
|
1012
1486
|
const font = quoteFontString(pickSegmentFont(seg, block));
|
|
1013
1487
|
const fontDecl = font !== quoteFontString(block.fontString) ? `font:${font};` : '';
|
|
1014
1488
|
const colorDecl = color !== block.color ? `color:${color};` : '';
|
|
1489
|
+
// The dots are measured and painted on the canvas left to right, as a
|
|
1490
|
+
// text of their own. In the box of a right-to-left block the browser
|
|
1491
|
+
// draws them in the forms of the script around them: a contents row of
|
|
1492
|
+
// 66 `. ` in Amiri at 27.08 px is 441.46 px in the layout and on the
|
|
1493
|
+
// canvas and 544.23 px in the box, 318.34 px against 277.98 px in Noto
|
|
1494
|
+
// Naskh Arabic. With its own direction on the positioned box (an inline
|
|
1495
|
+
// one inside it changes nothing) the leader is 441.47 px and 318.34 px.
|
|
1496
|
+
// In a document shaped in its own language (#770); one that keeps its
|
|
1497
|
+
// host's measured the stop as the page had it and is written as it was.
|
|
1498
|
+
// The direction is the box's `dir`, not a `direction` declaration, which
|
|
1499
|
+
// an EPUB's pages may not carry (EPUBCheck CSS-001); Chrome sets the
|
|
1500
|
+
// leader the same with either (882.92 px for 66 `. ` in Amiri).
|
|
1015
1501
|
const pos = `position:absolute;left:${x.toFixed(3)}px;top:0;white-space:pre;${noSelect}`;
|
|
1502
|
+
const dir = ownShaping ? ' dir="ltr"' : '';
|
|
1016
1503
|
if (fontDecl)
|
|
1017
|
-
return `<span ${hidden} style="${pos}"><span style="${fontDecl}line-height:0;${colorDecl}">${esc(seg.text)}</span></span>`;
|
|
1018
|
-
return `<span ${hidden} style="${pos}${colorDecl}">${esc(seg.text)}</span>`;
|
|
1504
|
+
return `<span ${hidden} style="${pos}"${dir}><span style="${fontDecl}line-height:0;${colorDecl}">${esc(seg.text)}</span></span>`;
|
|
1505
|
+
return `<span ${hidden} style="${pos}${colorDecl}"${dir}>${esc(seg.text)}</span>`;
|
|
1019
1506
|
}
|
|
1020
1507
|
/** Inline colour swatch (`:swatch{…}`): a square of `side` px on the line's
|
|
1021
1508
|
* baseline (`baselineOffset` from the line top), filled with `fill` when
|
|
@@ -1065,15 +1552,6 @@ rootDir = 'ltr') {
|
|
|
1065
1552
|
}
|
|
1066
1553
|
return parts.join('');
|
|
1067
1554
|
}
|
|
1068
|
-
/** Whether a marker's order differs when its leading European digits read
|
|
1069
|
-
* as Arabic numbers (`BidiOptions.arabicNumbers`, #665). */
|
|
1070
|
-
function arabicMarkNeeded(text) {
|
|
1071
|
-
if (!/[0-9]/.test(text))
|
|
1072
|
-
return false;
|
|
1073
|
-
const plain = markerRuns(text, 'rtl');
|
|
1074
|
-
const arabic = markerRuns(text, 'rtl-arabic');
|
|
1075
|
-
return plain.length !== arabic.length || plain.some((r, i) => r.text !== arabic[i].text);
|
|
1076
|
-
}
|
|
1077
1555
|
/**
|
|
1078
1556
|
* The box of a task item (`VDTBlock.bulletBox`, #765): an inline SVG the
|
|
1079
1557
|
* size of the box, from `bulletOffsetX` and centred on `bulletY`, in the
|
|
@@ -1173,7 +1651,7 @@ lead = '') {
|
|
|
1173
1651
|
// line's own (justification): measured into the segment widths, so the
|
|
1174
1652
|
// glyphs must spread the same way.
|
|
1175
1653
|
const tracking = (block.letterSpacing ?? 0) + (line.letterSpacing ?? 0);
|
|
1176
|
-
const trackingDecl = tracking !== 0 ? `letter-spacing:${tracking}px;${
|
|
1654
|
+
const trackingDecl = tracking !== 0 ? `letter-spacing:${tracking}px;${ligaturesDecl()}` : '';
|
|
1177
1655
|
return (`<div class="pt-line" data-block="${esc(block.id)}" style="` +
|
|
1178
1656
|
`position:absolute;` +
|
|
1179
1657
|
`left:${line.bbox.x}px;` +
|
|
@@ -1195,9 +1673,14 @@ lead = '') {
|
|
|
1195
1673
|
* with nothing between, so a copy and a screen reader read the first word
|
|
1196
1674
|
* whole.
|
|
1197
1675
|
*/
|
|
1198
|
-
function dropCapHtml(cap, line, lineFont) {
|
|
1676
|
+
function dropCapHtml(cap, line, lineFont, tracking = 0) {
|
|
1677
|
+
// The cap is measured and painted on the canvas with no tracking; set
|
|
1678
|
+
// inside the box of a tracked line it would inherit the line's
|
|
1679
|
+
// (#770: `Se` at 86.25 px on a line tracked 0.25 px, 95.39 px measured,
|
|
1680
|
+
// 95.91 px in the box).
|
|
1681
|
+
const untracked = tracking !== 0 ? 'letter-spacing:normal;' : '';
|
|
1199
1682
|
const run = (text, font, x, baselineY, width, cls) => `<span class="${cls}" style="position:absolute;left:${(x - line.bbox.x).toFixed(3)}px;top:${(baselineY - line.baseline).toFixed(3)}px;` +
|
|
1200
|
-
`width:${width.toFixed(3)}px;height:${line.bbox.height}px;font:${lineFont};color:${cap.color};white-space:pre;` +
|
|
1683
|
+
`width:${width.toFixed(3)}px;height:${line.bbox.height}px;font:${lineFont};color:${cap.color};white-space:pre;` + untracked +
|
|
1201
1684
|
(hasCJK(text) ? CJK_TEXT_DECL : '') +
|
|
1202
1685
|
`"><span style="font:${quoteFontString(font)};line-height:0;">${esc(text)}</span></span>`;
|
|
1203
1686
|
const hang = cap.hang ? run(cap.hang.text, cap.hang.fontString, cap.hang.x, cap.hang.baselineY, cap.hang.width, 'pt-dropcap-hang') : '';
|
|
@@ -1239,20 +1722,24 @@ function verticalTextHtml(text, v, orient, font, tracking = 0) {
|
|
|
1239
1722
|
return `<span style="text-combine-upright:all;">${esc(text)}</span>`;
|
|
1240
1723
|
if (orient === 'upright')
|
|
1241
1724
|
return `<span style="text-orientation:upright;">${esc(text)}</span>`;
|
|
1725
|
+
// A sideways Latin word of a tracked line in the canvas's pieces, its
|
|
1726
|
+
// ligatures with the tracking of each letter (#770).
|
|
1727
|
+
const tracked = font !== undefined ? trackedIn(font, tracking) : undefined;
|
|
1242
1728
|
if (orient === 'sideways')
|
|
1243
|
-
return `<span style="text-orientation:sideways;">${
|
|
1729
|
+
return `<span style="text-orientation:sideways;">${trackedTextHtml(text, tracked)}</span>`;
|
|
1244
1730
|
const digits = v.uprightDigits > 0 && /[0-9]/.test(text);
|
|
1245
1731
|
const japanese = v.region === 'japan' && JAPANESE_VERTICAL_RE.test(text);
|
|
1246
1732
|
if (!digits && !japanese && !holdsTurnedMark(text))
|
|
1247
|
-
return
|
|
1733
|
+
return trackedTextHtml(text, tracked);
|
|
1248
1734
|
const runs = verticalRuns(graphemesOf(text), v.region, v.uprightDigits);
|
|
1249
1735
|
if (!runs.some((r) => r.glyph.orient === 'tcy' || r.glyph.orient === 'rotate' || r.glyph.paintAs !== undefined))
|
|
1250
|
-
return
|
|
1736
|
+
return trackedTextHtml(text, tracked);
|
|
1251
1737
|
const advances = font !== undefined ? v.dashes?.[fontFamilyOf(font)] : undefined;
|
|
1252
1738
|
// A number in one cell combined upright; a turned mark in its cell.
|
|
1253
1739
|
return runs.map((r) => (r.glyph.orient === 'tcy'
|
|
1254
1740
|
? `<span style="text-combine-upright:all;">${esc(r.text)}</span>`
|
|
1255
|
-
: r.glyph.orient === 'rotate' ? turnedCellHtml(r, advances?.[r.text], tracking)
|
|
1741
|
+
: r.glyph.orient === 'rotate' ? turnedCellHtml(r, advances?.[r.text], tracking)
|
|
1742
|
+
: r.glyph.paintAs !== undefined ? esc(r.glyph.paintAs) : trackedTextHtml(r.text, tracked))).join('');
|
|
1256
1743
|
}
|
|
1257
1744
|
/**
|
|
1258
1745
|
* A mark a vertical line turns in a cell of its own (`rotate`: a dash, an
|
|
@@ -1322,6 +1809,44 @@ function verticalAnnotationRuns(runs, x, color, v, axisOf, hidden = true) {
|
|
|
1322
1809
|
return verticalSpanAt(x + run.dx, axisOf(run.fontString, run.dy), extractFontSizePx(run.fontString), verticalTextHtml(run.text, v, run.upright ? 'upright' : undefined, run.fontString), decl, hidden);
|
|
1323
1810
|
}).join('');
|
|
1324
1811
|
}
|
|
1812
|
+
/**
|
|
1813
|
+
* The advance of `text` down a vertical line, as the layout measures it
|
|
1814
|
+
* (`verticalTextWidth`): its sideways runs at the width the measurer
|
|
1815
|
+
* gives them, every cell at its length in ems, `tracking` px after each
|
|
1816
|
+
* character and each cell.
|
|
1817
|
+
*/
|
|
1818
|
+
function verticalAdvance(text, font, v, tracking) {
|
|
1819
|
+
const em = extractFontSizePx(font);
|
|
1820
|
+
let width = 0;
|
|
1821
|
+
for (const run of verticalRuns(graphemesOf(text), v.region, v.uprightDigits)) {
|
|
1822
|
+
width += run.cell === undefined
|
|
1823
|
+
? measureTextWidth(run.text, font) + (tracking === 0 ? 0 : tracking * graphemeCount(run.text))
|
|
1824
|
+
: em * run.cell + tracking;
|
|
1825
|
+
}
|
|
1826
|
+
return width;
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* The segments a line down a vertical page is written from (#770): the
|
|
1830
|
+
* line's own, the pieces of a word set sideways joined into one box where
|
|
1831
|
+
* the word was measured whole (`lineWordSegments`, the rule of a
|
|
1832
|
+
* horizontal line, see {@link wordSegmentsOf}).
|
|
1833
|
+
*
|
|
1834
|
+
* A Latin word runs sideways down the column, shaped as on a horizontal
|
|
1835
|
+
* line, and a piece of it is as wide as the word grew by it. Chrome 154,
|
|
1836
|
+
* Noto Sans TC at 15.63 px under `zh-Hant`: the layout holds `BY-` 22.51
|
|
1837
|
+
* and `SA` 18.46 px, `BY-SA` being 40.97 px, where `SA` alone is 18.81 px
|
|
1838
|
+
* (the face draws the hyphen 0.34 px narrower before the capital). In a
|
|
1839
|
+
* box of its own `SA` ended 0.35 px past its place; the word in one box
|
|
1840
|
+
* is 40.98 px. A line of the CJK composer, which measures a Western run
|
|
1841
|
+
* whole, and a line that carries an order are written as they are; so is
|
|
1842
|
+
* every line where the language is not known.
|
|
1843
|
+
*/
|
|
1844
|
+
function verticalWordSegments(line, block, v, tracking) {
|
|
1845
|
+
const shaped = shapedText;
|
|
1846
|
+
if (!shaped || !line.segments || line.cjkComposed || line.order !== undefined)
|
|
1847
|
+
return line.segments;
|
|
1848
|
+
return lineWordSegmentsInOrder(line, false, (seg, text) => withMeasureLanguage(seg.lang !== undefined ? shapingTagOf(seg.lang) : shaped.lang, () => verticalAdvance(text, pickSegmentFont(seg, block), v, tracking))).segments;
|
|
1849
|
+
}
|
|
1325
1850
|
/** A body line of a vertical page: its text segments down an upright box,
|
|
1326
1851
|
* its formulas, swatches and chips sideways where the canvas paints them
|
|
1327
1852
|
* (see the section comment). */
|
|
@@ -1335,8 +1860,10 @@ function renderVerticalLine(line, block, v, targets, next) {
|
|
|
1335
1860
|
const axisOf = (fontString, shift = 0) => baseline + shift - centralOf(v, fontString) * extractFontSizePx(fontString);
|
|
1336
1861
|
const inner = [];
|
|
1337
1862
|
const sideways = [];
|
|
1338
|
-
|
|
1339
|
-
|
|
1863
|
+
// The pieces of a sideways word in one box (see `verticalWordSegments`).
|
|
1864
|
+
const own = verticalWordSegments(line, block, v, lineTracking);
|
|
1865
|
+
const segs = own && own.length > 0
|
|
1866
|
+
? own
|
|
1340
1867
|
: [{ kind: 'text', text: line.text, width: line.bbox.width }];
|
|
1341
1868
|
let wordWidth = 0;
|
|
1342
1869
|
let spaceCount = 0;
|
|
@@ -1407,7 +1934,7 @@ function renderVerticalLine(line, block, v, targets, next) {
|
|
|
1407
1934
|
const stretched = seg.inkScale !== undefined;
|
|
1408
1935
|
// A run in another face of a tracked line asks for its ligatures
|
|
1409
1936
|
// again after its `font`, which resets them (#703).
|
|
1410
|
-
const decl = (font !== blockFont ? `font:${font};${lineTracking !== 0 ?
|
|
1937
|
+
const decl = (font !== blockFont ? `font:${font};${lineTracking !== 0 ? ligaturesDecl() : ''}` : '')
|
|
1411
1938
|
+ (color !== block.color ? `color:${color};` : '')
|
|
1412
1939
|
+ (seg.tracking !== undefined ? `letter-spacing:${lineTracking + seg.tracking}px;` : '')
|
|
1413
1940
|
+ (stretched ? `transform:scaleY(${seg.inkScale.toFixed(4)});transform-origin:0 0;${DASH_FEATURES_DECL}` : '');
|
|
@@ -1452,7 +1979,7 @@ function renderVerticalLine(line, block, v, targets, next) {
|
|
|
1452
1979
|
+ (block.strikethroughText ? 'text-decoration:line-through;' : '')
|
|
1453
1980
|
// A tracked line keeps the ligatures of its sideways Latin runs, as
|
|
1454
1981
|
// they were measured (#703).
|
|
1455
|
-
+ (lineTracking !== 0 ? `letter-spacing:${lineTracking}px;${
|
|
1982
|
+
+ (lineTracking !== 0 ? `letter-spacing:${lineTracking}px;${ligaturesDecl()}` : '')
|
|
1456
1983
|
+ (lineCjkDecl(line) || hasCJK(line.text) ? cjkTextDecl(lineTracking !== 0) : '');
|
|
1457
1984
|
return (`<div class="pt-line" data-block="${esc(block.id)}" style="position:absolute;left:${line.bbox.x}px;top:${line.bbox.y}px;width:${width}px;height:${line.bbox.height}px;">` +
|
|
1458
1985
|
uprightInner(width, line.bbox.height, decl, inner.join('')) +
|
|
@@ -1515,7 +2042,7 @@ function verticalDesignLines(block, v, originX, originY) {
|
|
|
1515
2042
|
// A dash of a 破折号 (`inkScale`, #652): turned with the flow and
|
|
1516
2043
|
// stretched down the column, as a body segment's is.
|
|
1517
2044
|
const stretched = run.inkScale !== undefined;
|
|
1518
|
-
const decl = (runFont !== font ? `font:${runFont};${tracked ?
|
|
2045
|
+
const decl = (runFont !== font ? `font:${runFont};${tracked ? ligaturesDecl() : ''}` : '')
|
|
1519
2046
|
+ (stretched ? `transform:scaleY(${run.inkScale.toFixed(4)});transform-origin:0 0;${DASH_FEATURES_DECL}` : '');
|
|
1520
2047
|
// A run whose width is its box (a CJK mark that gave up blank, #637)
|
|
1521
2048
|
// sets its glyphs `inkOffset` into it.
|
|
@@ -1523,7 +2050,7 @@ function verticalDesignLines(block, v, originX, originY) {
|
|
|
1523
2050
|
x += run.width;
|
|
1524
2051
|
}
|
|
1525
2052
|
const width = Math.max(line.width, x);
|
|
1526
|
-
const decl = `font:${font};color:${block.color};${hasCJK(line.text) ? cjkTextDecl(tracked) : tracked ?
|
|
2053
|
+
const decl = `font:${font};color:${block.color};${hasCJK(line.text) ? cjkTextDecl(tracked) : tracked ? ligaturesDecl() : ''}`
|
|
1527
2054
|
+ (line.wordSpacingPx ? `word-spacing:${line.wordSpacingPx.toFixed(3)}px;` : '');
|
|
1528
2055
|
parts.push(uprightBox(originX + line.xOffset, originY + top, width, h, decl, inner.join('')));
|
|
1529
2056
|
}
|
|
@@ -1556,13 +2083,15 @@ end = '') {
|
|
|
1556
2083
|
const parts = [];
|
|
1557
2084
|
const lineDecl = lineCjkDecl(line);
|
|
1558
2085
|
if (line.segments && line.segments.length > 0) {
|
|
1559
|
-
const segs = line.segments;
|
|
1560
2086
|
const composed = line.cjkComposed === true;
|
|
2087
|
+
const tracking = line.letterSpacing ?? 0;
|
|
2088
|
+
// The pieces of a word the layout measured whole are one box (#770).
|
|
2089
|
+
const set = composed ? { segments: line.segments, order: line.order } : wordSegmentsOf(line, tracking, (seg) => pickResourceFont(seg, fonts));
|
|
2090
|
+
const segs = set.segments;
|
|
1561
2091
|
const cjk = !composed && hasCJK(line.text);
|
|
1562
2092
|
// A caption or cell line with right-to-left runs, or on a mirrored
|
|
1563
2093
|
// page, advances through its segments in `order` (#379).
|
|
1564
|
-
const at =
|
|
1565
|
-
const tracking = line.letterSpacing ?? 0;
|
|
2094
|
+
const at = set.order && set.order.length === segs.length ? orderedOffsets(segs, set.order, 0, undefined) : undefined;
|
|
1566
2095
|
const segColorOf = (seg) => (seg.refResourceId !== undefined
|
|
1567
2096
|
? linkColor
|
|
1568
2097
|
: seg.captionLabel
|
|
@@ -1571,7 +2100,7 @@ end = '') {
|
|
|
1571
2100
|
const paintText = (seg, at, inLink = false) => {
|
|
1572
2101
|
const font = quoteFontString(pickResourceFont(seg, fonts));
|
|
1573
2102
|
const segColor = segColorOf(seg);
|
|
1574
|
-
const fontDecl = font !== baseFont ? `font:${font};${tracking !== 0 ?
|
|
2103
|
+
const fontDecl = font !== baseFont ? `font:${font};${tracking !== 0 ? ligaturesDecl() : ''}` : '';
|
|
1575
2104
|
const colorDecl = segColor !== color ? `color:${segColor};` : '';
|
|
1576
2105
|
const top = seg.baselineShift ? `${seg.baselineShift.toFixed(3)}px` : '0';
|
|
1577
2106
|
// Its direction, untracked joining letters and styled runs (#379).
|
|
@@ -1644,7 +2173,7 @@ end = '') {
|
|
|
1644
2173
|
parts.push(lineMarksHtml(line, color));
|
|
1645
2174
|
}
|
|
1646
2175
|
else {
|
|
1647
|
-
parts.push(`<span style="position:absolute;left:0;top:0;white-space:pre;">${
|
|
2176
|
+
parts.push(`<span style="position:absolute;left:0;top:0;white-space:pre;">${wordsHtml(line.text)}</span>`);
|
|
1648
2177
|
parts.push(lineEndHtml(end, line.bbox.width));
|
|
1649
2178
|
}
|
|
1650
2179
|
return (`<div class="pt-line" style="` +
|
|
@@ -1656,7 +2185,7 @@ end = '') {
|
|
|
1656
2185
|
`color:${color};` +
|
|
1657
2186
|
// A tracked line (a table header set with `headerLetterSpacing`) was
|
|
1658
2187
|
// measured with the tracking in its widths.
|
|
1659
|
-
(line.letterSpacing ? `letter-spacing:${line.letterSpacing}px;${
|
|
2188
|
+
(line.letterSpacing ? `letter-spacing:${line.letterSpacing}px;${ligaturesDecl()}` : '') +
|
|
1660
2189
|
(lineDecl ? cjkTextDecl(!!line.letterSpacing) : '') +
|
|
1661
2190
|
`">${parts.join('')}</div>`);
|
|
1662
2191
|
}
|
|
@@ -1974,6 +2503,15 @@ function renderBoxAt(bbox, style) {
|
|
|
1974
2503
|
boxStyleDecls(style) +
|
|
1975
2504
|
`"></div>`);
|
|
1976
2505
|
}
|
|
2506
|
+
/** Whether a line of design text carries one run that is its whole text
|
|
2507
|
+
* in the block's own font, set at its advance with nothing of its own (no
|
|
2508
|
+
* shift, no box, no direction) and no CJK character, which the composer
|
|
2509
|
+
* measures alone: a line without runs by another name. */
|
|
2510
|
+
function isPlainRun(line, block) {
|
|
2511
|
+
const run = line.runs?.length === 1 ? line.runs[0] : undefined;
|
|
2512
|
+
return run !== undefined && line.order === undefined && run.text === line.text && run.fontString === block.fontString && !hasCJK(run.text)
|
|
2513
|
+
&& !run.baselineShift && !run.stacked && !run.tcy && !run.orientation && !run.rtl && run.inkOffset === undefined && run.inkScale === undefined;
|
|
2514
|
+
}
|
|
1977
2515
|
function renderDesignTextBlock(block, options) {
|
|
1978
2516
|
const parts = [];
|
|
1979
2517
|
if (block.box)
|
|
@@ -1998,34 +2536,67 @@ function renderDesignTextBlock(block, options) {
|
|
|
1998
2536
|
// takes no room and the second in one as wide as the pair (EF-80).
|
|
1999
2537
|
// Each run was measured whole, and so was a line without runs: the
|
|
2000
2538
|
// browser's punctuation trimming is off on those that hold CJK text.
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
? `
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2539
|
+
// A line of several words in the block's font: a box per word (see
|
|
2540
|
+
// `wordBoxesHtml`), in a line box as wide as the line. So is a line
|
|
2541
|
+
// of one run that is its text in the block's font (the CJK composer
|
|
2542
|
+
// gives every line of a design text its runs): written through
|
|
2543
|
+
// `wordsHtml`, the colophon `Set in Noto Serif TC, … (SIL OFL).` in
|
|
2544
|
+
// Source Serif 4 at 13.54 px ended 0.31 px past its 520.63 px, a 64th
|
|
2545
|
+
// of a pixel and more at each of its 16 spaces.
|
|
2546
|
+
const words = line.runs && !isPlainRun(line, block) ? undefined : wordBoxesHtml(line.text, block.fontString, block.letterSpacingPx ?? 0, line.wordSpacingPx ?? 0);
|
|
2547
|
+
const inner = words !== undefined ? words
|
|
2548
|
+
: line.runs
|
|
2549
|
+
? line.runs.map((run, i) => {
|
|
2550
|
+
const runFont = quoteFontString(run.fontString);
|
|
2551
|
+
const cjkDecl = hasCJK(run.text) ? cjkTextDecl(tracked) : '';
|
|
2552
|
+
// The `font` shorthand resets `line-height` to `normal`: a run in
|
|
2553
|
+
// another face would grow the line box and push the line's
|
|
2554
|
+
// baseline down. It keeps the line's own.
|
|
2555
|
+
const fontDecl = runFont !== font ? `font:${runFont};line-height:1;${tracked && !cjkDecl ? ligaturesDecl() : ''}` : '';
|
|
2556
|
+
// A run whose width is its box comes from the CJK composer,
|
|
2557
|
+
// which measured its characters one by one: its cells are set
|
|
2558
|
+
// with no kerning between them (see {@link CELLS_DECL}).
|
|
2559
|
+
const cellsDecl = run.inkOffset !== undefined ? (cellsOf(run.text) ? CELLS_DECL : '')
|
|
2560
|
+
: designCellsDecl(run.text, run.fontString, run.width, block.letterSpacingPx ?? 0);
|
|
2561
|
+
const stackDecl = run.stacked
|
|
2562
|
+
? 'display:inline-block;width:0;'
|
|
2563
|
+
: line.runs[i - 1]?.stacked ? `display:inline-block;min-width:${run.width.toFixed(3)}px;` : '';
|
|
2564
|
+
const shiftDecl = run.baselineShift ? `position:relative;top:${run.baselineShift.toFixed(3)}px;` : '';
|
|
2565
|
+
// A run whose width is its box, not its glyphs' advance (a CJK
|
|
2566
|
+
// mark that gave up blank, a Han–Latin space, #637): a box that
|
|
2567
|
+
// wide, its glyphs set `inkOffset` into it.
|
|
2568
|
+
const boxDecl = run.inkOffset !== undefined && !run.stacked
|
|
2569
|
+
? `display:inline-block;width:${run.width.toFixed(3)}px;${run.inkOffset !== 0 ? `text-indent:${run.inkOffset.toFixed(3)}px;` : ''}`
|
|
2570
|
+
: '';
|
|
2571
|
+
// A dash of a 破折号 (`inkScale`, #652): a box of its own inside
|
|
2572
|
+
// the run's, stretched from where the run's indent puts its
|
|
2573
|
+
// glyph, as a body segment's is.
|
|
2574
|
+
const text = run.inkScale !== undefined
|
|
2575
|
+
? `<span style="display:inline-block;text-indent:0;${inkScaleDecl(run.inkScale)}">${esc(run.text)}</span>`
|
|
2576
|
+
// A run whose width is its box holds no word space: its box
|
|
2577
|
+
// indents its text, and so would a box inside it.
|
|
2578
|
+
: run.inkOffset !== undefined ? esc(run.text)
|
|
2579
|
+
: wordsHtml(run.text, trackedIn(run.fontString, block.letterSpacingPx));
|
|
2580
|
+
return fontDecl || cjkDecl || stackDecl || shiftDecl || boxDecl ? `<span style="${fontDecl}${cjkDecl}${cellsDecl}${stackDecl}${shiftDecl}${boxDecl}">${text}</span>` : text;
|
|
2581
|
+
}).join('')
|
|
2582
|
+
: letterlessRunHtml(line.text, block.fontString, false, line.width, block.letterSpacingPx ?? 0) ?? wordsHtml(line.text, trackedIn(block.fontString, block.letterSpacingPx));
|
|
2583
|
+
// A line that carries directions (an order, a right-to-left run): a box
|
|
2584
|
+
// per run (see `directedDesignRunsHtml`), unless a run is set otherwise
|
|
2585
|
+
// than as its text at its advance (a stacked script, a box of the CJK
|
|
2586
|
+
// composer).
|
|
2587
|
+
const apartable = line.runs !== undefined
|
|
2588
|
+
&& line.runs.every((run) => !run.stacked && run.inkOffset === undefined && run.inkScale === undefined && !run.tcy && !run.orientation);
|
|
2589
|
+
// So is a line of several runs with no CJK text, in a document shaped
|
|
2590
|
+
// in its own language: the canvas starts each run where the widths
|
|
2591
|
+
// before it end, and inline every run, every space in its box and
|
|
2592
|
+
// every span is as wide as a whole number of 64ths of a pixel. The
|
|
2593
|
+
// byline `John Jumper¹,⁴, Richard Evans¹,⁴, …` in Noto Sans at 15.42
|
|
2594
|
+
// px, fifteen runs and fourteen spaces, had its last run 0.32 px past
|
|
2595
|
+
// its place, and three more lines of it 0.30 to 0.46 px; a box per
|
|
2596
|
+
// run, 0.02 px at most.
|
|
2597
|
+
const apart = ownShaping && apartable && words === undefined && line.runs.length > 1 && !line.runs.some((run) => hasCJK(run.text));
|
|
2598
|
+
const directed = apart || (apartable && (line.order !== undefined || line.runs.some((run) => run.rtl)));
|
|
2599
|
+
const lineDecl = !line.runs && hasCJK(line.text) ? cjkTextDecl(tracked) + designCellsDecl(line.text, block.fontString, line.width, block.letterSpacingPx ?? 0) : '';
|
|
2029
2600
|
// A justified line: its word spaces widened as the canvas and the PDF
|
|
2030
2601
|
// advance its runs (EF-109).
|
|
2031
2602
|
const wordSpacingDecl = line.wordSpacingPx ? `word-spacing:${line.wordSpacingPx.toFixed(3)}px;` : '';
|
|
@@ -2037,13 +2608,14 @@ function renderDesignTextBlock(block, options) {
|
|
|
2037
2608
|
`position:absolute;` +
|
|
2038
2609
|
`left:${line.xOffset.toFixed(3)}px;` +
|
|
2039
2610
|
`top:${top.toFixed(3)}px;` +
|
|
2040
|
-
|
|
2611
|
+
(directed ? 'line-height:1;' : 'line-height:1;white-space:pre;') +
|
|
2612
|
+
(words !== undefined ? `width:${line.width.toFixed(3)}px;` : '') +
|
|
2041
2613
|
wordSpacingDecl +
|
|
2042
2614
|
lineDecl +
|
|
2043
|
-
`">${inner}</span>`);
|
|
2615
|
+
`">${directed ? directedDesignRunsHtml(line, block, font, tracked) : inner}</span>`);
|
|
2044
2616
|
}
|
|
2045
2617
|
const clipDecl = block.clip ? 'overflow:hidden;' : '';
|
|
2046
|
-
const trackingDecl = block.letterSpacingPx ? `letter-spacing:${block.letterSpacingPx}px;${
|
|
2618
|
+
const trackingDecl = block.letterSpacingPx ? `letter-spacing:${block.letterSpacingPx}px;${ligaturesDecl()}` : '';
|
|
2047
2619
|
// An outline over the glyphs (centred on their edges, as on canvas and in
|
|
2048
2620
|
// the PDF); hollow letters leave the fill transparent.
|
|
2049
2621
|
const strokeDecl = block.stroke && block.stroke.widthPx > 0
|
|
@@ -2180,7 +2752,7 @@ function renderBlockInner(block, options) {
|
|
|
2180
2752
|
parts.push(v ? renderVerticalBullet(block, v) : renderBullet(block, options.dir));
|
|
2181
2753
|
block.lines.forEach((line, i) => {
|
|
2182
2754
|
const next = block.lines[i + 1];
|
|
2183
|
-
const lead = i === 0 && block.dropCap && !v ? dropCapHtml(block.dropCap, line, quoteFontString(block.fontString)) : '';
|
|
2755
|
+
const lead = i === 0 && block.dropCap && !v ? dropCapHtml(block.dropCap, line, quoteFontString(block.fontString), (block.letterSpacing ?? 0) + (line.letterSpacing ?? 0)) : '';
|
|
2184
2756
|
parts.push(v ? renderVerticalLine(line, block, v, options.linkTargets, next) : renderLine(line, block, options.linkTargets, options.dir, options.rootLang, next, lead));
|
|
2185
2757
|
});
|
|
2186
2758
|
return parts.join('');
|
|
@@ -2467,6 +3039,12 @@ export function renderToHtmlIndexed(doc, options = {}) {
|
|
|
2467
3039
|
? Math.max(0, doc.trimOffset - dimensionToPx(doc.config.page.cutLines.bleed, doc.config.page.dpi))
|
|
2468
3040
|
: 0;
|
|
2469
3041
|
const svgUrl = svgFontInliner(doc, options);
|
|
3042
|
+
// The language the pages' text is shaped in, for the words set whole
|
|
3043
|
+
// (#770): the declared one, else the writing page's.
|
|
3044
|
+
shapedText = shapedTextOf(docLang);
|
|
3045
|
+
// A document shaped in its own language sets a tracked ligature with
|
|
3046
|
+
// the tracking of each of its letters, as the layout counted it (#770).
|
|
3047
|
+
ownShaping = shapedIn !== undefined && shapedText !== null;
|
|
2470
3048
|
for (const p of doc.pages) {
|
|
2471
3049
|
const pageOptions = onWarning
|
|
2472
3050
|
? {
|
|
@@ -2495,6 +3073,8 @@ export function renderToHtmlIndexed(doc, options = {}) {
|
|
|
2495
3073
|
...(detail.comicHtml ? { comicHtml: detail.comicHtml } : {}),
|
|
2496
3074
|
});
|
|
2497
3075
|
}
|
|
3076
|
+
shapedText = null;
|
|
3077
|
+
ownShaping = false;
|
|
2498
3078
|
// The host's inherited text properties are reset first (EF-96). A
|
|
2499
3079
|
// Chinese, Japanese or Korean document declares its language, so the
|
|
2500
3080
|
// browser picks the region's glyph forms, and so does a right-to-left one,
|