Skip to content

feat(core): resolve text geometry through the font the renderer paints with - #13

Open
YangtseSu wants to merge 2 commits into
linuxdeepin:mainfrom
YangtseSu:feat/font-text-advances
Open

YangtseSu wants to merge 2 commits into
linuxdeepin:mainfrom
YangtseSu:feat/font-text-advances

Conversation

@YangtseSu

@YangtseSu YangtseSu commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Edited 2026-10-19. The title and this description were rewritten, and the branch
recut from three commits to two. The original description is preserved verbatim at the
end of this body, under "Original description".

Why: upstream main has since taken four of the five requests this fork carried (#11,
#12, #14, #15), so this branch was rebased onto it; and the third commit of the old
branch mixed a bug fix (a highlight cut the bottom off the text it covered) with a
model change (one band for a whole face, plus the selection region's margin), which is
the part a reviewer is least likely to want riding along. The vertical half now lives
on a stacked branch, feat/font-text-band, to be opened as its own PR once this one
lands. What remains here is the horizontal half alone: the layer that reports
rectangles now follows the font the layer that paints already uses.


Text geometry has to describe the glyphs the renderer paints. A scalar of a run that
declares no DeltaX is positioned by that scalar's font advance — that is what
rofd-render's positioner does — but rofd-core advanced it by the font size and
bounded its box by the font size as well. The two layers therefore disagreed on every
document whose producers rely on font metrics instead of explicit deltas:

  • Positions drifted. Measured on a 22-character Latin line at size 3 mm: the
    character boxes spread over 63.0 mm while the drawn ink was 28.2 mm wide, and the
    42 match rectangle sat at 60.0–66.0 mm while the line ends at 43.4 mm — a search
    highlight in blank space, 4.5 mm right of the text it belongs to. CJK hides it: a
    full-width glyph's advance is the font size.
  • Boxes overhung their glyph. right = left + max(|dx|, size) made every box at
    least one font size wide, so a match rectangle extended past its text: on a 3 mm line
    the on match box was 4.82 mm wide against a 3.65 mm advance span, and the
    single-glyph boxes overlapped by 1.25 mm.

Both are fixed here, in two commits:

  • TextAdvanceProvider lets a caller supply the per-scalar advances,
    Document::from_bytes_with_advances / open_with_advances carry one into the page
    text index, and without one the previous font-size footprint stays in place (still
    flagged conservative). Explicit deltas stay authoritative, and the provider is asked
    only when a run actually declares no DeltaX.
  • A scalar whose advance the provider resolved gets exactly that advance as its box
    width, so the boxes of a run tile it and a match rectangle hugs its text.

rofd-render implements the provider over exactly the resolution and advance code the
positioner uses (position_glyph_runs, exposing the advance each positioned glyph
contributed), so the layer that draws and the layer that reports rectangles cannot
drift apart. rofd-ffi loads documents with the default font stack, i.e. the one a
renderer created with NULL options uses; the header documents the caveat for renderers
configured with other fallback families.

The vertical extents are deliberately out of scope here and proposed separately, in a
branch stacked on this one (the fork's feat/font-text-band): a box still stops at the
baseline, which cuts CJK overshoot and Latin descenders off a highlight, and the
follow-up spans the box with the face's ascent and descent. I will open it as its own
PR once this one lands, so this PR's diff stays the horizontal geometry alone.

Verified: cargo fmt --all -- --check; cargo clippy -p rofd-core -p rofd-render -p rofd-ffi --all-targets -- -D warnings; cargo test for the three crates;
crates/rofd-ffi/tests/run_c_tests.sh (the export set is unchanged, C/C++ headers,
smoke). New tests cover the provider seam in the core builder (including
explicit deltas staying authoritative and a provider that cannot resolve a font), the
provider/positioner agreement on an embedded subset font, and the ABI's layout and
search rectangles following font metrics.


Original description

An OFD text run that declares no DeltaX for a scalar is positioned by that scalar's
font advance, which is what the renderer draws, and the glyph is painted at that font's
own extent around the baseline. The semantic text index advanced such scalars by the
font size and sized every character box by it too, so page geometry drifted away from
the glyphs: on a 22-character Latin line at 3 mm the character boxes spread over 63 mm
while the drawn text was 28 mm wide, a search rectangle for a late match fell to the
right of the text it belonged to, and a selection highlight cut the bottom off the text
it covered — on a real document, 86% of 2464 glyphs reach below their baseline (CJK
overshoot 0.10–0.13 em, Latin descenders up to 0.19 em, mean 0.50 mm and up to 1.46 mm).
Consumers that draw from this geometry — search highlights, selection, caret,
hit-testing — looked misplaced or clipped on any document whose producers rely on font
metrics.

The fix follows the model a PDF reader uses, so highlights also line up with the way
Papers' PDF backend behaves:

  • TextMetricsProvider lets a caller supply the per-scalar geometry as
    TextScalarMetrics { advance_mm, ascent_mm, descent_mm };
    Document::from_bytes_with_metrics / open_with_metrics carry one into the page text
    index. Without a provider nothing changes, and a scalar whose glyph no face owns keeps
    the font-size footprint.
  • The builder sizes a resolved box by the glyph's advance (so boxes tile a run and a
    match rectangle hugs its text) and spans it with the face's typographic ascent and
    descent, which every glyph of that face shares — the same pair poppler's
    TextWord::setInitialBounds uses (yMin = y - ascent, yMax = y - descent). Boxes of
    Latin, CJK and punctuation therefore keep one band instead of hugging their own ink.
  • TextSelection::regions grows that band by an eighth of its height above and below,
    the margin poppler's TextSelectionSizer::visitLine gives a PDF selection region, so a
    selected line reads as one band that clears the glyphs. Character boxes and match
    rectangles keep the plain band, as poppler keeps them.
  • rofd-render implements the provider over exactly the resolution and metrics code the
    positioner uses: the face's ascender/descender are read once while the face is
    validated, each glyph's advance is cached beside them, and PositionedGlyph::metrics
    exposes the advance and the face extents so the two layers cannot drift apart.
  • rofd-ffi loads documents with the default font stack, i.e. the one a renderer created
    with NULL options uses, and the provider owns that shared resolver instead of leaking
    it. A renderer configured with other fallback families may still draw different glyphs
    for runs without deltas; the header documents this.

Verified: cargo fmt --all -- --check; cargo clippy -p rofd-core -p rofd-render -p rofd-ffi --all-targets -- -D warnings; cargo test for the three crates;
crates/rofd-ffi/tests/run_c_tests.sh (symbol list unchanged, 77 entries, C/C++
headers, smoke). New tests cover the provider seam in the core builder (explicit deltas
stay authoritative; a provider that cannot resolve a font leaves the footprint; the
box spans the resolved extents), the selection region's eighth-height margin on an
aligned band, the provider/positioner agreement on an embedded subset font (advance plus
the face's 1160/288 metrics for every glyph), and the ABI's layout and search
rectangles. Two pixel-level tests fail against the previous model and pass with this
one: crates/rofd-render/tests/cairo_text.rs::semantic_character_boxes_cover_the_rendered_ink
(the two scalars of one run share top and bottom, and the band is strictly taller than
the ink) and
crates/rofd-ffi/tests/semantic.rs::text_layout_rectangles_cover_the_rendered_ink_below_the_baseline
(same band for A and 中, the region equals the band plus the margin).

Note: crates/rofd-render/tests/images.rs::decoded_limits_have_exact_and_one_over_boundaries
fails on main here as well; it is unrelated to this change.

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: YangtseSu

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot

Copy link
Copy Markdown

Hi @YangtseSu. Thanks for your PR.

I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@sourcery-ai

sourcery-ai Bot commented Oct 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes semantic text geometry drift by allowing core to consume optional per-scalar font advances, implementing that provider through the renderer’s exact glyph-positioning path, and wiring the default provider into the FFI so layout, search, selection, and hit-testing align with drawn text while retaining conservative font-size behavior when metrics are unavailable.

Sequence diagram for font-resolved semantic text layout

sequenceDiagram
    participant FFI as rofd-ffi
    participant Document as rofd-core Document
    participant Page as rofd-core Page
    participant Provider as FontTextAdvances
    participant Positioner as position_glyph_runs
    participant Layout as PageText
    participant Renderer as Renderer

    FFI->>Document: open_with_advances(path, options, provider)
    FFI->>Renderer: render text
    Page->>Document: text()
    Document->>Provider: advances_mm(page, text, size_mm)
    Provider->>Positioner: position_glyph_runs(resolver, resource, text, limits)
    Positioner-->>Provider: PositionedGlyph.advance()
    Provider-->>Document: per-scalar advances
    Document->>Layout: build_page_text(page, advances)
    Layout-->>FFI: geometry for search, selection, and hit-testing
    Renderer-->>FFI: drawn glyph positions
Loading

Flow diagram for fallback text advance selection

flowchart TD
    Start[Text scalar in a run] --> Explicit{Explicit DeltaX?}
    Explicit -->|Yes| Delta[Use explicit delta]
    Explicit -->|No| Provider{Advance provider available?}
    Provider -->|Yes| Resolved{Valid per-scalar advance?}
    Resolved -->|Yes| Font[Use font advance]
    Resolved -->|No| Size[Use font-size footprint]
    Provider -->|No| Size
    Delta --> Geometry[Update semantic text geometry]
    Font --> Geometry
    Size --> Geometry
Loading

File-Level Changes

Change Details Files
Adds an optional font-advance provider to semantic text geometry while preserving the existing font-free fallback behavior.
  • Introduces the public TextAdvanceProvider seam and stores providers on documents.
  • Adds open_with_advances and from_bytes_with_advances; pages pass the provider into semantic text construction.
  • Uses resolved per-scalar advances only when DeltaX is absent, retaining explicit deltas and falling back to font-size footprints on resolution failure.
  • Adds core tests for provider advances, explicit-delta precedence, and fallback behavior.
crates/rofd-core/src/document.rs
crates/rofd-core/src/lib.rs
crates/rofd-core/src/semantic.rs
crates/rofd-core/tests/semantic_text.rs
Aligns renderer-provided semantic advances with the exact font resolution and glyph positioning logic used for drawing.
  • Exposes each PositionedGlyph advance in millimetres.
  • Implements FontTextAdvances by resolving and summing positioned glyph advances per scalar, including mapped glyph runs and fallback behavior.
  • Adds tests verifying provider advances match positioned glyph advances and reflect proportional font metrics.
crates/rofd-render/src/fonts.rs
crates/rofd-render/src/lib.rs
crates/rofd-render/tests/fonts.rs
Makes the C/FFI semantic layout use the default renderer font stack so layout and search geometry track rendered glyphs.
  • Loads documents with a process-wide cached default-font advance provider.
  • Documents the default-stack behavior and possible differences for custom renderer fallback families in the public header.
  • Adds ABI tests covering layout positions, conservative box widths, and search rectangles.
crates/rofd-ffi/src/document.rs
crates/rofd-ffi/src/renderer.rs
crates/rofd-ffi/include/rofd.h
crates/rofd-ffi/tests/semantic.rs
Updates the semantic API design specification to describe font-derived advances and conservative fallback semantics.
  • Documents provider injection, renderer/core alignment, default FFI font resolution, and unresolved-scalar fallback behavior.
docs/superpowers/specs/2026-09-09-poppler-style-c-semantic-api-design.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="crates/rofd-render/src/fonts.rs" line_range="1269-1270" />
<code_context>
+impl TextAdvanceProvider for FontTextAdvances<'_> {
+    fn advances_mm(&self, page: &Page, text: &TextObject, size_mm: f64) -> Option<Vec<f64>> {
+        let resource = page.font_resource(text.font_id()).ok()?;
+        let runs =
+            position_glyph_runs(self.resolver, &resource, text, page.resource_limits()).ok()?;
+        let total = text.runs().iter().try_fold(0usize, |total, run| {
+            total.checked_add(run.text().chars().count())
</code_context>
<issue_to_address>
**Font errors become silent fallback**

When font lookup or glyph positioning fails for a reason other than an unavailable font, `position_glyph_runs` errors are converted to `None`, which makes the core builder use font-size steps without exposing why resolution failed. Invalid embedded fonts, resource-limit failures, or cache errors thus produce silently inaccurate geometry instead of a diagnosable failure.

Preserve or report provider failures separately from the ordinary unresolved-font fallback so callers can distinguish degraded geometry from successful font resolution.
</issue_to_address>

### Comment 2
<location path="crates/rofd-core/src/semantic.rs" line_range="560" />
<code_context>
+        // authoritative and are never replaced.
+        let resolved = self
+            .advances
+            .and_then(|advances| advances.advances_mm(self.page, object, size));
+        let mut scalar = 0usize;
         for run in object.runs() {
</code_context>
<issue_to_address>
**Explicit advances still load fonts**

When semantic text is requested for objects whose runs all declare explicit `DeltaX`, `text_object` calls the provider before checking whether any run needs inferred advances, so the provider resolves fonts and positions glyphs even though explicit `DeltaX` values are authoritative. Semantic text construction does unnecessary font-loading and per-glyph work.

Check whether any run needs inferred advances before calling the provider.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread crates/rofd-render/src/fonts.rs
Comment thread crates/rofd-core/src/semantic.rs Outdated
archlinux-github pushed a commit to archlinux/aur that referenced this pull request Oct 14, 2026
Carry the font-advance fixes from YangtseSu/rofd feat/font-text-advances
(upstream linuxdeepin/rofd#13), on top of the page-size query of #11:

- rofd-core resolves the advance of a scalar whose run declares no DeltaX
  through an injected provider, so page geometry stops drifting from the
  rendered glyphs (a Ctrl+F highlight used to land right of its match);
- a resolved glyph box is sized by its advance, so boxes tile a run and a
  rectangle hugs the matched text;
- rofd-render implements the provider over the positioner's own font
  resolution, and rofd-ffi loads documents with the default font stack.

Both patches apply to the pinned upstream commit (140b936) and the package
builds fully offline. Verified: the packaged library exports
rofd_document_get_page_size_mm and gives a two-scalar "on" match a 3.65 mm
rectangle where the size-based footprint produced 4.82 mm.
@YangtseSu YangtseSu changed the title feat(core): resolve missing text deltas through font advances feat(core): resolve text geometry through the font the renderer paints with Oct 15, 2026
@YangtseSu
YangtseSu force-pushed the feat/font-text-advances branch from 7f6a185 to a364014 Compare October 15, 2026 03:04
@YangtseSu

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA.

An OFD text run that declares no `DeltaX` for a scalar is positioned by that
scalar's font advance, which is what the renderer draws. The semantic text
index, however, advanced such scalars by the font size, so page geometry
drifted away from the glyphs: on a 22-character Latin line at size 3 mm the
character boxes spread over 63 mm while the drawn text was 28 mm wide, and a
search rectangle for a match late in the line fell to the right of the text it
belonged to. Consumers that draw from this geometry - search highlights,
selection, caret and hit-testing - therefore looked misplaced on any document
whose producers rely on font metrics instead of explicit deltas.

The core crate still does not touch fonts. `TextAdvanceProvider` lets a caller
supply the per-scalar advances, `Document::from_bytes_with_advances` /
`open_with_advances` carry a provider into the page text index, and without one
the previous font-size footprint stays in place (and stays flagged
conservative). rofd-render implements the provider over exactly the resolution
and advance code the positioner uses, exposing the advance each positioned glyph
contributed so the two layers cannot drift apart. rofd-ffi loads documents with
the default font stack, which is what a renderer created with NULL options uses.

The provider is asked only when a run actually declares no `DeltaX`: an
object whose runs all carry their own deltas never loads a font for its
geometry.

Verified: cargo fmt --all -- --check; cargo clippy -p rofd-core -p rofd-render
-p rofd-ffi --all-targets -- -D warnings; cargo test for the three crates;
crates/rofd-ffi/tests/run_c_tests.sh (symbol list, C/C++ headers, smoke). New
tests cover the provider seam in the core builder (including explicit deltas
staying authoritative and a provider that cannot resolve a font), the
provider/positioner agreement on an embedded subset font, and the ABI's layout
and search rectangles following font metrics.
The conservative footprint (at least one font size per scalar) exists so a
font-free build still produces usable hit boxes. With a provider that footprint
is no longer needed: it made every box wider than its glyph, so the boxes of a
run overlapped and a match rectangle overhung its text by a fraction of a glyph -
visible as a search highlight that extends past the match into the next letter.
Measured on a 3 mm Latin line: the "on" match box was 4.82 mm wide against a
3.65 mm advance span, and the single-glyph boxes overlapped by 1.25 mm.

A scalar whose advance the provider resolved now gets exactly that advance as its
box width, so the boxes tile the run and the rectangles hug the matched text.
Scalars the provider cannot resolve, and builds without a provider, keep the
font-size footprint (an explicit zero advance still gets a nonempty box).

Verified: cargo fmt --all -- --check; cargo clippy -p rofd-core -p rofd-render
-p rofd-ffi --all-targets -- -D warnings; cargo test for the three crates (the
core and FFI tests now assert the advance width and that adjacent boxes meet);
crates/rofd-ffi/tests/run_c_tests.sh.
@YangtseSu
YangtseSu force-pushed the feat/font-text-advances branch from a364014 to f0572f2 Compare October 19, 2026 08:29
@YangtseSu YangtseSu changed the title feat(core): resolve text geometry through the font the renderer paints with fix(core): resolve text geometry through the font the renderer paints with Oct 19, 2026
@YangtseSu YangtseSu changed the title fix(core): resolve text geometry through the font the renderer paints with feat(core): resolve text geometry through the font the renderer paints with Oct 19, 2026
@YangtseSu

Copy link
Copy Markdown
Contributor Author

Status as of 2026-10-19 — what this PR is now, and what is deliberately not in it.

The branch is recut onto main and holds the horizontal half only, in two commits.

  • 8db94f9 resolve missing text deltas through font advances — a scalar of a run that
    declares no DeltaX is placed by that scalar's font advance, through an injected
    TextAdvanceProvider, because that is what the positioner paints with. Without a provider
    nothing changes; explicit deltas stay authoritative; the provider is asked only when a run
    actually declares no DeltaX (which answers the second review comment below).
  • f0572f2 size a resolved glyph box by its advance, not the font size — the box is exactly
    the advance wide, so the boxes of a run tile it and a match rectangle hugs its text: the on
    match box goes from 4.82 mm to 3.65 mm on a 3 mm line.

The vertical half moved out, to a stacked branch. Spanning a box with the face's
typographic ascent and descent — and growing a selection region by an eighth of its height — is
a different kind of change: a model, not a correction, even though it fixes a real bug (a
highlight cutting the bottom off CJK overshoot and Latin descenders). It lives on
YangtseSu/rofd:feat/font-text-band, two commits on top of this branch, with the margin commit
kept separate so it can be dropped on its own. A PR cannot be based on a fork's branch, so I
will open it here against main once this one lands; if you would rather review both at once,
say so and I will retarget it to main now and mark it as stacked.

Review comments, answered in their threads.

  • "Explicit advances still load fonts" — fixed in the first commit; the builder asks the
    provider only when some run declares no DeltaX.
  • "Font errors become silent fallback" — kept as the contract, with the reasoning in the
    thread: None means "no glyph resolved for this scalar", the font-size footprint is exactly
    what a build without a provider produces, and geometry deliberately has no failure mode, so
    text(), find() and select() keep working on any document. If you want the two cases
    distinguishable, the trait can carry a reason — happy to add it.

Also: the four other changes this fork carried are in main now — #11 16eaa8d, #14
bef8a3e, #15 f6fb312, #12 2f5013d — thank you. The fork's dev was rebased onto
2f5013d and no longer carries them, and its CI no longer skips or tolerates the two tests
those fixes made green.

Verified on the branch tip: cargo fmt --all -- --check; cargo clippy -p rofd-core -p rofd-render -p rofd-ffi --all-targets -- -D warnings; cargo test for the three crates
(49 test binaries, 0 failures); crates/rofd-ffi/tests/run_c_tests.sh (the export set is
unchanged — 78 symbols in the tree now that the page-size query is in main, and
expected-symbols.txt is not touched by this branch); the fork's CI on dev, which carries
both halves, is green. rofd.h documents the one caveat: a renderer configured with fallback
families other than the defaults may draw glyphs the semantic layer did not resolve.

Still waiting on /ok-to-test from a maintainer.

@YangtseSu

Copy link
Copy Markdown
Contributor Author

我把#13重构了下,目前的代码只修两个水平问题——缺 DeltaX 的标量按字体 advance 定位(高亮不再右移、落到文字右侧)与已解析字符框的宽度取该 advance(匹配框不再比文字宽、相邻框不再重叠)。我vibe的 papers-ofd,需要这个来修复选中的高亮向右偏移的问题。

而「下面没覆盖全、上下各露多少」的那部分(按字面 ascent/descent 的行带 + 选择区 1/8 margin)已拆到叠在它之上的 feat/font-text-band,等 #13 落地后另开 PR。之所以这样操作,是看到别的PR都合并了,这个没有合并。虽然没有收到回复,但是我自己在我电脑上用deepin-reader(https://aur.archlinux.org/packages/deepin-reader)测试了,deepin-reader对于pdf和ofd的行为基本一致,都没有在文字上下多高亮一点区域。而GNOME Papers对于pdf的处理,是在文字上下多高亮一点区域,所以我目前的feat/font-text-band和原先的#13 PR,都是这样的。
如果这个能合并(也希望这个能合并,实测对deepin-reader没有影响),下一步:
一是,如果你们能接受类似GNOME Papers的选中效果,我直接将feat/font-text-band开新PR(或者在这里补充).目前的显示方式,我个人认为不是很美观,单个字一个高亮,中文、英文、数字、标点参差不齐。
二是,不接受这样的选中效果,要和deepin-reader对pdf的处理保持一致。我下一步提交“下面没覆盖全”的修复。这个是显示的真bug。我自己维护“上下各露多少”的patch。
希望能收到你们确切的回复。感谢。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants