@sushichan044/kg-core 0.6.1 → 0.6.2

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.
Files changed (3) hide show
  1. package/dist/index.d.mts +248 -248
  2. package/dist/index.mjs +143 -133
  3. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1062,6 +1062,83 @@ const GridCell = {
1062
1062
  annotations: []
1063
1063
  })
1064
1064
  };
1065
+ //#endregion
1066
+ //#region src/internal/japanese-brackets.ts
1067
+ /**
1068
+ * Brackets and quotes a Japanese paragraph may open with. Halfwidth forms are included because a
1069
+ * manuscript that uses them is still opening a paragraph, not writing prose.
1070
+ */
1071
+ const OPENING_BRACKETS = "「『〖〈《((“\"‘'[[〔{{<<";
1072
+ /**
1073
+ * The closing counterparts, in the same order.
1074
+ */
1075
+ const CLOSING_BRACKETS = "」』〗〉》))”\"’']]〕}}>>";
1076
+ /**
1077
+ * Escapes the characters a regular expression character class would otherwise read as syntax, so a
1078
+ * set declared above can be dropped into a pattern.
1079
+ */
1080
+ function characterClass(characters) {
1081
+ return characters.replaceAll(/[\\\]^-]/gu, "\\$&");
1082
+ }
1083
+ //#endregion
1084
+ //#region src/internal/japanese-spaces.ts
1085
+ const LEADING_SPACES = /^[  \t]+/u;
1086
+ /**
1087
+ * The run of spaces the text begins with, or an empty string. Halfwidth spaces and tabs count: a
1088
+ * rule has to see them to say they are the wrong space.
1089
+ */
1090
+ function leadingSpaces(text) {
1091
+ return LEADING_SPACES.exec(text)?.[0] ?? "";
1092
+ }
1093
+ //#endregion
1094
+ //#region src/internal/question-or-exclamation-spacing.ts
1095
+ /**
1096
+ * Runs, not single marks: `!?` is one mark and takes one gap, so the gap belongs after the run.
1097
+ */
1098
+ const MARKS = /[?!]+/gu;
1099
+ function analyzeMatch(line, marks) {
1100
+ const start = marks.index;
1101
+ if (start === void 0) return void 0;
1102
+ const marksRange = {
1103
+ start,
1104
+ end: start + marks[0].length
1105
+ };
1106
+ const next = line[marksRange.end];
1107
+ if (next === void 0 || "」』〗〉》))”\"’']]〕}}>>".includes(next)) return void 0;
1108
+ const spaces = leadingSpaces(line.slice(marksRange.end));
1109
+ if (spaces === "") return {
1110
+ kind: "missing",
1111
+ marks: marksRange
1112
+ };
1113
+ const gap = {
1114
+ start: marksRange.end,
1115
+ end: marksRange.end + spaces.length
1116
+ };
1117
+ const following = line[gap.end];
1118
+ if (following !== void 0 && "」』〗〉》))”\"’']]〕}}>>".includes(following)) return {
1119
+ kind: "before-closing-bracket",
1120
+ marks: marksRange,
1121
+ gap
1122
+ };
1123
+ if (spaces !== " ") return {
1124
+ kind: "invalid-space",
1125
+ marks: marksRange,
1126
+ gap
1127
+ };
1128
+ return {
1129
+ kind: "valid",
1130
+ marks: marksRange,
1131
+ gap
1132
+ };
1133
+ }
1134
+ /**
1135
+ * Classifies the spacing after each run of fullwidth question or exclamation marks in one display
1136
+ * line. Runs at the end of the line or immediately before a closing bracket need no gap and are
1137
+ * omitted from the result.
1138
+ */
1139
+ function questionOrExclamationSpacings(line) {
1140
+ return [...line.matchAll(MARKS)].map((marks) => analyzeMatch(line, marks)).filter((spacing) => spacing !== void 0);
1141
+ }
1065
1142
  const GridLine = {
1066
1143
  schema: readonlyObject({
1067
1144
  range: v.nullable(ManuscriptRange.schema),
@@ -1191,12 +1268,32 @@ function toCells(sourceLine, annotations) {
1191
1268
  annotations: ManuscriptAnnotation.overlapping(annotations, grapheme.range)
1192
1269
  }));
1193
1270
  }
