Skip to content

Roadmap / existing work for improved CJK line breaking and controlled line compression? #3884

Description

@lioensky

What problem does this solve?

Summary

We are integrating SuperDoc 1.45.0 into a desktop DOCX authoring application and are evaluating how to improve CJK line breaking and pagination quality.

The current layout engine already appears to contain several important building blocks:

  • Canvas-based text measurement;
  • run-level letterSpacing support;
  • preservation of both natural and final line widths;
  • justification-aware width adjustment;
  • parsing support for OOXML paragraph properties such as wordWrap, kinsoku, and widowControl;
  • a limited compression path when an overflowing candidate is placed in a justified paragraph.

Before maintaining a downstream patch or a long-lived fork, we would like to ask whether improved CJK line breaking, controlled line compression, or related font-metric work is already being developed in a branch or pull request.

What we are observing

For Chinese and mixed CJK/Latin paragraphs, the current greedy wrapping behavior can produce visually weak results in cases where:

  • a line exceeds the available width by only a few pixels;
  • moving one CJK character to the next line creates a very short final line;
  • punctuation ends up near an undesirable line boundary;
  • a left-aligned CJK paragraph cannot use the compression behavior currently available to justified paragraphs;
  • font fallback changes the physical font used for painting after the line was measured.

Native Office layout is not perfect either, but a small amount of controlled line fitting usually produces a more usable result than an immediate hard wrap.

The main behavior we are interested in is not arbitrary DOM scaling. It is layout-model-level fitting where measurement, rendering, hit testing, selection geometry, caret placement, and pagination all use the same result.

Findings from the 1.45.0 distribution

While inspecting the distributed layout code, we found logic that appears to:

  1. detect an overflowing word or run candidate;
  2. check whether the paragraph alignment is justify;
  3. attempt a compressed fit under a bounded condition;
  4. store the pre-compression width as naturalWidth;
  5. use the available width as the final line width.

In the distributed ESM chunk, the relevant area is around the logic containing variables such as:

const justifyAlignment = block.attrs?.alignment === "justify";
const totalWidthWithWord = /* ... */;
const availableWidth = currentLine.maxWidth - WIDTH_FUDGE_PX;
let shouldBreak = /* ... */;
let compressedWidth = null;

if (shouldBreak && justifyAlignment) {
    // bounded compressed-fit logic
}

Later, the line keeps both values:

if (compressedWidth != null) {
    currentLine.naturalWidth = roundValue(totalWidthWithWord);
}
currentLine.width = roundValue(targetWidth);

This looks like a useful foundation for a more general policy rather than something that should be reimplemented outside the layout engine.

Questions

  1. Is anyone currently working on improved CJK line breaking, kinsoku handling, line balancing, or controlled line compression?

  2. Is there an existing development branch, draft PR, internal roadmap item, or unreleased version that we should evaluate before implementing a downstream patch?

  3. Is the current justified-line compression behavior considered a stable part of the layout design, or is it an implementation detail expected to change?

  4. Would the maintainers be open to a configurable layout policy that allows bounded compression for eligible left-aligned CJK paragraphs?

  5. Is there a preferred extension point for:

    • line-break candidate generation;
    • line-break scoring;
    • script-aware compression limits;
    • physical font resolution;
    • invalidating line measurements after asynchronous font loading?
  6. Does the current kinsoku property affect the new layout engine's actual line-break candidate selection, or is it currently preserved mainly for DOCX import/export compatibility?

  7. Are there known issues where the font used by CanvasRenderingContext2D.measureText() differs from the physical font ultimately used by the renderer?

  8. If we prepare a PR, would the project prefer:

    • a small change extending the existing compression path;
    • a configurable lineBreakPolicy / compressionPolicy;
    • or a broader CJK line-breaking implementation based on Unicode Line Breaking Algorithm rules plus OOXML compatibility?

Possible incremental proposal

Our current idea is to implement this incrementally rather than replacing the line-breaking engine.

Phase 1: font-metric consistency

Ensure that layout measurement and final rendering resolve to the same physical font, including CJK fallback.

Important requirements would include:

  • waiting for, or reacting to, asynchronous font availability;
  • invalidating only affected measurement caches;
  • incrementally reflowing affected paragraphs/pages;
  • avoiding CSS-only font substitution after layout.

Phase 2: bounded CJK compression

Generalize the existing compressed-fit path so that eligible left-aligned CJK body paragraphs may use it under strict limits.

A possible policy could be conceptually similar to:

interface CompressionPolicy {
    enabled: boolean;
    allowForLeftAlignedCjk: boolean;
    preferredMinimumRatio: number;
    absoluteMinimumRatio: number;
    excludeCode: boolean;
    excludeMath: boolean;
    excludeUrls: boolean;
}

Possible initial thresholds:

  • ratio at or above 0.985: generally allow;
  • ratio from 0.97 to 0.985: allow only when it avoids a prohibited punctuation boundary, orphan character, or very short final line;
  • ratio below 0.97: wrap normally.

Here, the ratio is:

available line width / natural candidate width

The exact values would need visual and geometry regression testing; they are not intended as fixed API defaults yet.

Phase 3: CJK break candidates and limited lookahead

Add CJK-aware legal break candidates and a small scoring window rather than implementing full paragraph-wide Knuth–Plass layout.

Potential scoring inputs:

  • remaining line slack;
  • prohibited leading punctuation;
  • prohibited trailing opening punctuation;
  • single-character final lines;
  • very short paragraph-final lines;
  • Latin word splitting;
  • URL/number/unit grouping;
  • compression cost;
  • pagination orphan/widow cost.

A one- or two-line lookahead with a small beam width may provide most of the visual benefit while remaining practical for live editing.

Important compatibility concern

We do not want automatic fitting to silently modify the user's explicit DOCX character formatting.

Ideally, bounded compression would remain an ephemeral layout decision unless it directly corresponds to an existing OOXML compatibility behavior. If the document is exported, the original run formatting should remain unchanged unless the user explicitly applies character spacing or character scaling.

This distinction is important:

  • explicit w:spacing / character formatting belongs to the document;
  • automatic line fitting belongs to the layout result.

Why we are asking before patching

A downstream patch to the generated distribution is possible, but it would be fragile because:

  • chunk filenames are versioned/hashed;
  • layout, rendering, hit testing, and caret mapping are coupled;
  • modifying only the wrap decision may leave geometry inconsistent elsewhere;
  • dependency upgrades would require repeatedly reconstructing the patch.

We would prefer to align with existing architecture and upstream plans, and we are willing to test a branch or help prepare a focused PR if this work is not already underway.

Environment

  • SuperDoc: 1.45.0
  • Integration type: desktop DOCX authoring application
  • Document mode: editing
  • Layout engine: enabled
  • Primary content: Simplified Chinese and mixed Chinese/English
  • Runtime: Chromium/Electron on Windows 11

Expected outcome from this issue

We are primarily looking for guidance on:

  • whether related work already exists;
  • which branch or PR to test;
  • the preferred architectural extension point;
  • and whether a focused upstream contribution would be welcome.

Thank you for any pointers to current work, design discussions, or relevant tests.

Proposed solution

No response

Alternatives considered

No response

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    stage: triageNew issue. Needs initial assessment and classificationtype: featureRequest for new functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions