NodeMixin.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2022-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Implements a mixin for node-based adaptors that overrides
  19. * the methods that obtain DOM node sizes, when those aren't
  20. * available from the DOM itself.
  21. *
  22. * @author dpvc@mathjax.org (Davide Cervone)
  23. */
  24. import {DOMAdaptor} from '../core/DOMAdaptor.js';
  25. import {userOptions, defaultOptions, OptionList} from '../util/Options.js';
  26. /**
  27. * A constructor for a given class
  28. *
  29. * @template T The class to construct
  30. */
  31. export type Constructor<T> = (new(...args: any[]) => T);
  32. /**
  33. * The type of an Adaptor class
  34. */
  35. export type AdaptorConstructor<N, T, D> = Constructor<DOMAdaptor<N, T, D>>;
  36. /**
  37. * The options to the NodeMixin
  38. */
  39. export const NodeMixinOptions: OptionList = {
  40. badCSS: true, // getComputedStyles() is not implemented in the DOM
  41. badSizes: true, // element sizes (e.g., ClientWidth, etc.) are not implemented in the DOM
  42. };
  43. /**
  44. * @template N The HTMLElement node class
  45. * @template T The Text node class
  46. * @template D The Document class
  47. */
  48. export function NodeMixin<N, T, D, A extends AdaptorConstructor<N, T, D>>(
  49. Base: A,
  50. options: typeof NodeMixinOptions = {}
  51. ): A {
  52. options = userOptions(defaultOptions({}, NodeMixinOptions), options);
  53. return class NodeAdaptor extends Base {
  54. /**
  55. * The default options
  56. */
  57. public static OPTIONS: OptionList = {
  58. ...(options.badCSS ? {
  59. fontSize: 16, // We can't compute the font size, so always use this
  60. fontFamily: 'Times', // We can't compute the font family, so always use this
  61. } : {}),
  62. ...(options.badSizes ? {
  63. cjkCharWidth: 1, // Width (in em units) of full width characters
  64. unknownCharWidth: .6, // Width (in em units) of unknown (non-full-width) characters
  65. unknownCharHeight: .8, // Height (in em units) of unknown characters
  66. } : {})
  67. };
  68. /**
  69. * Pattern to identify CJK (i.e., full-width) characters
  70. */
  71. public static cjkPattern = new RegExp([
  72. '[',
  73. '\u1100-\u115F', // Hangul Jamo
  74. '\u2329\u232A', // LEFT-POINTING ANGLE BRACKET, RIGHT-POINTING ANGLE BRACKET
  75. '\u2E80-\u303E', // CJK Radicals Supplement ... CJK Symbols and Punctuation
  76. '\u3040-\u3247', // Hiragana ... Enclosed CJK Letters and Months
  77. '\u3250-\u4DBF', // Enclosed CJK Letters and Months ... CJK Unified Ideographs Extension A
  78. '\u4E00-\uA4C6', // CJK Unified Ideographs ... Yi Radicals
  79. '\uA960-\uA97C', // Hangul Jamo Extended-A
  80. '\uAC00-\uD7A3', // Hangul Syllables
  81. '\uF900-\uFAFF', // CJK Compatibility Ideographs
  82. '\uFE10-\uFE19', // Vertical Forms
  83. '\uFE30-\uFE6B', // CJK Compatibility Forms ... Small Form Variants
  84. '\uFF01-\uFF60\uFFE0-\uFFE6', // Halfwidth and Fullwidth Forms
  85. '\u{1B000}-\u{1B001}', // Kana Supplement
  86. '\u{1F200}-\u{1F251}', // Enclosed Ideographic Supplement
  87. '\u{20000}-\u{3FFFD}', // CJK Unified Ideographs Extension B ... Tertiary Ideographic Plane
  88. ']'
  89. ].join(''), 'gu');
  90. /**
  91. * The options for the instance
  92. */
  93. public options: OptionList;
  94. /**
  95. * @param {any} window The window to work with
  96. * @param {OptionList} options The options for the adaptor
  97. * @constructor
  98. */
  99. constructor(...args: any[]) {
  100. super(args[0]);
  101. let CLASS = this.constructor as typeof NodeAdaptor;
  102. this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), args[1]);
  103. }
  104. /**
  105. * For DOMs that don't handle CSS well, use the font size from the options
  106. *
  107. * @override
  108. */
  109. public fontSize(node: N) {
  110. return (options.badCSS ? this.options.fontSize : super.fontSize(node));
  111. }
  112. /**
  113. * For DOMs that don't handle CSS well, use the font family from the options
  114. *
  115. * @override
  116. */
  117. public fontFamily(node: N) {
  118. return (options.badCSS ? this.options.fontFamily : super.fontFamily(node));
  119. }
  120. /**
  121. * @override
  122. */
  123. public nodeSize(node: N, em: number = 1, local: boolean = null) {
  124. if (!options.badSizes) {
  125. return super.nodeSize(node, em, local);
  126. }
  127. const text = this.textContent(node);
  128. const non = Array.from(text.replace(NodeAdaptor.cjkPattern, '')).length; // # of non-CJK chars
  129. const CJK = Array.from(text).length - non; // # of cjk chars
  130. return [
  131. CJK * this.options.cjkCharWidth + non * this.options.unknownCharWidth,
  132. this.options.unknownCharHeight
  133. ] as [number, number];
  134. }
  135. /**
  136. * @override
  137. */
  138. public nodeBBox(node: N) {
  139. return (options.badSizes ? {left: 0, right: 0, top: 0, bottom: 0} : super.nodeBBox(node));
  140. }
  141. };
  142. }