@sushichan044/kg-core 0.10.1 → 0.10.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.
package/README.md CHANGED
@@ -150,6 +150,24 @@ tate-chu-yoko unit. Each render unit carries its selected `presentation` as a
150
150
  string. A `CombinedGlyphUnit` renders the two digits together; the composer owns
151
151
  that decision, so renderers do not compare source group ranges.
152
152
 
153
+ ASCII sentences are recognized before individual words are classified. A continuous
154
+ stretch of ASCII letters, digits, punctuation, and spaces containing a letter and
155
+ multiple alphanumeric groups separated by spaces or punctuation renders sideways.
156
+ Japanese characters, fullwidth spaces, and newlines end that context. Thus `I love you`,
157
+ `This is a Web novel`, `I'm happy!`, and `STR 10` keep their initials, abbreviations,
158
+ numbers, and punctuation sideways. Standalone `I`, `BGM`, and `Web` retain their
159
+ upright presentation, and standalone `12` still forms a tate-chu-yoko unit.
160
+ Standalone dotted abbreviations whose parts match the upright abbreviation rule,
161
+ such as `U.S.` and `Ph.D.`, keep their letters upright and their periods in the
162
+ existing mixed presentation. In a sentence such as `I live in the U.S.`, the
163
+ abbreviation and its periods render sideways with the surrounding words.
164
+
165
+ Sentences can wrap between words. Words, including attached punctuation,
166
+ apostrophes, and hyphens, stay together; ASCII spaces retain
167
+ their source positions and supply line-break opportunities. Ruby and inline styling
168
+ do not interrupt sentence recognition. Non-ASCII Western text keeps the existing
169
+ presentation rules.
170
+
153
171
  An upright Western character sets solid against the kana or kanji beside it rather than
154
172
  taking the quarter em 3.2.6 puts around a sideways run, following JLReq 3.2.4: it classes
155
173
  with kanji, the same way 3.2.5 classes a tate-chu-yoko run. `QR` and `URL` set upright by
@@ -0,0 +1,9 @@
1
+ //#region src/internal/describe-cause.ts
2
+ /**
3
+ * Renders an unknown throwable for display, without assuming it is an Error.
4
+ */
5
+ function describeCause(cause) {
6
+ return cause instanceof Error ? cause.message : String(cause);
7
+ }
8
+ //#endregion
9
+ export { describeCause as t };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { c as graphemeSegmenter, d as ManuscriptDiagnostic, l as ValidationIssue, m as ManuscriptRange, p as NamespacedId, u as ManuscriptResult, v as readonlyObject } from "./question-or-exclamation-spacing-Cp3eOGlA.mjs";
2
- import { a as PaperSize, d as ManuscriptOffsets, f as ManuscriptAppearanceSettings, g as ParsedManuscript, h as FontPresetId, i as ManuscriptGeometry, l as NovelCompositionSettings, m as FontSizePt, n as novelComposer, p as PaperSizeId, u as NovelFlowSettings } from "./novel-composer-D_orH6ET.mjs";
3
- import { n as assertNever, t as describeCause } from "./describe-cause-BV8mZ3Um.mjs";
1
+ import { c as assertNever, d as ManuscriptResult, f as ManuscriptDiagnostic, h as ManuscriptRange, l as graphemeSegmenter, m as NamespacedId, u as ValidationIssue, y as readonlyObject } from "./question-or-exclamation-spacing-C5cFLeKr.mjs";
2
+ import { a as PaperSize, d as ManuscriptOffsets, f as ManuscriptAppearanceSettings, g as ParsedManuscript, h as FontPresetId, i as ManuscriptGeometry, l as NovelCompositionSettings, m as FontSizePt, n as novelComposer, p as PaperSizeId, u as NovelFlowSettings } from "./novel-composer-CHNWyvn8.mjs";
3
+ import { t as describeCause } from "./describe-cause-SABI_1ex.mjs";
4
4
  import * as v from "valibot";
5
5
  const Rejection = { schema: readonlyObject({ reason: v.string() }) };
6
6
  //#endregion