1271
+ function hangingGapIndexes(sourceLine) {
1272
+ const line = sourceLine.map(({ value }) => value).join("");
1273
+ const gapStarts = new Set(questionOrExclamationSpacings(line).filter((spacing) => spacing.kind === "valid").map(({ gap }) => gap.start));
1274
+ const indexes = /* @__PURE__ */ new Set();
1275
+ let offset = 0;
1276
+ for (const [index, grapheme] of sourceLine.entries()) {
1277
+ if (gapStarts.has(offset)) indexes.add(index);
1278
+ offset += grapheme.value.length;
1279
+ }
1280
+ return indexes;
1281
+ }
1194
1282
  /**
1195
- * Wraps one source line across as many grid lines as its length requires.
1283
+ * Wraps one source line across as many grid lines as its length requires. A valid gap after ! or ?
1284
+ * that would start a wrapped line is consumed as hanging punctuation instead of occupying a cell.
1196
1285
  */
1197
- function wrapLine(cells, charsPerLine) {
1286
+ function wrapLine(cells, charsPerLine, hangingGaps) {
1198
1287
  if (cells.length === 0) return [GridLine.padded([], charsPerLine)];
1199
- return Array.from({ length: Math.ceil(cells.length / charsPerLine) }, (_unused, index) => GridLine.padded(cells.slice(index * charsPerLine, (index + 1) * charsPerLine), charsPerLine));
1288
+ const lines = [];
1289
+ let cursor = 0;
1290
+ while (cursor < cells.length) {
1291
+ if (lines.length > 0 && hangingGaps.has(cursor)) cursor += 1;
1292
+ if (cursor >= cells.length) break;
1293
+ lines.push(GridLine.padded(cells.slice(cursor, cursor + charsPerLine), charsPerLine));
1294
+ cursor += charsPerLine;
1295
+ }
1296
+ return lines;
1200
1297
  }
1201
1298
  function blankLines(count, charsPerLine) {
1202
1299
  return Array.from({ length: count }, () => GridLine.padded([], charsPerLine));
@@ -1237,8 +1334,7 @@ function buildPages(contentLines, settings) {
1237
1334
  function composeGrid(manuscript, settings) {
1238
1335
  const { charsPerLine } = settings.grid;
1239
1336
  const sourceLines = displayedLines(manuscript.graphemes);
1240
- const cellLines = sourceLines.map((line) => toCells(line, manuscript.annotations));
1241
- const manuscriptLines = cellLines.flatMap((cells) => wrapLine(cells, charsPerLine));
1337
+ const manuscriptLines = sourceLines.flatMap((line) => wrapLine(toCells(line, manuscript.annotations), charsPerLine, hangingGapIndexes(line)));
1242
1338
  const pages = buildPages([
1243
1339
  ...blankLines(settings.offsets.document.leading, charsPerLine),
1244
1340
  ...manuscriptLines,
@@ -1248,7 +1344,7 @@ function composeGrid(manuscript, settings) {
1248
1344
  pages,
1249
1345
  geometry: ManuscriptGeometry.of(settings.grid, settings.appearance),
1250
1346
  stats: {
1251
- chars: cellLines.reduce((total, cells) => total + cells.length, 0),
1347
+ chars: sourceLines.reduce((total, line) => total + line.length, 0),
1252
1348
  sourceLines: sourceLines.length,
1253
1349
  pages: pages.length
1254
1350
  }
@@ -1544,7 +1640,7 @@ function defineMatchRule(options) {
1544
1640
  }
1545
1641
  //#endregion
1546
1642
  //#region src/proofreading/rules/consistent-kanji-opening.ts
1547
- const RULE_ID$6 = "kg/consistent-kanji-opening";
1643
+ const RULE_ID$5 = "kg/consistent-kanji-opening";
1548
1644
  const MESSAGE = "「{{ closed }}」と「{{ opened }}」が混在しています。文脈上の使い分けか表記ゆれかを確認してください";
1549
1645
  const KanjiOpeningPairSchema = v.pipe(readonlyObject({
1550
1646
  closed: v.pipe(v.string(), v.nonEmpty()),
@@ -1598,7 +1694,7 @@ const DEFAULT_PAIRS = [
1598
1694
  * two spellings are a deliberate distinction is the author's call, so the finding is a warning.
1599
1695
  */
1600
1696
  function build$3(pairs) {
1601
- return defineParsedRule(RULE_ID$6, MESSAGE, (manuscript, report) => {
1697
+ return defineParsedRule(RULE_ID$5, MESSAGE, (manuscript, report) => {
1602
1698
  for (const { closed, opened } of pairs) {
1603
1699
  if (!manuscript.displayText.includes(opened)) continue;
1604
1700
  const index = manuscript.displayText.indexOf(closed);
@@ -1619,7 +1715,7 @@ function createConsistentKanjiOpeningRule(options = {}) {
1619
1715
  const pairs = v.safeParse(PairsSchema, options.pairs);
1620
1716
  if (!pairs.success) return ManuscriptResult.fail({
1621
1717
  kind: "InvalidRuleOptions",
1622
- ruleId: RULE_ID$6,
1718
+ ruleId: RULE_ID$5,
1623
1719
  option: "pairs",
1624
1720
  issues: ValidationIssue.from(pairs.issues)
1625
1721
  });
@@ -1664,7 +1760,7 @@ const consistentNumeralWidthRule = () => defineMixedWidthRule({
1664
1760
  });
1665
1761
  //#endregion
1666
1762
  //#region src/proofreading/rules/dash.ts
1667
- const RULE_ID$5 = "kg/dash";
1763
+ const RULE_ID$4 = "kg/dash";
1668
1764
  const DEFAULT_PREFERRED = "―";
1669
1765
  const DashCharacterSchema = v.picklist([
1670
1766
  "―",
@@ -1686,7 +1782,7 @@ function build$2(preferred) {
1686
1782
  return {
1687
1783
  kind: "parsed",
1688
1784
  meta: {
1689
- id: RULE_ID$5,
1785
+ id: RULE_ID$4,
1690
1786
  messages
1691
1787
  },
1692
1788
  check: (manuscript, context) => {
@@ -1715,7 +1811,7 @@ function createDashRule(options = {}) {
1715
1811
  const preferred = v.safeParse(DashCharacterSchema, options.preferred);
1716
1812
  if (!preferred.success) return ManuscriptResult.fail({
1717
1813
  kind: "InvalidRuleOptions",
1718
- ruleId: RULE_ID$5,
1814
+ ruleId: RULE_ID$4,
1719
1815
  option: "preferred",
1720
1816
  issues: ValidationIssue.from(preferred.issues)
1721
1817
  });
@@ -1742,8 +1838,8 @@ const evenEllipsisRule = () => defineMatchRule({
1742
1838
  });
1743
1839
  //#endregion
1744
1840
  //#region src/proofreading/rules/fullwidth-japanese-punctuation.ts
1745
- const RULE_ID$4 = "kg/fullwidth-japanese-punctuation";
1746
- const MESSAGES$3 = {
1841
+ const RULE_ID$3 = "kg/fullwidth-japanese-punctuation";
1842
+ const MESSAGES$2 = {
1747
1843
  "halfwidth-japanese-punctuation": "半角の「{{ character }}」があります。日本語の句読点・括弧・感嘆符・疑問符は全角にしてください",
1748
1844
  "halfwidth-near-japanese": "日本語の前後に半角の「{{ character }}」があります。全角にすべきか確認してください"
1749
1845
  };
@@ -1788,8 +1884,8 @@ function isEllipsisSubstitute(text, index) {
1788
1884
  const fullwidthJapanesePunctuationRule = () => ({
1789
1885
  kind: "parsed",
1790
1886
  meta: {
1791
- id: RULE_ID$4,
1792
- messages: MESSAGES$3
1887
+ id: RULE_ID$3,
1888
+ messages: MESSAGES$2
1793
1889
  },
1794
1890
  check: (manuscript, context) => {
1795
1891
  for (const line of splitDisplayLines(manuscript.displayText)) for (const { pattern, messageId, severity, requiresJapanese } of CHECKS) for (const match of line.text.matchAll(pattern)) {
@@ -1807,12 +1903,12 @@ const fullwidthJapanesePunctuationRule = () => ({
1807
1903
  });
1808
1904
  //#endregion
1809
1905
  //#region src/proofreading/rules/max-arabic-numeral-digits.ts
1810
- const RULE_ID$3 = "kg/max-arabic-numeral-digits";
1906
+ const RULE_ID$2 = "kg/max-arabic-numeral-digits";
1811
1907
  const DEFAULT_MAX_DIGITS = 2;
1812
1908
  const MaxDigitsSchema = v.pipe(v.number(), v.finite(), v.integer(), v.minValue(1));
1813
1909
  function build$1(maxDigits) {
1814
1910
  return defineMatchRule({
1815
- id: RULE_ID$3,
1911
+ id: RULE_ID$2,
1816
1912
  pattern: /([0-90-9]+)(?:[..]([0-90-9]+))?/gu,
1817
1913
  accept: (match) => (match[1]?.length ?? 0) > maxDigits || (match[2]?.length ?? 0) > maxDigits,
1818
1914
  message: `${maxDigits}桁を超えるアラビア数字が使われています`
@@ -1824,7 +1920,7 @@ function createMaxArabicNumeralDigitsRule(options = {}) {
1824
1920
  const maxDigits = v.safeParse(MaxDigitsSchema, options.maxDigits);
1825
1921
  if (!maxDigits.success) return ManuscriptResult.fail({
1826
1922
  kind: "InvalidRuleOptions",
1827
- ruleId: RULE_ID$3,
1923
+ ruleId: RULE_ID$2,
1828
1924
  option: "maxDigits",
1829
1925
  issues: ValidationIssue.from(maxDigits.issues)
1830
1926
  });
@@ -1852,24 +1948,6 @@ const noConsecutivePunctuationRule = () => defineMatchRule({
1852
1948
  message: "句読点が連続しています"
1853
1949
  });
1854
1950
  //#endregion
1855
- //#region src/proofreading/rules/internal/brackets.ts
1856
- /**
1857
- * Brackets and quotes a Japanese paragraph may open with. Halfwidth forms are included because a
1858
- * manuscript that uses them is still opening a paragraph, not writing prose.
1859
- */
1860
- const OPENING_BRACKETS = "「『〖〈《((“\"‘'[[〔{{<<";
1861
- /**
1862
- * The closing counterparts, in the same order.
1863
- */
1864
- const CLOSING_BRACKETS = "」』〗〉》))”\"’']]〕}}>>";
1865
- /**
1866
- * Escapes the characters a regular expression character class would otherwise read as syntax, so a
1867
- * set declared above can be dropped into a pattern.
1868
- */
1869
- function characterClass(characters) {
1870
- return characters.replaceAll(/[\\\]^-]/gu, "\\$&");
1871
- }
1872
- //#endregion
1873
1951
  //#region src/proofreading/rules/internal/decoration-line.ts
1874
1952
  const DECORATION_LINE = /^[**◆◇■□●○▽▼△▲☆★・…―─━ー\-==~~\s]+$/u;
1875
1953
  /**
@@ -1880,19 +1958,9 @@ function isDecorationLine(text) {
1880
1958
  return DECORATION_LINE.test(text);
1881
1959
  }
1882
1960
  //#endregion
1883
- //#region src/proofreading/rules/internal/spaces.ts
1884
- const LEADING_SPACES = /^[  \t]+/u;
1885
- /**
1886
- * The run of spaces the text begins with, or an empty string. Halfwidth spaces and tabs count: a
1887
- * rule has to see them to say they are the wrong space.
1888
- */
1889
- function leadingSpaces(text) {
1890
- return LEADING_SPACES.exec(text)?.[0] ?? "";
1891
- }
1892
- //#endregion
1893
1961
  //#region src/proofreading/rules/paragraph-opening.ts
1894
- const RULE_ID$2 = "kg/paragraph-opening";
1895
- const MESSAGES$2 = {
1962
+ const RULE_ID$1 = "kg/paragraph-opening";
1963
+ const MESSAGES$1 = {
1896
1964
  "leading-character": "段落の先頭には全角スペースまたは開き括弧が必要です",
1897
1965
  "indent-width": "段落冒頭の字下げは全角スペース1字にしてください",
1898
1966
  "unexpected-indent": "括弧から始まる段落を字下げすることはできません"
@@ -1907,8 +1975,8 @@ function build(openingBrackets) {
1907
1975
  return {
1908
1976
  kind: "parsed",
1909
1977
  meta: {
1910
- id: RULE_ID$2,
1911
- messages: MESSAGES$2
1978
+ id: RULE_ID$1,
1979
+ messages: MESSAGES$1
1912
1980
  },
1913
1981
  check: (manuscript, context) => {
1914
1982
  const report = (start, end, messageId) => {
@@ -1938,7 +2006,7 @@ function createParagraphOpeningRule(options = {}) {
1938
2006
  const brackets = v.safeParse(OpeningBracketsSchema, options.openingBrackets);
1939
2007
  if (!brackets.success) return ManuscriptResult.fail({
1940
2008
  kind: "InvalidRuleOptions",
1941
- ruleId: RULE_ID$2,
2009
+ ruleId: RULE_ID$1,
1942
2010
  option: "openingBrackets",
1943
2011
  issues: ValidationIssue.from(brackets.issues)
1944
2012
  });
@@ -1954,25 +2022,21 @@ const punctuationBeforeClosingQuoteRule = () => defineMatchRule({
1954
2022
  });
1955
2023
  //#endregion
1956
2024
  //#region src/proofreading/rules/space-after-question-or-exclamation.ts
1957
- const RULE_ID$1 = "kg/space-after-question-or-exclamation";
1958
- const MESSAGES$1 = {
2025
+ const RULE_ID = "kg/space-after-question-or-exclamation";
2026
+ const MESSAGES = {
1959
2027
  default: "感嘆符または疑問符の直後には全角スペースか閉じ括弧が必要です",
1960
2028
  "space-width": "感嘆符または疑問符の直後の空白は全角スペース1字にしてください",
1961
2029
  "before-closing-bracket": "閉じ括弧の直前に空白を置くことはできません"
1962
2030
  };
1963
2031
  /**
1964
- * Runs, not single marks: `!?` is one mark and takes one gap, so the gap belongs after the run.
1965
- */
1966
- const MARKS = /[?!]+/gu;
1967
- /**
1968
2032
  * Checks the gap that follows every run of ! or ?. A run at the end of a line or right before a
1969
2033
  * closing bracket needs no gap; anywhere else it takes exactly one ideographic space.
1970
2034
  */
1971
2035
  const spaceAfterQuestionOrExclamationRule = () => ({
1972
2036
  kind: "parsed",
1973
2037
  meta: {
1974
- id: RULE_ID$1,
1975
- messages: MESSAGES$1
2038
+ id: RULE_ID,
2039
+ messages: MESSAGES
1976
2040
  },
1977
2041
  check: (manuscript, context) => {
1978
2042
  const report = (start, end, messageId) => {
@@ -1981,93 +2045,39 @@ const spaceAfterQuestionOrExclamationRule = () => ({
1981
2045
  messageId
1982
2046
  });
1983
2047
  };
1984
- for (const line of splitDisplayLines(manuscript.displayText)) for (const marks of line.text.matchAll(MARKS)) {
1985
- const marksEnd = marks.index + marks[0].length;
1986
- const next = line.text[marksEnd];
1987
- if (next === void 0 || "」』〗〉》))”\"’']]〕}}>>".includes(next)) continue;
1988
- const spaces = leadingSpaces(line.text.slice(marksEnd));
1989
- if (spaces === "") {
1990
- report(line.start + marks.index, line.start + marksEnd, "default");
1991
- continue;
1992
- }
1993
- const following = line.text[marksEnd + spaces.length];
1994
- const spacesStart = line.start + marksEnd;
1995
- const spacesEnd = spacesStart + spaces.length;
1996
- if (following !== void 0 && "」』〗〉》))”\"’']]〕}}>>".includes(following)) report(spacesStart, spacesEnd, "before-closing-bracket");
1997
- else if (spaces !== " ") report(spacesStart, spacesEnd, "space-width");
2048
+ for (const line of splitDisplayLines(manuscript.displayText)) for (const spacing of questionOrExclamationSpacings(line.text)) switch (spacing.kind) {
2049
+ case "valid": break;
2050
+ case "missing":
2051
+ report(line.start + spacing.marks.start, line.start + spacing.marks.end, "default");
2052
+ break;
2053
+ case "invalid-space":
2054
+ report(line.start + spacing.gap.start, line.start + spacing.gap.end, "space-width");
2055
+ break;
2056
+ case "before-closing-bracket":
2057
+ report(line.start + spacing.gap.start, line.start + spacing.gap.end, "before-closing-bracket");
2058
+ break;
2059
+ default: assertNever(spacing);
1998
2060
  }
1999
2061
  }
2000
2062
  });
2001
2063
  //#endregion
2002
2064
  //#region src/proofreading/rules/variant-character.ts
2003
- const RULE_ID = "kg/variant-character";
2004
- const MESSAGES = {
2005
- default: "異体字または字形選択子が使われています",
2006
- "with-suggestion": "異体字または字形選択子が使われています。「{{ suggestion }}」ではありませんか?"
2007
- };
2008
- /**
2009
- * Mongolian free variation selectors, variation selectors (incl. emoji presentation), IVSes: all
2010
- * combine with a preceding base character rather than standing on their own.
2011
- */
2012
- function isSelector(codePoint) {
2013
- return codePoint >= 6155 && codePoint <= 6157 || codePoint === 6159 || codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999;
2014
- }
2015
2065
  /**
2016
- * CJK compatibility ideographs stand alone; each has a canonical unified form reachable via NFKC.
2066
+ * Mongolian free variation selectors, variation selectors, CJK compatibility ideographs, IVSes.
2017
2067
  */
2018
- function isCompatibilityIdeograph(codePoint) {
2019
- return codePoint >= 63744 && codePoint <= 64255 || codePoint >= 194560 && codePoint <= 195103;
2020
- }
2021
2068
  function isVariantCharacter(character) {
2022
2069
  const codePoint = character.codePointAt(0);
2023
2070
  if (codePoint === void 0) return false;
2024
- return isSelector(codePoint) || isCompatibilityIdeograph(codePoint);
2025
- }
2026
- /**
2027
- * The plain form a reader would expect: the selector stripped away, or the compatibility
2028
- * ideograph's canonical unified form. Undefined when that plain form is identical to the original,
2029
- * since NFKC does not touch selectors and a suggestion equal to the input helps no one.
2030
- */
2031
- function suggestPlainForm(segment) {
2032
- const withoutSelectors = Array.from(segment).filter((character) => {
2033
- const codePoint = character.codePointAt(0);
2034
- return codePoint !== void 0 && !isSelector(codePoint);
2035
- }).join("");
2036
- if (withoutSelectors === "") return void 0;
2037
- const normalized = Array.from(withoutSelectors).map((character) => {
2038
- const codePoint = character.codePointAt(0);
2039
- return codePoint !== void 0 && isCompatibilityIdeograph(codePoint) ? character.normalize("NFKC") : character;
2040
- }).join("");
2041
- return normalized === segment ? void 0 : normalized;
2071
+ return codePoint >= 6155 && codePoint <= 6157 || codePoint === 6159 || codePoint >= 65024 && codePoint <= 65039 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 194560 && codePoint <= 195103 || codePoint >= 917760 && codePoint <= 917999;
2042
2072
  }
2043
2073
  /**
2044
2074
  * Scans the source rather than the display text: notation removal must not hide a variant.
2045
2075
  */
2046
- const variantCharacterRule = () => ({
2047
- kind: "parsed",
2048
- meta: {
2049
- id: RULE_ID,
2050
- messages: MESSAGES
2051
- },
2052
- check: (manuscript, context) => {
2053
- for (const { index, segment } of graphemeSegmenter.segment(manuscript.source)) {
2054
- if (!Array.from(segment).some(isVariantCharacter)) continue;
2055
- const range = sourceRange(manuscript, {
2056
- start: index,
2057
- end: index + segment.length
2058
- });
2059
- const suggestion = suggestPlainForm(segment);
2060
- if (suggestion === void 0) context.report({
2061
- range,
2062
- messageId: "default"
2063
- });
2064
- else context.report({
2065
- range,
2066
- messageId: "with-suggestion",
2067
- data: { suggestion }
2068
- });
2069
- }
2070
- }
2076
+ const variantCharacterRule = () => defineParsedRule("kg/variant-character", "異体字または字形選択子が使われています", (manuscript, report) => {
2077
+ for (const { index, segment } of graphemeSegmenter.segment(manuscript.source)) if (Array.from(segment).some(isVariantCharacter)) report(sourceRange(manuscript, {
2078
+ start: index,
2079
+ end: index + segment.length
2080
+ }));
2071
2081
  });
2072
2082
  //#endregion
2073
2083
  //#region src/proofreading/rules/default-rules.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sushichan044/kg-core",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Framework-independent parsing, composition, and proofreading for Japanese manuscripts",
5
5
  "keywords": [
6
6
  "japanese",