@sushichan044/kg-core 0.9.4 → 0.10.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/README.md CHANGED
@@ -146,36 +146,58 @@ The composer classifies vertical text before measuring and breaking lines. Follo
146
146
  [JLReq](https://www.w3.org/TR/jlreq/#mixed_text_composition_in_vertical_writing_mode),
147
147
  upright Latin initials and abbreviations advance by one em per character, Western
148
148
  words remain unbroken and render sideways, and two ASCII digits form one
149
- tate-chu-yoko unit. The resulting `VerticalTextPresentation` is carried by every
150
- glyph item, so renderers do not need to infer orientation independently.
149
+ tate-chu-yoko unit. Each render unit carries its selected `presentation` as a
150
+ string. A `CombinedGlyphUnit` renders the two digits together; the composer owns
151
+ that decision, so renderers do not compare source group ranges.
151
152
 
152
153
  An upright Western character sets solid against the kana or kanji beside it rather than
153
154
  taking the quarter em 3.2.6 puts around a sideways run, following JLReq 3.2.4: it classes
154
155
  with kanji, the same way 3.2.5 classes a tate-chu-yoko run. `QR` and `URL` set upright by
155
156
  this rule read flush against their surrounding Japanese text, the way a kanji compound does.
156
157
 
157
- The default logical measurer uses East Asian Width in a Japanese context. Upright
158
- ASCII advances by one em; proportional ASCII and the members of a tate-chu-yoko unit
159
- advance by half an em. Supply a synchronous measurer when the caller has more accurate
160
- font metrics. Base-text requests also include the selected `presentation`:
158
+ The default `logicalRunMeasurer` uses East Asian Width in a Japanese context.
159
+ Upright ASCII advances by one em; sideways ASCII and tate-chu-yoko members
160
+ advance by half an em. Measurement is synchronous and DOM-independent.
161
+
162
+ Supply a `RunMeasurer` through the plugin entry when metrics are already available:
161
163
 
162
164
  ```ts
163
- import { createNovelComposer } from "@sushichan044/kg-core/plugin";
164
-
165
- const composer = createNovelComposer({
166
- measurer: (request) => ({
167
- advanceEm: measureWithAvailableFont(request.text, {
168
- role: request.role,
169
- fontPreset: request.fontPreset,
170
- writingMode: request.writingMode,
171
- ...(request.role === "base" ? { presentation: request.presentation } : {}),
172
- }),
173
- }),
174
- });
165
+ import { createNovelComposer, logicalRunMeasurer } from "@sushichan044/kg-core/plugin";
166
+
167
+ const composer = createNovelComposer({ measurer: logicalRunMeasurer });
175
168
  ```
176
169
 
177
- A measurer returns `{ advanceEm }` in logical em units. Negative or non-finite
178
- results reject the composition instead of producing a partial layout.
170
+ `MeasurementRequest` includes `text`, `kind` (`base` or `ruby`), `presentation`,
171
+ `fontPreset`, body `fontSizePt`, `writingMode`, and `scale`. A provider returns
172
+ measurements in body em and applies `scale` once: `1` for base text, `0.5` for ruby.
173
+ Measured widths exclude punctuation box adjustments, pair spacing, hanging, and
174
+ ruby expansion.
175
+
176
+ `RunMeasurement` has two variants:
177
+
178
+ - `{ kind: "advance", advanceEm }` supplies aggregate width. The composer retains
179
+ per-grapheme measurement for positioning; it never divides a nonadditive run
180
+ width equally among its members. Complete reading widths still inform base
181
+ widening.
182
+ - `{ kind: "clustered", advanceEm, clusters }` supplies authoritative cluster
183
+ positions. Each `MeasuredCluster` has a request-local `textRange`, `layoutSpan`,
184
+ and `renderSpan`. Use `MeasurementTextRange.of({ start, end })` to construct the
185
+ branded UTF-16 range. Clusters partition text at grapheme boundaries and layout
186
+ advance within `1e-9`; render spans can overlap and overhang. The composer does
187
+ not remeasure cluster members or break a line inside a cluster.
188
+
189
+ A multi-grapheme sideways cluster, such as a shaped `ffi` ligature, is drawn in a
190
+ combined run with shared source positions. Upright `QR` and `URL` are orientation
191
+ choices; they do not by themselves imply a shared cluster. The combined-unit
192
+ contract supports sideways text and tate-chu-yoko. A provider result that cannot
193
+ be represented by that contract is refused rather than split or approximated.
194
+ The logical provider returns `advance`; a real shaped provider and matching font
195
+ realization remain later work.
196
+
197
+ Tate-chu-yoko retains the sum of member advances, including custom widths. The
198
+ composer does not silently rescale a custom measurement to one em. Negative or
199
+ non-finite advances and invalid cluster mappings produce a typed composition
200
+ failure rather than a partial layout.
179
201
 
180
202
  Ruby annotations retain one of three associations: `group` for one reading over
181
203
  the entire base, `mono` for one reading segment per base grapheme, and `jukugo`
@@ -206,7 +228,7 @@ ranges are distinct branded types, and plugin IDs are branded `NamespacedId`.
206
228
 
207
229
  A companion object is exported as a value only when one of the three roles
208
230
  calls something on it. Concepts you only ever read out of a composed layout —
209
- `NovelPage`, `ComposedGlyph`, `LineBreakResult`, and the like — are exported as
231
+ `NovelPage`, `SingleGlyphUnit`, `CombinedGlyphUnit`, `LineBreakResult`, and the like — are exported as
210
232
  types alone, so their schemas are not part of the API. The settings you persist
211
233
  and re-validate keep theirs: `NovelCompositionSettings.schema`,
212
234
  `ManuscriptAppearanceSettings.schema`, `ManuscriptOffsets.schema`,
@@ -218,12 +240,65 @@ failure carries exactly one error from a discriminated union, so callers can
218
240
  as typed fields; `describe` renders one for display. Invalid settings and
219
241
  plugin output fail explicitly; values are never clamped or partially trusted.
220
242
 
221
- ## Source mapping
243
+ ## Source mapping and render units
222
244
 
223
245
  Source and display ranges are zero-based, end-exclusive UTF-16 offsets.
224
- Grapheme ranges index the normalized grapheme array. Diagnostics include
225
- one-based source line and column positions, so renderers do not need to search
226
- or recalculate locations.
246
+ Grapheme ranges index the normalized grapheme array. Measurement text ranges are
247
+ local to a request and have a different brand.
248
+
249
+ `NovelLine.items` contains `PositionedInlineItem`: single glyphs, combined glyphs,
250
+ glue, kern, and suppressed source text. Render units have distinct `layoutSpan`
251
+ and `renderSpan` values plus `sources`. An `exact` source placement locates one
252
+ parsed grapheme; a `shared` placement associates several graphemes with one span
253
+ without inventing interior caret positions. Default tate-chu-yoko retains exact
254
+ half-em positions for each digit. A diagnostic on a shared member highlights the
255
+ shared span while keeping the diagnostic's original range.
256
+
257
+ Hanging glyphs have zero layout advance and a nonzero render span. Authored glue
258
+ and suppressed separators keep their source ranges, including at line edges.
259
+ `NovelLine.range` includes all source-backed members. Generated spacing has no
260
+ source range.
261
+
262
+ Ruby fragments contain `readingItems`, each with a fragment-local `textRange`
263
+ and `AnnotationPlacement`. Emphasis fragments contain `placements`. Inline
264
+ positions are line-relative; block offsets start at the body's right edge and
265
+ increase leftward. The current right-side, half-size decorations use `side:
266
+ "before"`, `blockOffsetEm: -0.5`, and `blockSizeEm: 0.5`. The viewer consumes these
267
+ coordinates without computing ruby or emphasis placement.
268
+
269
+ Annotations retain their original logical association even on part of a combined
270
+ unit, whose whole physical span anchors the decoration. Incompatible readings on
271
+ members of the same unit cause a composition refusal. Bold and italic retain
272
+ source-range fragments. Ruby overhang depends on adjacent characters, resolved
273
+ punctuation spacing, and the candidate's line edges. Mono readings anchor to their
274
+ own bases; jukugo readings use solid shoulder placement and joint arrangement.
275
+ Jukugo placement tries compound-interior overhang before outside neighbours, then
276
+ distributes required spacing around long readings in proportion to their measured
277
+ widths. Line-edge shares go inward. A single-base fragment touching both edges
278
+ uses head alignment and box expansion when needed. Oversized group readings are
279
+ assigned as contiguous measurement-cluster intervals before line scoring.
280
+
281
+ Emphasis placements have a half-em inline extent centered on their render unit.
282
+ Brackets, commas, and full stops receive no marks. Ruby and nonempty emphasis
283
+ must fit the fixed half-em right-side area without colliding. Core may choose
284
+ another line break; an impossible combination returns `ComposerRejected` with
285
+ a reason. Body pitch, source ranges, and persisted settings remain unchanged.
286
+ Actual font shaping, selectable sides, sidelines, and mixed sizes remain deferred.
287
+
288
+ Diagnostics include one-based source line and column positions, so renderers do
289
+ not need to search or recalculate locations.
290
+
291
+ ### Breaking API migration
292
+
293
+ Update core and viewer together. `SingleGlyphUnit`, `CombinedGlyphUnit`, and
294
+ `PositionedInlineItem` replace `ComposedGlyph` and `ComposedInlineItem`;
295
+ `PresentationKind` replaces the `VerticalTextPresentation` group object. The
296
+ plugin entry replaces `InlineMeasureRequest`, `InlineMeasurement`,
297
+ `InlineMeasurer`, and `logicalInlineMeasurer` with the run measurement contracts
298
+ above. `createNovelComposer({ measurer })` keeps its option name but requires the
299
+ new request and discriminated result. Ruby `readingGraphemes` becomes
300
+ `readingItems`; decoration positions move into core output. Persisted settings
301
+ and parser/proofreading entrypoints are unchanged.
227
302
 
228
303
  ## Proofreading rules
229
304