package/dist/lint.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as readonlyArray, a as CLOSING_BRACKETS, c as graphemeSegmenter, d as ManuscriptDiagnostic, f as DiagnosticSeverity, l as ValidationIssue, m as ManuscriptRange, n as leadingSpaces, o as OPENING_BRACKETS, p as NamespacedId, s as characterClass, t as questionOrExclamationSpacings, u as ManuscriptResult, v as readonlyObject } from "./question-or-exclamation-spacing-Cp3eOGlA.mjs";
2
- import { n as assertNever, t as describeCause } from "./describe-cause-BV8mZ3Um.mjs";
1
+ import { a as CLOSING_BRACKETS, c as assertNever, d as ManuscriptResult, f as ManuscriptDiagnostic, h as ManuscriptRange, l as graphemeSegmenter, m as NamespacedId, n as leadingSpaces, o as OPENING_BRACKETS, p as DiagnosticSeverity, s as characterClass, t as questionOrExclamationSpacings, u as ValidationIssue, v as readonlyArray, y as readonlyObject } from "./question-or-exclamation-spacing-C5cFLeKr.mjs";
2
+ import { t as describeCause } from "./describe-cause-SABI_1ex.mjs";
3
3
  import * as v from "valibot";
4
4
  //#region src/proofreading/invalid-rule-options.ts
5
5
  const InvalidRuleOptions = { describe: (error) => `ルール ${error.ruleId} のオプション "${error.option}" が不正です: ${error.issues.map(({ message }) => message).join(", ")}` };
@@ -1,4 +1,4 @@
1
- import { _ as readonlyArray, a as CLOSING_BRACKETS, c as graphemeSegmenter, g as nonNegativeInteger, h as TextRange, i as TATE_CHU_YOKO_MARKS, m as ManuscriptRange, o as OPENING_BRACKETS, p as NamespacedId, r as DIVIDING_PUNCTUATION, s as characterClass, t as questionOrExclamationSpacings, u as ManuscriptResult, v as readonlyObject } from "./question-or-exclamation-spacing-Cp3eOGlA.mjs";
1
+ import { _ as nonNegativeInteger, a as CLOSING_BRACKETS, c as assertNever, d as ManuscriptResult, g as TextRange, h as ManuscriptRange, i as TATE_CHU_YOKO_MARKS, l as graphemeSegmenter, m as NamespacedId, o as OPENING_BRACKETS, r as DIVIDING_PUNCTUATION, s as characterClass, t as questionOrExclamationSpacings, v as readonlyArray, y as readonlyObject } from "./question-or-exclamation-spacing-C5cFLeKr.mjs";
2
2
  import * as v from "valibot";
3
3
  import { eastAsianWidth } from "get-east-asian-width";
