fix: justified lines fit the real display, not an idealized one
Two root causes made justified text overrun the window's right edge in real user sessions (every line ending in the "$" truncation marker), while emacs -Q looked fine: 1. Windows WITHOUT fringes draw the truncation/continuation indicator in the text area's LAST COLUMN, so the usable width is one character less than window-body-width. ekp reserved only a 2px margin: with fringes disabled (a common minimal setup, and all ttys) every line that hit the target width exactly had its final glyph displaced by the "$". ekp-region--window-pixel now reserves one frame-char-width when the window has no right fringe. (The same lesson ebox-playground encodes in its viewport-width reserve.) 2. Measurement was blind to the buffer's display context: string-pixel-width works in a bare hidden buffer, ignoring face-remapping-alist — which is where text-scale-mode, themes and per-buffer font tweaks live. Under a remap, the DP laid lines out with one font's metrics and the display rendered them with another's: scale +3 made 5 of 7 sample lines overflow a 1330px window by up to 900px (GUI-measured). Measurement now runs with the destination buffer's face-remapping-alist (the 29/30-compatible equivalent of Emacs 31's string-pixel-width BUFFER argument), and the width/paragraph caches key on that context so buffers at different scales never alias. text-scale changes also trigger a re-flow in ekp-auto-justify-mode. Ground truth, measured with window-text-pixel-size in GUI Emacs across 7 display contexts (plain / text-scale ±| face remap / no-fringes / no-fringes+scale / narrow+scale): the widest justified line equals the target width exactly in every case, zero lines overflow. Verbatim code blocks are exempt by design (they never reflow, like any code line in a narrow window). New tooling so this never regresses invisibly: - M-x ekp-diagnose: renders a probe line in YOUR buffer and reports target vs rendered width — run it in any session where justified text looks wrong. - tests/ekp-gui-verify.el: M-x ekp-gui-verify (single check in a customized session) and ekp-gui-verify-matrix (the 7-case table, for emacs -Q). - 2 new batch ERT tests pin the context-keyed caches (94 total). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
448198bdb9
commit
29cef97ef8
@ -166,13 +166,27 @@ Non-zero only while `ekp-protrusion' is enabled: protruding glyphs
|
||||
extend past the flush edge, so the layout width must leave room."
|
||||
(if ekp-protrusion
|
||||
(max 2 (ceiling (* (alist-get 'cjk-close ekp-protrusion-ratios 0.5)
|
||||
(string-pixel-width "。"))))
|
||||
(ekp--measured-width "。"))))
|
||||
0))
|
||||
|
||||
(defun ekp-region--indicator-reserve (&optional window)
|
||||
"Pixels the truncation/continuation indicator eats in WINDOW.
|
||||
With no right fringe (and on text terminals), Emacs draws the `$'
|
||||
or `\\' indicator in the text area's LAST COLUMN — a line that
|
||||
fills the body width exactly gets its final glyph displaced and
|
||||
every justified line appears truncated. Reserve that column; with
|
||||
a right fringe the indicator lives in the fringe and costs nothing."
|
||||
(let ((win (or window (selected-window))))
|
||||
(if (and (display-graphic-p (window-frame win))
|
||||
(> (or (cadr (window-fringes win)) 0) 0))
|
||||
0
|
||||
(frame-char-width (window-frame win)))))
|
||||
|
||||
(defun ekp-region--window-pixel (&optional window)
|
||||
"Usable text width in pixels of WINDOW (default: selected window)."
|
||||
(max 1 (- (window-body-width window t)
|
||||
ekp-region-margin-pixel
|
||||
(ekp-region--indicator-reserve window)
|
||||
(ekp-region-protrusion-reserve))))
|
||||
|
||||
(defun ekp-region--effective-width (&optional buffer)
|
||||
@ -185,6 +199,43 @@ Falls back to the selected window when the buffer is not displayed."
|
||||
(apply #'min (mapcar #'ekp-region--window-pixel wins))
|
||||
(ekp-region--window-pixel))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ekp-diagnose ()
|
||||
"Check that ekp's measurement matches this window's real rendering.
|
||||
Justifies a probe line to the window width, renders it invisibly in
|
||||
this buffer, and compares the rendered pixel width against the
|
||||
target. A mismatch means glyph metrics differ between measurement
|
||||
and display (e.g. a face-remapping ekp does not see) and justified
|
||||
lines would come out over- or under-full."
|
||||
(interactive)
|
||||
(let* ((win (or (get-buffer-window (current-buffer)) (selected-window)))
|
||||
(target (ekp-region--window-pixel win))
|
||||
(probe (ekp-pixel-justify
|
||||
(concat "汉字排版像素精确性探针,中英混排 probe line with "
|
||||
"Latin words, 标点。悬挂?以及 hyphenation-ready "
|
||||
"vocabulary examples 结尾。")
|
||||
target))
|
||||
(line (car (split-string probe "\n")))
|
||||
(rendered
|
||||
(with-silent-modifications
|
||||
(let ((beg (point-max)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(goto-char beg)
|
||||
(insert "\n" line)
|
||||
(car (window-text-pixel-size win (1+ beg) (point-max))))
|
||||
(delete-region beg (point-max))))))
|
||||
(delta (- rendered target)))
|
||||
(message (concat "ekp-diagnose: target %dpx, rendered %dpx (Δ%+d) — %s"
|
||||
(if (buffer-local-value 'face-remapping-alist
|
||||
(current-buffer))
|
||||
" [buffer has face remappings]" ""))
|
||||
target rendered delta
|
||||
(if (<= (abs delta) ekp-region-margin-pixel)
|
||||
"OK, measurement matches rendering"
|
||||
"MISMATCH: justified lines will not fit this window"))
|
||||
delta))
|
||||
|
||||
;;;; Pure string transforms
|
||||
|
||||
(defun ekp-region--split-hard (string)
|
||||
@ -703,6 +754,19 @@ Catches the buffer becoming displayed (possibly for the first time),
|
||||
window splits, and deletions of the narrowest window."
|
||||
(ekp-region--schedule-reflow))
|
||||
|
||||
(defun ekp-region--on-text-scale ()
|
||||
"Re-flow after a text-scale change.
|
||||
Scaling remaps the default face, so every glyph metric changed: the
|
||||
current layout is wrong at the same pixel width and must be re-done
|
||||
under the new measurement context."
|
||||
(when (and ekp-auto-justify-mode ekp-region--auto-width)
|
||||
(when (timerp ekp-region--resize-timer)
|
||||
(cancel-timer ekp-region--resize-timer))
|
||||
(setq ekp-region--resize-timer
|
||||
(run-with-timer ekp-auto-justify-resize-delay nil
|
||||
#'ekp-region--reflow
|
||||
(current-buffer) ekp-region--auto-width))))
|
||||
|
||||
(defun ekp-region--schedule-reflow ()
|
||||
"Debounce a re-flow of the current buffer to its effective width."
|
||||
(when ekp-auto-justify-mode
|
||||
@ -855,6 +919,7 @@ the buffer text is restored exactly when the mode is turned off."
|
||||
(add-hook 'window-configuration-change-hook
|
||||
#'ekp-region--on-window-change nil t)
|
||||
(add-hook 'window-scroll-functions #'ekp-region--on-scroll nil t)
|
||||
(add-hook 'text-scale-mode-hook #'ekp-region--on-text-scale nil t)
|
||||
(add-hook 'after-change-functions #'ekp-region--after-change nil t)
|
||||
(ekp-region--install-integrations)
|
||||
;; Turning the major mode off/over kills local hooks silently;
|
||||
@ -864,6 +929,7 @@ the buffer text is restored exactly when the mode is turned off."
|
||||
(remove-hook 'window-configuration-change-hook
|
||||
#'ekp-region--on-window-change t)
|
||||
(remove-hook 'window-scroll-functions #'ekp-region--on-scroll t)
|
||||
(remove-hook 'text-scale-mode-hook #'ekp-region--on-text-scale t)
|
||||
(remove-hook 'after-change-functions #'ekp-region--after-change t)
|
||||
(remove-hook 'change-major-mode-hook #'ekp-region--teardown t)
|
||||
(ekp-region--teardown)
|
||||
|
||||
@ -114,6 +114,8 @@ family when STRING contains no Latin letter."
|
||||
;; no latin letter in string, use default
|
||||
(face-attribute 'default :family)))
|
||||
|
||||
(declare-function ekp--measured-width "ekp")
|
||||
|
||||
(defun ekp-word-spacing-pixel (string)
|
||||
"Return the pixel width of an inter-word space for STRING.
|
||||
Use the blank glyph of STRING's Latin font; for a monospace font
|
||||
@ -121,11 +123,11 @@ that width is the space's own advance."
|
||||
;; font is monospace, use the pixel of blank
|
||||
;; as word spacing pixel
|
||||
(if-let ((font-family (ekp-monospace-p string)))
|
||||
(string-pixel-width
|
||||
(ekp--measured-width
|
||||
(propertize " " 'face `(:family ,font-family)))
|
||||
(let* ((letter (ekp-get-latin-letter string))
|
||||
(font-family (ekp-font-family letter)))
|
||||
(string-pixel-width
|
||||
(ekp--measured-width
|
||||
(propertize
|
||||
" " 'face `(:family ,font-family))))))
|
||||
|
||||
|
||||
52
ekp.el
52
ekp.el
@ -249,7 +249,7 @@ when non-zero the C module is bypassed automatically."
|
||||
"Cache: equal-keyed table, content key → ekp-para struct.")
|
||||
|
||||
(defvar ekp--last-para nil
|
||||
"Fast path: (string-object lang para) of the most recent lookup.
|
||||
"Fast path: (string-object lang width-context para), most recent lookup.
|
||||
One justification call resolves the same string object many times;
|
||||
this avoids recomputing the full cache key each time. Invalidated
|
||||
by parameter changes, language changes, style-variable changes (see
|
||||
@ -671,16 +671,48 @@ before.")
|
||||
(defvar ekp--box-width-cache-limit 65536
|
||||
"Entry cap for `ekp--box-width-cache'; the cache is flushed beyond it.")
|
||||
|
||||
(defun ekp--string-pixel-width (string)
|
||||
"Pixel width of STRING as it will render in the current buffer.
|
||||
Like `string-pixel-width', but honors the current buffer's
|
||||
`face-remapping-alist' — which is where `text-scale-mode', themes
|
||||
and mode-specific font tweaks live. Plain `string-pixel-width'
|
||||
measures in a bare hidden buffer, so in any buffer with remapped
|
||||
faces it reports the wrong font's metrics and every \"pixel-exact\"
|
||||
line comes out wrong on screen (Emacs 31 grew a BUFFER argument for
|
||||
exactly this; this is the 29/30-compatible equivalent)."
|
||||
(if (null face-remapping-alist)
|
||||
(string-pixel-width string)
|
||||
(let ((remap face-remapping-alist))
|
||||
(with-current-buffer (get-buffer-create " *ekp-pixel-width*" t)
|
||||
(setq-local face-remapping-alist remap)
|
||||
(delete-region (point-min) (point-max))
|
||||
;; Keep line-affecting context out, like string-pixel-width.
|
||||
(setq-local line-prefix nil wrap-prefix nil)
|
||||
(insert string)
|
||||
(prog1 (car (buffer-text-pixel-size nil nil t))
|
||||
(delete-region (point-min) (point-max)))))))
|
||||
|
||||
(defun ekp--width-context ()
|
||||
"The display context that box measurement depends on.
|
||||
nil in an unremapped buffer (the common case); otherwise the
|
||||
buffer's `face-remapping-alist', which changes glyph metrics and
|
||||
therefore must key every measurement and paragraph cache entry."
|
||||
face-remapping-alist)
|
||||
|
||||
(defun ekp--measured-width (str)
|
||||
"`string-pixel-width' of STR, through the global width cache."
|
||||
"Pixel width of STR in the current display context, cached."
|
||||
(let* ((ivs (ekp--key-intervals str))
|
||||
(key (if ivs (cons str ivs) str)))
|
||||
(ctx (ekp--width-context))
|
||||
(key (cond ((and (null ivs) (null ctx)) str)
|
||||
((null ctx) (cons str ivs))
|
||||
(t (list str ivs ctx)))))
|
||||
(or (gethash key ekp--box-width-cache)
|
||||
(progn
|
||||
(when (>= (hash-table-count ekp--box-width-cache)
|
||||
ekp--box-width-cache-limit)
|
||||
(clrhash ekp--box-width-cache))
|
||||
(puthash key (string-pixel-width str) ekp--box-width-cache)))))
|
||||
(puthash key (ekp--string-pixel-width str)
|
||||
ekp--box-width-cache)))))
|
||||
|
||||
(defun ekp--para-key (string)
|
||||
"Compute cache key for STRING.
|
||||
@ -694,6 +726,10 @@ are derived per string)."
|
||||
(list string
|
||||
(prin1-to-string (ekp--key-intervals string))
|
||||
latin-font cjk-font
|
||||
;; Buffers with remapped faces (text-scale, themes) render
|
||||
;; — and therefore measure — differently: never alias their
|
||||
;; paragraphs with an unremapped buffer's.
|
||||
(ekp--width-context)
|
||||
ekp-latin-lang
|
||||
ekp-alignment
|
||||
ekp-ragged-stretch-pixel
|
||||
@ -970,8 +1006,9 @@ Computes ALL data in one pass: text, params, and prefix arrays."
|
||||
This is the main entry point for cached paragraph data."
|
||||
(if (and ekp--last-para
|
||||
(eq (car ekp--last-para) string)
|
||||
(equal (nth 1 ekp--last-para) ekp-latin-lang))
|
||||
(nth 2 ekp--last-para)
|
||||
(equal (nth 1 ekp--last-para) ekp-latin-lang)
|
||||
(equal (nth 2 ekp--last-para) (ekp--width-context)))
|
||||
(nth 3 ekp--last-para)
|
||||
(unless ekp--para-cache
|
||||
(setq ekp--para-cache (make-hash-table :test 'equal :size 100)))
|
||||
(let* ((key (ekp--para-key string))
|
||||
@ -986,7 +1023,8 @@ This is the main entry point for cached paragraph data."
|
||||
(let ((p (ekp--make-para string)))
|
||||
(puthash key p ekp--para-cache)
|
||||
p)))))
|
||||
(setq ekp--last-para (list string ekp-latin-lang para))
|
||||
(setq ekp--last-para
|
||||
(list string ekp-latin-lang (ekp--width-context) para))
|
||||
para)))
|
||||
|
||||
;;;###autoload
|
||||
|
||||
10
readme.md
10
readme.md
@ -262,9 +262,13 @@ the point.)
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- Widths are computed from the string's own text properties. If the
|
||||
destination buffer remaps faces (different `:height`, themes), widths
|
||||
may differ; justify with the same properties you will display.
|
||||
- Measurement follows the current buffer's face remappings
|
||||
(`text-scale-mode`, themes, `ekp-org-setup`-style tweaks) and
|
||||
reserves the truncation-indicator column in windows without
|
||||
fringes, so justified lines fit the real display. If lines ever
|
||||
look truncated or short in an exotic setup, run `M-x ekp-diagnose`
|
||||
in that buffer — it reports whether measurement matches rendering
|
||||
(and `M-x ekp-gui-verify` runs a full fit check).
|
||||
- One font is assumed per Latin/CJK script per paragraph when computing
|
||||
spacing defaults; mixed-font paragraphs work but spacing defaults come
|
||||
from the first font found.
|
||||
|
||||
@ -224,8 +224,11 @@ Silicon 测得;方法见 DEVELOPER_ZH.md:
|
||||
|
||||
## 已知限制
|
||||
|
||||
- 宽度按字符串自身的文本属性测量。若目标 buffer 重映射了 face(不同
|
||||
`:height`、主题),宽度可能有偏差;请用与显示时相同的属性做排版。
|
||||
- 测量会跟随当前 buffer 的 face 重映射(`text-scale-mode`、主题等),
|
||||
并在无 fringe 的窗口里为截断指示符预留一列,排版行贴合真实显示。
|
||||
若在特殊配置下仍出现截断或偏短,在该 buffer 里执行
|
||||
`M-x ekp-diagnose`——它会报告测量与渲染是否一致
|
||||
(`M-x ekp-gui-verify` 可跑完整贴合检查)。
|
||||
- 计算默认间距时假定每段落的拉丁/CJK 各使用一种字体;混合字体段落可以
|
||||
工作,但默认间距取自找到的第一个字体。
|
||||
- `ekp-pixel-range-justify` 用三分搜索加局部扫描最小化平均 demerits;
|
||||
|
||||
195
tests/ekp-gui-verify.el
Normal file
195
tests/ekp-gui-verify.el
Normal file
@ -0,0 +1,195 @@
|
||||
;;; ekp-gui-verify.el --- GUI pixel-fit verification for ekp -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2024-2026 Kinney Zhang
|
||||
|
||||
;; Author: Kinney Zhang <kinneyzhang666@gmail.com>
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Ground-truth verification that justified text really fits the
|
||||
;; window, measured with `window-text-pixel-size' on the live display
|
||||
;; — the one thing batch tests cannot check.
|
||||
;;
|
||||
;; Two ways to run:
|
||||
;;
|
||||
;; M-x ekp-gui-verify in ANY running GUI Emacs — including your
|
||||
;; fully customized session. Use this when
|
||||
;; justified text looks truncated: it tells
|
||||
;; you whether measurement matches rendering
|
||||
;; under your fonts/remappings.
|
||||
;;
|
||||
;; emacs -Q -L . -L tests -l tests/ekp-gui-verify.el \
|
||||
;; -f ekp-gui-verify-matrix
|
||||
;; runs the full matrix (plain, text-scale
|
||||
;; up/down, face remap, narrow+scale) and
|
||||
;; prints a PASS/FAIL table.
|
||||
;;
|
||||
;; Criterion: every justified line's rendered width equals the target
|
||||
;; width (± `ekp-region-margin-pixel'). Verbatim paragraphs
|
||||
;; (`ekp-verbatim') are exempt — code blocks pass through unwrapped by
|
||||
;; design and may exceed a narrow window, like any code line.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ekp)
|
||||
(require 'ekp-region)
|
||||
(require 'ekp-showcase)
|
||||
|
||||
(defun ekp-gui-verify--scan (buffer)
|
||||
"Measure every line of BUFFER in its window; return a result plist."
|
||||
(with-current-buffer buffer
|
||||
(let* ((win (get-buffer-window buffer))
|
||||
(body (window-body-width win t))
|
||||
(target ekp-region--auto-width)
|
||||
(worst 0) (over 0) (n 0) (exempt 0))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(let* ((bol (line-beginning-position))
|
||||
(eol (line-end-position))
|
||||
(px (if (= bol eol) 0
|
||||
(car (window-text-pixel-size win bol eol t)))))
|
||||
(when (> px 0)
|
||||
(if (text-property-not-all bol eol 'ekp-verbatim nil)
|
||||
(setq exempt (1+ exempt))
|
||||
(setq n (1+ n))
|
||||
(when (> px worst) (setq worst px))
|
||||
(when (> px body) (setq over (1+ over))))))
|
||||
(forward-line 1)))
|
||||
(list :body body :target target :widest worst :over over
|
||||
:lines n :exempt exempt
|
||||
:pass (and (= over 0)
|
||||
(<= (abs (- worst target))
|
||||
(max 2 ekp-region-margin-pixel)))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ekp-gui-verify ()
|
||||
"Verify pixel-exact justification against this session's display.
|
||||
Opens the ekp showcase, enables follow-window justification, and
|
||||
checks with `window-text-pixel-size' that every justified line
|
||||
renders at exactly the window's text width — under YOUR fonts,
|
||||
themes, remappings and text-scale. Reports PASS or FAIL."
|
||||
(interactive)
|
||||
(unless (display-graphic-p)
|
||||
(user-error "GUI verification needs a graphical frame"))
|
||||
(ekp-showcase)
|
||||
(redisplay t)
|
||||
(with-current-buffer "*ekp-showcase*"
|
||||
(ekp-auto-justify-mode 1)
|
||||
(when (timerp ekp-region--resize-timer)
|
||||
(cancel-timer ekp-region--resize-timer))
|
||||
(ekp-region--reflow (current-buffer) (ekp-region--effective-width))
|
||||
(redisplay t)
|
||||
(let* ((r (ekp-gui-verify--scan (current-buffer)))
|
||||
(msg (format
|
||||
"ekp-gui-verify: %s — %d lines, widest %dpx vs target %dpx (window %dpx)%s"
|
||||
(if (plist-get r :pass) "PASS" "FAIL")
|
||||
(plist-get r :lines) (plist-get r :widest)
|
||||
(plist-get r :target) (plist-get r :body)
|
||||
(if (> (plist-get r :exempt) 0)
|
||||
(format ", %d verbatim lines exempt"
|
||||
(plist-get r :exempt))
|
||||
""))))
|
||||
(message "%s" msg)
|
||||
r)))
|
||||
|
||||
(defun ekp-gui-verify--case (name setup)
|
||||
"Run one matrix case NAME with buffer SETUP; return a report line."
|
||||
;; Leftover debounce timers from the previous case must not fire
|
||||
;; into this case's fresh buffer.
|
||||
(dolist (fn (list #'ekp-region--reflow
|
||||
#'ekp-region--flush-dirty
|
||||
#'ekp-region--process-chunk))
|
||||
(cancel-function-timers fn))
|
||||
(when (get-buffer "*ekp-showcase*")
|
||||
(kill-buffer "*ekp-showcase*"))
|
||||
(ekp-showcase)
|
||||
(redisplay t)
|
||||
(with-current-buffer "*ekp-showcase*"
|
||||
(funcall setup)
|
||||
(redisplay t)
|
||||
(ekp-auto-justify-mode 1)
|
||||
(when (timerp ekp-region--resize-timer)
|
||||
(cancel-timer ekp-region--resize-timer))
|
||||
(ekp-region--reflow (current-buffer) (ekp-region--effective-width))
|
||||
(redisplay t)
|
||||
(let ((r (ekp-gui-verify--scan (current-buffer))))
|
||||
(prog1 (format "%-22s body=%4d target=%4d widest=%4d over=%d/%d %s"
|
||||
name (plist-get r :body) (plist-get r :target)
|
||||
(plist-get r :widest) (plist-get r :over)
|
||||
(plist-get r :lines)
|
||||
(if (plist-get r :pass) "PASS" "FAIL"))
|
||||
(ekp-auto-justify-mode -1)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ekp-gui-verify-matrix ()
|
||||
"Run the display-context matrix and print a PASS/FAIL table.
|
||||
Covers: plain, text-scale up/down, family+height face remap, and a
|
||||
narrow frame with scaling. Intended for `emacs -Q'; in a customized
|
||||
session prefer `ekp-gui-verify'."
|
||||
(interactive)
|
||||
(unless (display-graphic-p)
|
||||
(user-error "GUI verification needs a graphical frame"))
|
||||
(save-current-buffer
|
||||
(ekp-gui-verify--matrix-1)))
|
||||
|
||||
(defun ekp-gui-verify--matrix-1 ()
|
||||
"Run the matrix cases; caller guards the current buffer."
|
||||
(let (results)
|
||||
(set-frame-size (selected-frame) 190 40)
|
||||
(push (ekp-gui-verify--case "base" #'ignore) results)
|
||||
(push (ekp-gui-verify--case "text-scale +3"
|
||||
(lambda () (text-scale-set 3)))
|
||||
results)
|
||||
(push (ekp-gui-verify--case "text-scale -2"
|
||||
(lambda () (text-scale-set -2)))
|
||||
results)
|
||||
(push (ekp-gui-verify--case "remap family+height"
|
||||
(lambda ()
|
||||
(face-remap-add-relative
|
||||
'default :height 1.15)))
|
||||
results)
|
||||
(push (ekp-gui-verify--case "no fringes"
|
||||
(lambda ()
|
||||
(set-window-fringes
|
||||
(get-buffer-window (current-buffer))
|
||||
0 0)))
|
||||
results)
|
||||
(push (ekp-gui-verify--case "no fringes + scale +2"
|
||||
(lambda ()
|
||||
(set-window-fringes
|
||||
(get-buffer-window (current-buffer))
|
||||
0 0)
|
||||
(text-scale-set 2)))
|
||||
results)
|
||||
(set-frame-size (selected-frame) 70 40)
|
||||
(push (ekp-gui-verify--case "narrow + scale +2"
|
||||
(lambda () (text-scale-set 2)))
|
||||
results)
|
||||
(let ((table (string-join (nreverse results) "\n")))
|
||||
(if noninteractive
|
||||
(princ (concat table "\n"))
|
||||
(with-current-buffer (get-buffer-create "*ekp-gui-verify*")
|
||||
(erase-buffer)
|
||||
(insert table "\n")
|
||||
(display-buffer (current-buffer))))
|
||||
table)))
|
||||
|
||||
(provide 'ekp-gui-verify)
|
||||
|
||||
;;; ekp-gui-verify.el ends here
|
||||
@ -764,6 +764,42 @@ the (position × line-count) Elisp DP — they must agree."
|
||||
(ekp-clear-caches)
|
||||
(should (equal-including-properties via-c via-el)))))))))
|
||||
|
||||
;;;; Display-context measurement (M5 wave)
|
||||
|
||||
(ert-deftest ekp-test-width-context-keys-caches ()
|
||||
"Buffers with face remappings must never share cached paragraphs.
|
||||
`text-scale-mode' and theme tweaks live in `face-remapping-alist';
|
||||
glyphs render at different sizes there, so paragraph data measured
|
||||
in one context is wrong in another."
|
||||
(ekp-clear-caches)
|
||||
(let ((s "上下文键控检查内容足够长断行"))
|
||||
(should-not (equal (ekp--para-key s)
|
||||
(let ((face-remapping-alist
|
||||
'((default :height 1.5))))
|
||||
(ekp--para-key s))))
|
||||
;; the same-string fast path must not leak across contexts either
|
||||
(ekp-clear-caches)
|
||||
(let* ((p1 (ekp--get-para s))
|
||||
(p2 (let ((face-remapping-alist '((default :height 1.5))))
|
||||
(ekp--get-para s))))
|
||||
(should-not (eq p1 p2)))
|
||||
(ekp-clear-caches)))
|
||||
|
||||
(ert-deftest ekp-test-width-context-measurement-cached-separately ()
|
||||
"The width cache keeps remapped and plain measurements apart."
|
||||
(ekp-clear-caches)
|
||||
(let* ((s "宽")
|
||||
(plain (ekp--measured-width s))
|
||||
(remapped (let ((face-remapping-alist '((default :height 2.0))))
|
||||
(ekp--measured-width s))))
|
||||
;; In batch both degrade to columns (equal values); the point is
|
||||
;; that neither call poisons the other's cache entry.
|
||||
(should (= plain (ekp--measured-width s)))
|
||||
(should (= remapped
|
||||
(let ((face-remapping-alist '((default :height 2.0))))
|
||||
(ekp--measured-width s))))
|
||||
(ekp-clear-caches)))
|
||||
|
||||
(provide 'ekp-tests)
|
||||
|
||||
;;; ekp-tests.el ends here
|
||||
|
||||
Loading…
Reference in New Issue
Block a user