4
4
  const BoldAnnotation = { schema: readonlyObject({
@@ -365,6 +365,9 @@ const ASCII_TWO_DIGITS = /^[0-9]{2}$/u;
365
365
  const UPRIGHT_LATIN_ABBREVIATION = /^(?:[A-Z]+|[A-Z][a-z]{1,2})$/u;
366
366
  const TATE_CHU_YOKO_MARK = new RegExp(`^[${characterClass(TATE_CHU_YOKO_MARKS)}]$`, "u");
367
367
  const FULLWIDTH_ALPHANUMERIC = /^[A-Za-z0-9]$/u;
368
+ const ASCII_TEXT = /^[ -~]$/u;
369
+ const ASCII_WORD = /^[!-~]$/u;
370
+ const WESTERN_CONTEXT = /[A-Za-z0-9][^A-Za-z0-9]+[A-Za-z0-9]/u;
368
371
  function stretchEnd(graphemes, start, pattern) {
369
372
  let end = start + 1;
370
373
  while (pattern.test(graphemes[end]?.value ?? "")) end += 1;
@@ -377,6 +380,10 @@ function presentationOf(text) {
377
380
  }
378
381
  return FULLWIDTH_ALPHANUMERIC.test(text) ? "upright" : "mixed";
379
382
  }
383
+ /**
384
+ * Recognize sentence-wide presentation while keeping words as separate line-break units. Standalone
385
+ * dotted abbreviations retain the existing per-token presentation.
386
+ */
380
387
  function recognize(graphemes) {
381
388
  const runs = [];
382
389
  const push = (members, presentation) => {
@@ -388,9 +395,25 @@ function recognize(graphemes) {
388
395
  });
389
396
  };
390
397
  let cursor = 0;
398
+ let checkedThrough = 0;
391
399
  while (cursor < graphemes.length) {
392
400
  const first = graphemes[cursor];
393
401
  if (first === void 0) break;
402
+ if (cursor >= checkedThrough && ASCII_TEXT.test(first.value)) {
403
+ const contextEnd = stretchEnd(graphemes, cursor, ASCII_TEXT);
404
+ checkedThrough = contextEnd;
405
+ const text = graphemes.slice(cursor, contextEnd).map(({ value }) => value).join("");
406
+ const abbreviationParts = text.trim().replace(/\.$/u, "").split(".");
407
+ const standaloneDottedAbbreviation = abbreviationParts.length > 1 && abbreviationParts.every((part) => UPRIGHT_LATIN_ABBREVIATION.test(part));
408
+ if (/[A-Za-z]/u.test(text) && WESTERN_CONTEXT.test(text) && !standaloneDottedAbbreviation) {
409
+ while (cursor < contextEnd) {
410
+ const end = ASCII_WORD.test(graphemes[cursor]?.value ?? "") ? stretchEnd(graphemes, cursor, ASCII_WORD) : cursor + 1;
411
+ push(graphemes.slice(cursor, end), "sideways");
412
+ cursor = end;
413
+ }
414
+ continue;
415
+ }
416
+ }
394
417
  if (TATE_CHU_YOKO_MARK.test(first.value)) {
395
418
  const end = stretchEnd(graphemes, cursor, TATE_CHU_YOKO_MARK);
396
419
  const members = graphemes.slice(cursor, end);
@@ -959,29 +982,35 @@ const WESTERN = /^[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}]$/u;
959
982
  * the same as a tate-chu-yoko run's 3.2.5.
960
983
  */
961
984
  function classify({ value, presentation }) {
962
- if (presentation === "tate-chu-yoko") return COMBINED_DIVIDING_MARKS.has(value) ? "cl-04" : "cl-30";
963
- if (presentation === "upright") return "cl-19";
964
- if (OPENING.has(value)) return "cl-01";
965
- if (CLOSING.has(value)) return "cl-02";
966
- if (HYPHENS.has(value)) return "cl-03";
967
- if (DIVIDING_MARKS.has(value)) return "cl-04";
968
- if (MIDDLE_DOTS.has(value)) return "cl-05";
969
- if (value === "。" || value === ".") return "cl-06";
970
- if (value === "、" || value === ",") return "cl-07";
971
- if (INSEPARABLE.has(value)) return "cl-08";
972
- if (ITERATION_MARKS.has(value)) return "cl-09";
973
- if (value === "ー") return "cl-10";
974
- if (SMALL_KANA.has(value)) return "cl-11";
975
- if (PREFIXED_ABBREVIATIONS.has(value)) return "cl-12";
976
- if (POSTFIXED_ABBREVIATIONS.has(value)) return "cl-13";
977
- if (value === " ") return "cl-14";
978
- if (HIRAGANA.test(value)) return "cl-15";
979
- if (KATAKANA.test(value)) return "cl-16";
980
- if (GROUPED_NUMERAL.test(value)) return "cl-24";
981
- if (UNIT_SYMBOLS.has(value)) return "cl-25";
982
- if (value === " ") return "cl-26";
983
- if (WESTERN.test(value)) return "cl-27";
984
- return "cl-19";
985
+ switch (presentation) {
986
+ case "tate-chu-yoko": return COMBINED_DIVIDING_MARKS.has(value) ? "cl-04" : "cl-30";
987
+ case "upright": return "cl-19";
988
+ case "sideways":
989
+ case "mixed":
990
+ if (presentation === "sideways" && /^[!-~]$/u.test(value) && !/^[A-Za-z0-9]$/u.test(value)) return "cl-27";
991
+ if (OPENING.has(value)) return "cl-01";
992
+ if (CLOSING.has(value)) return "cl-02";
993
+ if (HYPHENS.has(value)) return "cl-03";
994
+ if (DIVIDING_MARKS.has(value)) return "cl-04";
995
+ if (MIDDLE_DOTS.has(value)) return "cl-05";
996
+ if (value === "。" || value === ".") return "cl-06";
997
+ if (value === "、" || value === ",") return "cl-07";
998
+ if (INSEPARABLE.has(value)) return "cl-08";
999
+ if (ITERATION_MARKS.has(value)) return "cl-09";
1000
+ if (value === "ー") return "cl-10";
1001
+ if (SMALL_KANA.has(value)) return "cl-11";
1002
+ if (PREFIXED_ABBREVIATIONS.has(value)) return "cl-12";
1003
+ if (POSTFIXED_ABBREVIATIONS.has(value)) return "cl-13";
1004
+ if (value === " ") return "cl-14";
1005
+ if (HIRAGANA.test(value)) return "cl-15";
1006
+ if (KATAKANA.test(value)) return "cl-16";
1007
+ if (GROUPED_NUMERAL.test(value)) return "cl-24";
1008
+ if (UNIT_SYMBOLS.has(value)) return "cl-25";
1009
+ if (value === " ") return "cl-26";
1010
+ if (WESTERN.test(value)) return "cl-27";
1011
+ return "cl-19";
1012
+ default: return assertNever(presentation);
1013
+ }
985
1014
  }
986
1015
  /**
987
1016
  * 無印 in 表1 with no colour in 表6: the pair is set solid and line adjustment leaves it alone.
package/dist/plugin.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { l as ValidationIssue, p as NamespacedId, u as ManuscriptResult, v as readonlyObject } from "./question-or-exclamation-spacing-Cp3eOGlA.mjs";
2
- import { c as MeasurementTextRange, g as ParsedManuscript, h as FontPresetId, m as FontSizePt, o as RunMeasurement, r as logicalRunMeasurer, s as MeasuredCluster, t as createNovelComposer } from "./novel-composer-D_orH6ET.mjs";
1
+ import { d as ManuscriptResult, m as NamespacedId, u as ValidationIssue, y as readonlyObject } from "./question-or-exclamation-spacing-C5cFLeKr.mjs";
2
+ import { c as MeasurementTextRange, g as ParsedManuscript, h as FontPresetId, m as FontSizePt, o as RunMeasurement, r as logicalRunMeasurer, s as MeasuredCluster, t as createNovelComposer } from "./novel-composer-CHNWyvn8.mjs";
3
3
  import * as v from "valibot";
4
4
  const PresentationKind = { schema: v.picklist([
5
5
  "mixed",
@@ -171,6 +171,15 @@ const ValidationIssue = {
171
171
  */
172
172
  const graphemeSegmenter = new Intl.Segmenter("ja", { granularity: "grapheme" });
173
173
  //#endregion
174
+ //#region src/internal/assert-never.ts
175
+ /**
176
+ * Marks a branch the compiler proved unreachable. Reaching it at runtime means a variant was added
177
+ * to a union without updating the switch that produced this call.
178
+ */
179
+ function assertNever(value) {
180
+ throw new TypeError(`unhandled variant: ${JSON.stringify(value)}`);
181
+ }
182
+ //#endregion
174
183
  //#region src/internal/japanese-brackets.ts
175
184
  /**
176
185
  * Brackets and quotes a Japanese paragraph may open with. Halfwidth forms are included because a
@@ -259,4 +268,4 @@ function questionOrExclamationSpacings(line) {
259
268
  return [...line.matchAll(MARKS)].map((marks) => analyzeMatch(line, marks)).filter((spacing) => spacing !== void 0);
260
269
  }
261
270
  //#endregion
262
- export { readonlyArray as _, CLOSING_BRACKETS as a, graphemeSegmenter as c, ManuscriptDiagnostic as d, DiagnosticSeverity as f, nonNegativeInteger as g, TextRange as h, TATE_CHU_YOKO_MARKS as i, ValidationIssue as l, ManuscriptRange as m, leadingSpaces as n, OPENING_BRACKETS as o, NamespacedId as p, DIVIDING_PUNCTUATION as r, characterClass as s, questionOrExclamationSpacings as t, ManuscriptResult as u, readonlyObject as v };
271
+ export { nonNegativeInteger as _, CLOSING_BRACKETS as a, assertNever as c, ManuscriptResult as d, ManuscriptDiagnostic as f, TextRange as g, ManuscriptRange as h, TATE_CHU_YOKO_MARKS as i, graphemeSegmenter as l, NamespacedId as m, leadingSpaces as n, OPENING_BRACKETS as o, DiagnosticSeverity as p, DIVIDING_PUNCTUATION as r, characterClass as s, questionOrExclamationSpacings as t, ValidationIssue as u, readonlyArray as v, readonlyObject as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sushichan044/kg-core",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Framework-independent parsing, composition, and proofreading for Japanese manuscripts",
5
5
  "keywords": [
6
6
  "japanese",
@@ -1,18 +0,0 @@
1
- //#region src/internal/assert-never.ts
2
- /**
3
- * Marks a branch the compiler proved unreachable. Reaching it at runtime means a variant was added
4
- * to a union without updating the switch that produced this call.
5
- */
6
- function assertNever(value) {
7
- throw new TypeError(`unhandled variant: ${JSON.stringify(value)}`);
8
- }
9
- //#endregion
10
- //#region src/internal/describe-cause.ts
11
- /**
12
- * Renders an unknown throwable for display, without assuming it is an Error.
13
- */
14
- function describeCause(cause) {
15
- return cause instanceof Error ? cause.message : String(cause);
16
- }
17
- //#endregion
18
- export { assertNever as n, describeCause as t };