refactor: unify canonical style and font projection
This commit is contained in:
parent
cb0e1823d3
commit
722791a01e
@ -30,7 +30,7 @@ Use the functions and properties in `docs/user/ebox-api-reference.en.md`; the fa
|
||||
|
||||
## Layout scope
|
||||
|
||||
Ebox supports character and pixel dimensions, padding, margins, borders, colors, Emacs face/font properties, overflow, wrapping, row/column/flex formatting, and Grid tracks including fixed, fractional, implicit, gap, placement, span, and alignment behavior. The supported typography subset is `:font`, `:font-family`, `:font-height`/`:font-size`, `:font-weight`, and `:font-slant`. CSS compatibility is intentionally partial: percentages, absolute positioning, z-index, shadows, border radius, full browser typography, and browser-level bidi are outside this package.
|
||||
Ebox supports column- and pixel-based horizontal dimensions, line-based vertical dimensions, padding, margins, pixel borders, colors, overflow, wrapping, row/column/flex formatting, and Grid tracks including fixed, fractional, implicit, gap, placement, span, and alignment behavior. The public typography subset is `:font-family`, CSS-reference-pixel `:font-size`, `:font-weight`, and `:font-style`; `:font-slant` is only an exact parse-time alias of `:font-style`. Emacs faces and text properties belong to the final adapter and are not Ebox author properties. CSS compatibility is intentionally partial: percentages, absolute positioning, z-index, shadows, border radius, full browser typography, and browser-level bidi are outside this package.
|
||||
|
||||
## Verification rule
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ Ebox 是底层空间渲染引擎。它负责把声明式节点树转换为经过
|
||||
|
||||
## 布局范围
|
||||
|
||||
Ebox 支持字符和像素尺寸、padding、margin、border、颜色、Emacs face/font property、overflow、换行、row/column/flex formatting,以及固定、分数、隐式、gap、放置、span 和对齐等 Grid 能力。支持的 typography 子集是 `:font`、`:font-family`、`:font-height`/`:font-size`、`:font-weight` 和 `:font-slant`。CSS 兼容性有意是部分实现:百分比、绝对定位、z-index、阴影、border radius、完整浏览器 typography 和浏览器级 bidi 不属于本包。
|
||||
Ebox 支持以列或像素表示的横向尺寸、以行表示的纵向尺寸、padding、margin、像素 border、颜色、overflow、换行、row/column/flex formatting,以及固定、分数、隐式、gap、放置、span 和对齐等 Grid 能力。公共 typography 子集是 `:font-family`、以 CSS reference pixel 表示的 `:font-size`、`:font-weight` 和 `:font-style`;`:font-slant` 只是 `:font-style` 的精确 parse-time alias。Emacs face 与文本属性只属于最终 adapter,不是 Ebox 作者属性。CSS 兼容性有意是部分实现:百分比、绝对定位、z-index、阴影、border radius、完整浏览器 typography 和浏览器级 bidi 不属于本包。
|
||||
|
||||
## 验证规则
|
||||
|
||||
|
||||
7
Makefile
7
Makefile
@ -10,7 +10,7 @@ NATIVE_MANIFEST = native/Cargo.toml
|
||||
NATIVE_TARGET ?= $(shell $(EMACS_BATCH) -l ebox-native-reflow.el --eval '(princ (ebox-native-reflow--rust-target))')
|
||||
NATIVE_RELEASE_DIR = native/target/$(NATIVE_TARGET)/release
|
||||
|
||||
.PHONY: all check ci load compile test checkdoc source-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
|
||||
all: check
|
||||
|
||||
@ -25,11 +25,14 @@ compile:
|
||||
rm -f *.elc tests/*.elc scripts/*.elc
|
||||
$(EMACS_BATCH) --eval '(setq byte-compile-error-on-warn t byte-compile-warnings (quote (not obsolete)))' -l ebox.el --eval '(ebox-byte-compile)'
|
||||
|
||||
test: source-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests docs-contract-tests ci-contract-tests
|
||||
test: source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests docs-contract-tests ci-contract-tests
|
||||
|
||||
source-tests:
|
||||
$(EMACS_BATCH) -l tests/ebox-source-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
font-tests:
|
||||
$(EMACS_BATCH) -l tests/ebox-font-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
core-tests:
|
||||
$(EMACS_TEST) -l tests/ebox-core-render-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ field-based child syntax.
|
||||
"*Ebox Example*"
|
||||
(ebox-build
|
||||
'(column :padding (1 2)
|
||||
:border ((1) solid "#8A93A6")
|
||||
:border (1 solid "#8A93A6")
|
||||
(text :color "#263244" "Hello Ebox")
|
||||
(row :item-gap 1
|
||||
(box "Left")
|
||||
|
||||
@ -30,7 +30,7 @@ Ebox 需要 Emacs 29.1 或更高版本,并依赖 ECSS 与 TP。包管理器应
|
||||
"*Ebox Example*"
|
||||
(ebox-build
|
||||
'(column :padding (1 2)
|
||||
:border ((1) solid "#8A93A6")
|
||||
:border (1 solid "#8A93A6")
|
||||
(text :color "#263244" "Hello Ebox")
|
||||
(row :item-gap 1
|
||||
(box "Left")
|
||||
|
||||
@ -90,7 +90,7 @@ Caller-owned Source Tree
|
||||
|
||||
## Grid contract
|
||||
|
||||
Grid currently covers fixed and pixel tracks, `auto`, fractional tracks, `minmax`, `repeat`, implicit rows and columns, row/column gap, auto-flow, one-based placement, positive spans, item/content alignment, and ordinary buffer rendering and updates. Ebox supports a finite typography subset (`:font`, `:font-family`, `:font-height`/`:font-size`, `:font-weight`, and `:font-slant`); full browser typography is outside the contract. Percentages, absolute positioning, z-index, border radius, shadows, and browser-level bidi are also outside the contract.
|
||||
Grid currently covers fixed and pixel tracks, `auto`, fractional tracks, `minmax`, `repeat`, implicit rows and columns, row/column gap, auto-flow, one-based placement, positive spans, item/content alignment, and ordinary buffer rendering and updates. Ebox supports canonical font inputs (`:font-family`, `:font-size`, `:font-weight`, and `:font-style`) plus text-decoration paint; full browser typography is outside the contract. Percentages, absolute positioning, z-index, border radius, shadows, and browser-level bidi are also outside the contract.
|
||||
|
||||
## Active examples and tests
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
||||
|
||||
## Grid 合同
|
||||
|
||||
当前 Grid 支持固定和像素轨道、`auto`、分数轨道、`minmax`、`repeat`、隐式行列、行列 gap、auto-flow、从 1 开始的 placement、正整数 span、item/content 对齐,以及普通 buffer 渲染和更新。Ebox 支持有限 typography 子集(`:font`、`:font-family`、`:font-height`/`:font-size`、`:font-weight` 和 `:font-slant`);完整浏览器 typography 不在合同内。百分比、绝对定位、z-index、圆角、阴影和浏览器级 bidi 也不在合同内。
|
||||
当前 Grid 支持固定和像素轨道、`auto`、分数轨道、`minmax`、`repeat`、隐式行列、行列 gap、auto-flow、从 1 开始的 placement、正整数 span、item/content 对齐,以及普通 buffer 渲染和更新。Ebox 支持规范字体输入(`:font-family`、`:font-size`、`:font-weight`、`:font-style`)与 text-decoration paint;完整浏览器 typography 不在合同内。百分比、绝对定位、z-index、圆角、阴影和浏览器级 bidi 也不在合同内。
|
||||
|
||||
## Active 示例与测试
|
||||
|
||||
|
||||
@ -72,7 +72,9 @@ another public node or wrapper.
|
||||
| Overflow | `:overflow` (`visible`, `hidden`, `scroll`) |
|
||||
|
||||
Ordinary horizontal numbers are character columns; a one-element horizontal
|
||||
list is pixels. Vertical numbers are lines. `(viewport)` and
|
||||
list is pixels. Vertical numbers are lines. Border widths are the exception:
|
||||
they are always nonnegative integer pixels, so use `(1 solid "#687386")`, not
|
||||
`((1) solid "#687386")`. `(viewport)` and
|
||||
`(viewport-height)` use the current render context. Property validation belongs
|
||||
to Ebox and invalid context/value combinations signal an error.
|
||||
|
||||
|
||||
@ -67,8 +67,9 @@ participation property 就是普通子 Box property,不会创建另一种公
|
||||
| 文本布局 | `:text-align`、`:wrap-mode`(`word`、`char`、`kp`、`none`) |
|
||||
| Overflow | `:overflow`(`visible`、`hidden`、`scroll`) |
|
||||
|
||||
普通横向数字表示字符列,单元素横向 list 表示像素;纵向数字表示行数。
|
||||
`(viewport)` 与 `(viewport-height)` 使用当前 render context。property 验证属于
|
||||
普通横向数字表示字符列,单元素横向 list 表示像素;纵向数字表示行数。Border
|
||||
width 是例外,始终使用非负整数像素,因此写 `(1 solid "#687386")`,不写
|
||||
`((1) solid "#687386")`。`(viewport)` 与 `(viewport-height)` 使用当前 render context。property 验证属于
|
||||
Ebox;context/value 组合无效时会直接报错。
|
||||
|
||||
## 2. Evaluated typed construction
|
||||
|
||||
@ -38,7 +38,7 @@ child-layout algorithm, not a different kind of visual object.
|
||||
(defvar ebox-guide-input
|
||||
(ebox-build
|
||||
'(column :padding (1 2)
|
||||
:border ((1) solid "#8A93A6")
|
||||
:border (1 solid "#8A93A6")
|
||||
(text :color "#263244" "Research notes")
|
||||
(row :item-gap 1
|
||||
(box :id "status" :background-color "#F4F6FB" "Inbox")
|
||||
@ -94,7 +94,7 @@ child `box`.
|
||||
```elisp
|
||||
(ebox-build
|
||||
'(grid :width (640)
|
||||
:grid-template-columns ((200) 1fr 1fr)
|
||||
:grid-template-columns ((200) (fr 1) (fr 1))
|
||||
:grid-template-rows (1 1)
|
||||
:gap (1 (12))
|
||||
(box :grid-column (1 :span 3) "Header")
|
||||
@ -117,7 +117,7 @@ Text Box geometry.
|
||||
'(box :width (420)
|
||||
:padding (1 2)
|
||||
:margin (0 1)
|
||||
:border ((1) solid "#8A93A6")
|
||||
:border (1 solid "#8A93A6")
|
||||
:color "#263244"
|
||||
:background-color "#FFFFFF"
|
||||
"A readable panel"))
|
||||
|
||||
@ -34,7 +34,7 @@ Ebox 文档由 Text 和 Box 节点组成。author 语法只有七个入口:
|
||||
(defvar ebox-guide-input
|
||||
(ebox-build
|
||||
'(column :padding (1 2)
|
||||
:border ((1) solid "#8A93A6")
|
||||
:border (1 solid "#8A93A6")
|
||||
(text :color "#263244" "Research notes")
|
||||
(row :item-gap 1
|
||||
(box :id "status" :background-color "#F4F6FB" "Inbox")
|
||||
@ -86,7 +86,7 @@ placement property 同样直接属于子 `box`。
|
||||
```elisp
|
||||
(ebox-build
|
||||
'(grid :width (640)
|
||||
:grid-template-columns ((200) 1fr 1fr)
|
||||
:grid-template-columns ((200) (fr 1) (fr 1))
|
||||
:grid-template-rows (1 1)
|
||||
:gap (1 (12))
|
||||
(box :grid-column (1 :span 3) "Header")
|
||||
@ -107,7 +107,7 @@ placement property 同样直接属于子 `box`。
|
||||
'(box :width (420)
|
||||
:padding (1 2)
|
||||
:margin (0 1)
|
||||
:border ((1) solid "#8A93A6")
|
||||
:border (1 solid "#8A93A6")
|
||||
:color "#263244"
|
||||
:background-color "#FFFFFF"
|
||||
"A readable panel"))
|
||||
|
||||
@ -6,9 +6,24 @@
|
||||
|
||||
;;; Code:
|
||||
|
||||
(define-error 'ebox-surface-capability-error
|
||||
"Ebox surface capability violation")
|
||||
|
||||
(defun ebox-buffer-validate-border-capability (style)
|
||||
"Return STYLE when its border can be represented exactly by Emacs text."
|
||||
(dolist (side '(top bottom) style)
|
||||
(let ((width (or (plist-get
|
||||
style (intern (format ":border-%s-pixel" side)))
|
||||
0)))
|
||||
(when (> width 1)
|
||||
(signal 'ebox-surface-capability-error
|
||||
(list :border side :declared-pixels width
|
||||
:supported-pixels '(0 1)))))))
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'subr-x)
|
||||
(require 'ebox-measure)
|
||||
(require 'ebox-font)
|
||||
(require 'tp-style)
|
||||
|
||||
(defvar ebox--propertize-private-content-line-p nil
|
||||
@ -320,29 +335,50 @@ Return FUNCTION's value."
|
||||
|
||||
;;; Paint Mapping
|
||||
|
||||
(defun ebox-buffer--font-attributes (style)
|
||||
"Return anonymous face attributes for STYLE typography longhands."
|
||||
(let (face)
|
||||
(dolist (entry '((:font-family . :family)
|
||||
(:font-height . :height)
|
||||
(:font-weight . :weight)
|
||||
(:font-slant . :slant)))
|
||||
(when-let* ((value (plist-get style (car entry))))
|
||||
(setq face (plist-put face (cdr entry) value))))
|
||||
face))
|
||||
(defun ebox-buffer--font-fact (style)
|
||||
"Return STYLE's shared surface-resolved font fact."
|
||||
(and (listp style) (plist-get style :ebox-font-fact)))
|
||||
|
||||
(defun ebox-buffer--font-face (style)
|
||||
"Return STYLE's complete Emacs face value for typography."
|
||||
(let* ((base (plist-get style :font))
|
||||
(base (if (stringp base) (list :family base) base))
|
||||
(attributes (ebox-buffer--font-attributes style)))
|
||||
(cond
|
||||
((and attributes base)
|
||||
(if (and (listp base) (not (keywordp (car-safe base))))
|
||||
(cons attributes base)
|
||||
(list attributes base)))
|
||||
(attributes attributes)
|
||||
(base base))))
|
||||
"Return a detached Emacs face from STYLE's shared font fact."
|
||||
(when-let* ((fact (ebox-buffer--font-fact style)))
|
||||
(ebox-font-paint-face fact)))
|
||||
|
||||
(defconst ebox-buffer--decoration-style-map
|
||||
'((solid . line) (double . double-line) (dotted . dots)
|
||||
(dashed . dashes) (wavy . wave))
|
||||
"Canonical CSS decoration styles mapped to Emacs underline styles.")
|
||||
|
||||
(defun ebox-buffer--text-decoration-face (style)
|
||||
"Return STYLE's final Emacs text-decoration face, or nil."
|
||||
(let* ((line (plist-get style :text-decoration-line))
|
||||
(lines (if (listp line) line (list line)))
|
||||
(color (plist-get style :text-decoration-color))
|
||||
(decoration-style
|
||||
(or (plist-get style :text-decoration-style) 'solid))
|
||||
(resolved-color
|
||||
(unless (or (memq color '(nil currentColor))
|
||||
(tp-paint-slot-p color))
|
||||
color))
|
||||
face)
|
||||
(unless (or (null line) (equal lines '(none)))
|
||||
(when (memq 'underline lines)
|
||||
(setq face
|
||||
(plist-put
|
||||
face :underline
|
||||
(append
|
||||
(when resolved-color (list :color resolved-color))
|
||||
(let ((emacs-style
|
||||
(alist-get decoration-style
|
||||
ebox-buffer--decoration-style-map)))
|
||||
(unless (eq emacs-style 'line)
|
||||
(list :style emacs-style)))))))
|
||||
(when (memq 'overline lines)
|
||||
(setq face (plist-put face :overline (or resolved-color t))))
|
||||
(when (memq 'line-through lines)
|
||||
(setq face (plist-put face :strike-through
|
||||
(or resolved-color t)))))
|
||||
face))
|
||||
|
||||
(defun ebox-buffer--paint-color (style role)
|
||||
"Return the foreground paint color for STYLE and ROLE."
|
||||
@ -377,6 +413,8 @@ foreground color."
|
||||
This backend mapper intentionally accepts computed style facts and emits only
|
||||
buffer-facing paint properties. Layout-only properties never pass through."
|
||||
(let ((face (ebox-buffer--font-face style)))
|
||||
(when-let* ((decoration (ebox-buffer--text-decoration-face style)))
|
||||
(setq face (append face decoration)))
|
||||
(when-let* ((foreground (ebox-buffer--paint-color style role)))
|
||||
(setq face
|
||||
(cond
|
||||
@ -397,7 +435,17 @@ buffer-facing paint properties. Layout-only properties never pass through."
|
||||
|
||||
(defun ebox--propertize-typography (string style)
|
||||
"Apply STYLE's typography to one copy of STRING."
|
||||
(if-let* ((face (ebox-buffer--font-face style)))
|
||||
(if-let* ((fact (ebox-buffer--font-fact style)))
|
||||
(let ((copy (copy-sequence string))
|
||||
(face (ebox-font-measurement-face fact)))
|
||||
(when face
|
||||
(ebox--add-render-face! copy 0 (length copy) face t))
|
||||
(ebox--register-render-owned-face-values string copy))
|
||||
string))
|
||||
|
||||
(defun ebox--propertize-text-decoration (string style)
|
||||
"Apply STYLE's text decoration to one copy of STRING."
|
||||
(if-let* ((face (ebox-buffer--text-decoration-face style)))
|
||||
(let ((copy (copy-sequence string)))
|
||||
(ebox--add-render-face! copy 0 (length copy) face t)
|
||||
(ebox--register-render-owned-face-values string copy))
|
||||
|
||||
@ -242,9 +242,11 @@ canonical inputs, not additional source authority."
|
||||
(plist-put node :ebox-source-handle handle)
|
||||
node)
|
||||
|
||||
(defun ebox-canonical--text-measurement-property-p (property)
|
||||
"Return non-nil when PROPERTY belongs to canonical Text measurement."
|
||||
(eq (plist-get property :group) 'typography))
|
||||
(defun ebox-canonical--text-engine-property-p (_property)
|
||||
"Return non-nil for a Text fact materializable without a Surface.
|
||||
Canonical Text style currently consists of paint facts and host-resolved font
|
||||
facts. Neither is a constructor-owned measurement field."
|
||||
nil)
|
||||
|
||||
(defun ebox-canonical--box-frame-property-p (property)
|
||||
"Return non-nil when PROPERTY belongs to Box geometry or participation."
|
||||
@ -284,20 +286,18 @@ and author TAG."
|
||||
declarations
|
||||
(lambda (property)
|
||||
(or (funcall predicate property)
|
||||
(memq (plist-get property :group)
|
||||
'(paint typography)))))
|
||||
(eq (plist-get property :group) 'paint))))
|
||||
(ebox-style-declarations-require-projection-p
|
||||
declarations
|
||||
(lambda (property)
|
||||
(or (funcall predicate property)
|
||||
(memq (plist-get property :group)
|
||||
'(paint typography))
|
||||
(eq (plist-get property :group) 'paint)
|
||||
(and (memq tag '(flex grid))
|
||||
(memq tag
|
||||
(plist-get property
|
||||
:contexts)))))))))
|
||||
(setf (nth 1 facts)
|
||||
(ebox-style-expand-ebox-plist (nth 1 facts)))
|
||||
(ebox-style--expand-engine-plist (nth 1 facts)))
|
||||
(puthash tag facts by-tag)
|
||||
facts))))
|
||||
(let ((declarations
|
||||
@ -316,13 +316,12 @@ and author TAG."
|
||||
do (error "%s does not accept %S"
|
||||
context (plist-get property :name)))
|
||||
(list declarations
|
||||
(ebox-style-expand-ebox-plist
|
||||
(ebox-style--expand-engine-plist
|
||||
(ebox-style-declaration-properties
|
||||
declarations
|
||||
(lambda (property)
|
||||
(or (funcall predicate property)
|
||||
(memq (plist-get property :group)
|
||||
'(paint typography))))))
|
||||
(eq (plist-get property :group) 'paint)))))
|
||||
nil))))
|
||||
|
||||
(defun ebox-canonical-facts-from-declarations (tag declarations)
|
||||
@ -331,7 +330,7 @@ Source declarations stay behind the source handle; the returned value contains
|
||||
only the current canonical-node projection needed before G4c separates paint."
|
||||
(let ((predicate
|
||||
(if (eq tag 'text)
|
||||
#'ebox-canonical--text-measurement-property-p
|
||||
#'ebox-canonical--text-engine-property-p
|
||||
#'ebox-canonical--box-frame-property-p)))
|
||||
(pcase-let ((`(,canonical ,engine ,style-required-p)
|
||||
(ebox-canonical--node-properties
|
||||
|
||||
51
ebox-dsl.el
51
ebox-dsl.el
@ -73,31 +73,49 @@
|
||||
(when-let* ((entry (ebox-dsl--optional-field properties field context)))
|
||||
(cdr entry)))
|
||||
|
||||
(defun ebox-dsl--validate-source-fields (properties context)
|
||||
"Validate shared source metadata in PROPERTIES for CONTEXT."
|
||||
(dolist (entry `((:key . ,#'ebox-source-stable-key-p)
|
||||
(:class . ,#'ebox-source-class-list-p)
|
||||
(:id . ,#'ebox-source-semantic-id-p)))
|
||||
(when-let* ((field (car entry))
|
||||
(present (ebox-dsl--optional-field properties field context)))
|
||||
(unless (funcall (cdr entry) (cdr present))
|
||||
(error "%s rejected %S value: %S" context field (cdr present)))))
|
||||
properties)
|
||||
|
||||
(defun ebox-dsl--compile-style (tag properties context)
|
||||
"Compile and validate TAG author PROPERTIES for CONTEXT."
|
||||
(condition-case err
|
||||
(if properties
|
||||
(ebox-style-compile-form tag properties)
|
||||
nil)
|
||||
(let ((declarations
|
||||
(and properties (ebox-style-compile-form tag properties))))
|
||||
(cl-loop
|
||||
for (property value)
|
||||
on (ebox-style-declaration-properties
|
||||
declarations
|
||||
(lambda (definition)
|
||||
(eq (plist-get definition :normalizer) 'color)))
|
||||
by #'cddr
|
||||
unless (stringp value)
|
||||
do (error "Standalone Ebox %S requires a CSS color string: %S"
|
||||
property value))
|
||||
declarations)
|
||||
(error
|
||||
(error "%s rejected properties: %s"
|
||||
context (error-message-string err)))))
|
||||
|
||||
(defun ebox-dsl--layout-config
|
||||
(tag declarations extensions context)
|
||||
"Construct TAG LayoutConfig from DECLARATIONS and EXTENSIONS for CONTEXT."
|
||||
(defun ebox-dsl--layout-config (tag declarations context)
|
||||
"Construct TAG LayoutConfig from canonical DECLARATIONS for CONTEXT."
|
||||
(if (eq tag 'text)
|
||||
nil
|
||||
(condition-case err
|
||||
(let ((names (ebox-layout-config-property-names tag)))
|
||||
(ebox-layout-config-for-form
|
||||
tag
|
||||
(if (memq tag '(row column))
|
||||
extensions
|
||||
(let ((names (ebox-layout-config-property-names tag)))
|
||||
(ebox-style-declaration-properties
|
||||
declarations
|
||||
(lambda (property)
|
||||
(memq (plist-get property :name) names))))))
|
||||
(memq (plist-get property :name) names)))))
|
||||
(error
|
||||
(error "%s rejected layout config: %s"
|
||||
context (error-message-string err))))))
|
||||
@ -114,6 +132,7 @@ function."
|
||||
(let* ((context (format "Ebox %S" tag))
|
||||
(properties
|
||||
(ebox-dsl--validate-plist properties context)))
|
||||
(ebox-dsl--validate-source-fields properties context)
|
||||
(dolist (field '(:content :layout :display :ebox-type :ebox-content-node))
|
||||
(when (plist-member properties field)
|
||||
(error "%s does not accept private author property %S"
|
||||
@ -124,17 +143,9 @@ function."
|
||||
properties :outer context)))
|
||||
(outer (and (not (eq tag 'text))
|
||||
(if outer-field (cdr outer-field) 'block)))
|
||||
(extension-keys (and (memq tag '(row column))
|
||||
'(:item-gap :cross-align)))
|
||||
(extensions
|
||||
(ebox-dsl--keep-keys
|
||||
properties extension-keys))
|
||||
(style-properties
|
||||
(ebox-dsl--without-keys
|
||||
properties
|
||||
(append (unless (eq tag 'text) '(:outer))
|
||||
extension-keys
|
||||
ebox-dsl--source-fields)))
|
||||
properties ebox-dsl--source-fields))
|
||||
(declarations
|
||||
(ebox-dsl--compile-style
|
||||
tag style-properties context)))
|
||||
@ -152,7 +163,7 @@ function."
|
||||
:outer outer
|
||||
:layout
|
||||
(ebox-dsl--layout-config
|
||||
tag declarations extensions context)
|
||||
tag declarations context)
|
||||
:declarations declarations
|
||||
:owned-facts
|
||||
(ebox-canonical-facts-from-declarations tag declarations)))))
|
||||
|
||||
19
ebox-flex.el
19
ebox-flex.el
@ -102,18 +102,17 @@
|
||||
"Supported canonical FlexConfig wrapping values.")
|
||||
|
||||
(defconst ebox--flex-justify-content-values
|
||||
'(flex-start flex-end center space-between space-around space-evenly
|
||||
start end left right normal)
|
||||
'(normal start end flex-start flex-end center stretch
|
||||
space-between space-around space-evenly)
|
||||
"Supported canonical FlexConfig main-axis alignment values.")
|
||||
|
||||
(defconst ebox--flex-align-items-values
|
||||
'(stretch flex-start flex-end center baseline normal
|
||||
start end self-start self-end)
|
||||
'(normal start end stretch flex-start flex-end center baseline)
|
||||
"Supported canonical FlexConfig item alignment values.")
|
||||
|
||||
(defconst ebox--flex-align-content-values
|
||||
'(stretch flex-start flex-end center space-between space-around space-evenly
|
||||
baseline normal start end)
|
||||
'(normal start end stretch flex-start flex-end center
|
||||
space-between space-around space-evenly)
|
||||
"Supported canonical FlexConfig line alignment values.")
|
||||
|
||||
(defconst ebox--flex-container-neutral-box-prop-keys
|
||||
@ -1843,7 +1842,7 @@ a prefix of the eventual full wrapper render."
|
||||
(unless complete
|
||||
(plist-put copy :padding-bottom-height 0)
|
||||
(plist-put copy :margin-bottom-height 0)
|
||||
(plist-put copy :border-bottom-p nil))
|
||||
(plist-put copy :border-bottom-pixel 0))
|
||||
copy))
|
||||
|
||||
(defun ebox--flex-window-complete-prefix-supported-p (box)
|
||||
@ -1858,7 +1857,7 @@ a prefix of the eventual full wrapper render."
|
||||
line
|
||||
(let ((region-id (ebox--ensure-region-id box)))
|
||||
(puthash region-id box ebox--region-box-table)
|
||||
(if (ebox-get box :border-bottom-p)
|
||||
(if (> (ebox-get box :border-bottom-pixel) 0)
|
||||
(propertize
|
||||
(ebox--propertize-underline
|
||||
line (ebox-get box :border-bottom-color))
|
||||
@ -1918,7 +1917,7 @@ the chunk begins at the wrapper top and should include top chrome."
|
||||
(border-right-pixel (ebox-get box :border-right-pixel))
|
||||
(border-right-color (ebox-get box :border-right-color))
|
||||
(border-top-p (and include-top
|
||||
(ebox-get box :border-top-p)))
|
||||
(> (ebox-get box :border-top-pixel) 0)))
|
||||
(border-top-color (ebox-get box :border-top-color))
|
||||
(color (and (ebox-style--text-paint-owner-p box)
|
||||
(ebox-get box :color)))
|
||||
@ -2242,7 +2241,7 @@ the chunk begins at the wrapper top and should include top chrome."
|
||||
"Return how many cached prefix tail lines completion rewrites for NODE."
|
||||
(let ((box (ebox--flex-window-box node)))
|
||||
(if (and box
|
||||
(ebox-get box :border-bottom-p)
|
||||
(> (ebox-get box :border-bottom-pixel) 0)
|
||||
(= (floor (ebox-get box :padding-bottom-height)) 0))
|
||||
1
|
||||
0)))
|
||||
|
||||
222
ebox-font.el
Normal file
222
ebox-font.el
Normal file
@ -0,0 +1,222 @@
|
||||
;;; ebox-font.el --- Surface-resolved font facts -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Resolves canonical font inputs against one Emacs display capability and
|
||||
;; returns a shared immutable fact for measurement and final paint. It does
|
||||
;; not own author parsing, cascade, layout, TP priority, or buffer publication.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(cl-defstruct (ebox-font-fact
|
||||
(:constructor ebox-font--make-fact)
|
||||
(:conc-name ebox-font-fact--))
|
||||
"One surface-resolved font projection shared by all downstream consumers."
|
||||
(requested-decipoints nil :read-only t)
|
||||
(resolved-font-identity nil :read-only t)
|
||||
(measurement-metrics nil :read-only t)
|
||||
(capability-signature nil :read-only t))
|
||||
|
||||
(defun ebox-font-fact-requested-decipoints (fact)
|
||||
"Return FACT's requested decipoints scalar."
|
||||
(ebox-font-fact--requested-decipoints fact))
|
||||
|
||||
(defun ebox-font-fact-resolved-font-identity (fact)
|
||||
"Return a detached resolved identity from FACT."
|
||||
(copy-tree (ebox-font-fact--resolved-font-identity fact) t))
|
||||
|
||||
(defun ebox-font-fact-measurement-metrics (fact)
|
||||
"Return detached measurement metrics from FACT."
|
||||
(copy-tree (ebox-font-fact--measurement-metrics fact) t))
|
||||
|
||||
(defun ebox-font-fact-capability-signature (fact)
|
||||
"Return a detached display capability signature from FACT."
|
||||
(copy-tree (ebox-font-fact--capability-signature fact) t))
|
||||
|
||||
(defconst ebox-font--cache-max-entries 512
|
||||
"Maximum resolved font facts retained across display capabilities.")
|
||||
|
||||
(defvar ebox-font--cache (make-hash-table :test #'equal)
|
||||
"Cache keyed by canonical font inputs and complete capability signature.")
|
||||
|
||||
(defun ebox-font--requested-decipoints (font-size-px)
|
||||
"Convert positive FONT-SIZE-PX reference pixels to integer decipoints."
|
||||
(when font-size-px
|
||||
(unless (and (numberp font-size-px) (> font-size-px 0))
|
||||
(error "Ebox font size must be positive CSS reference pixels: %S"
|
||||
font-size-px))
|
||||
;; CSS reference px is 1/96 inch. Emacs integer face height is 1/10 point.
|
||||
;; px * 72 / 96 * 10 = px * 7.5; positive values round half up.
|
||||
(floor (+ (* font-size-px 7.5) 0.5))))
|
||||
|
||||
(defun ebox-font--face
|
||||
(family decipoints weight style weight-active-p style-active-p)
|
||||
"Return Emacs face projection for canonical font inputs."
|
||||
(let (face)
|
||||
(when family
|
||||
(setq face
|
||||
(plist-put face :family
|
||||
(if (symbolp family) (symbol-name family) family))))
|
||||
(when decipoints (setq face (plist-put face :height decipoints)))
|
||||
(when weight-active-p (setq face (plist-put face :weight weight)))
|
||||
(when style-active-p (setq face (plist-put face :slant style)))
|
||||
face))
|
||||
|
||||
(defun ebox-font--parent-css-weight (fact)
|
||||
"Return FACT's resolved CSS numeric weight, defaulting to normal."
|
||||
(or (and (ebox-font-fact-p fact)
|
||||
(plist-get (ebox-font-fact--resolved-font-identity fact)
|
||||
:css-weight))
|
||||
400))
|
||||
|
||||
(defun ebox-font--absolute-css-weight (weight parent-fact)
|
||||
"Resolve CSS WEIGHT against PARENT-FACT to one absolute numeric weight."
|
||||
(pcase weight
|
||||
((or 'nil 'normal) 400)
|
||||
('bold 700)
|
||||
('bolder
|
||||
(let ((parent (ebox-font--parent-css-weight parent-fact)))
|
||||
(cond ((<= parent 300) 400)
|
||||
((<= parent 500) 700)
|
||||
(t 900))))
|
||||
('lighter
|
||||
(let ((parent (ebox-font--parent-css-weight parent-fact)))
|
||||
(cond ((<= parent 500) 100)
|
||||
((<= parent 700) 400)
|
||||
(t 700))))
|
||||
((pred numberp) weight)
|
||||
(_ (error "Invalid canonical CSS font weight: %S" weight))))
|
||||
|
||||
(defun ebox-font--emacs-weight (css-weight)
|
||||
"Map absolute numeric CSS-WEIGHT to an Emacs face weight."
|
||||
(alist-get css-weight
|
||||
'((100 . ultra-light) (200 . extra-light) (300 . light)
|
||||
(400 . normal) (500 . normal) (600 . semi-bold)
|
||||
(700 . bold) (800 . extra-bold) (900 . ultra-bold))))
|
||||
|
||||
(defun ebox-font--default-resolver (face window)
|
||||
"Resolve FACE in WINDOW and return identity plus actual measurement metrics."
|
||||
(let* ((window (and (window-live-p window) window))
|
||||
(probe (propertize "M " 'face face))
|
||||
(font (ignore-errors (font-at 0 window probe)))
|
||||
(name
|
||||
(and font
|
||||
(or (ignore-errors (font-get font :name))
|
||||
(and (fboundp 'font-xlfd-name)
|
||||
(ignore-errors (font-xlfd-name font))))))
|
||||
(info
|
||||
(and name
|
||||
(ignore-errors
|
||||
(font-info name (and window (window-frame window)))))))
|
||||
(list
|
||||
:identity
|
||||
(if font
|
||||
(list :type (font-get font :type)
|
||||
:name name
|
||||
:family (font-get font :family)
|
||||
:weight (font-get font :weight)
|
||||
:slant (font-get font :slant)
|
||||
:width (font-get font :width)
|
||||
:size (font-get font :size)
|
||||
:file (and info (> (length info) 12) (aref info 12)))
|
||||
(list :unresolved-face (copy-tree face)))
|
||||
:metrics
|
||||
(if info
|
||||
(list :pixel-size (aref info 2)
|
||||
:height (aref info 3)
|
||||
:baseline-offset (aref info 4)
|
||||
:max-width (aref info 7)
|
||||
:ascent (aref info 8)
|
||||
:descent (aref info 9)
|
||||
:space-width (aref info 10)
|
||||
:average-width (aref info 11))
|
||||
(list :probe-width (string-pixel-width probe)
|
||||
:space-width
|
||||
(string-pixel-width (propertize " " 'face face)))))))
|
||||
|
||||
(defvar ebox-font--resolver #'ebox-font--default-resolver
|
||||
"Function resolving an Emacs face into identity and measurement metrics.")
|
||||
|
||||
(cl-defun ebox-font--project
|
||||
(&key family size weight style weight-active-p style-active-p
|
||||
capability-signature window parent-fact)
|
||||
"Return one cached font fact for canonical inputs and display capability."
|
||||
(let* ((css-weight (ebox-font--absolute-css-weight weight parent-fact))
|
||||
(emacs-weight (ebox-font--emacs-weight css-weight))
|
||||
(decipoints (ebox-font--requested-decipoints size))
|
||||
(face (ebox-font--face
|
||||
family decipoints emacs-weight style
|
||||
weight-active-p style-active-p))
|
||||
(key (list family size weight css-weight style
|
||||
weight-active-p style-active-p capability-signature))
|
||||
(missing (make-symbol "ebox-font-fact-missing"))
|
||||
(cached (gethash key ebox-font--cache missing)))
|
||||
(if (not (eq cached missing))
|
||||
cached
|
||||
(let* ((resolved (funcall ebox-font--resolver face window))
|
||||
(fact
|
||||
(ebox-font--make-fact
|
||||
:requested-decipoints decipoints
|
||||
:resolved-font-identity
|
||||
(list :css-weight css-weight
|
||||
:face (copy-tree face)
|
||||
:resource (copy-tree (plist-get resolved :identity)))
|
||||
:measurement-metrics
|
||||
(copy-tree (plist-get resolved :metrics))
|
||||
:capability-signature (copy-tree capability-signature))))
|
||||
(when (>= (hash-table-count ebox-font--cache)
|
||||
ebox-font--cache-max-entries)
|
||||
(clrhash ebox-font--cache))
|
||||
(puthash (copy-tree key) fact ebox-font--cache)
|
||||
fact))))
|
||||
|
||||
(defun ebox-font-project-values
|
||||
(values specified capability-signature &optional window parent-fact)
|
||||
"Project canonical font VALUES for one Emacs display capability.
|
||||
SPECIFIED is the canonical property-id set with declaration winners."
|
||||
(let* ((values values)
|
||||
(family (plist-get values 'ebox/font-family))
|
||||
(size (plist-get values 'ebox/font-size))
|
||||
(weight (plist-get values 'ebox/font-weight))
|
||||
(font-style (plist-get values 'ebox/font-style))
|
||||
(weight-active-p
|
||||
(or (not (eq weight 'normal))
|
||||
(memq 'ebox/font-weight specified)))
|
||||
(style-active-p
|
||||
(or (not (eq font-style 'normal))
|
||||
(memq 'ebox/font-style specified))))
|
||||
(when (or family size weight-active-p style-active-p)
|
||||
(ebox-font--project
|
||||
:family family :size size :weight weight :style font-style
|
||||
:weight-active-p weight-active-p :style-active-p style-active-p
|
||||
:capability-signature capability-signature :window window
|
||||
:parent-fact parent-fact))))
|
||||
|
||||
(defun ebox-font-clear-cache ()
|
||||
"Clear all surface-resolved font facts."
|
||||
(clrhash ebox-font--cache))
|
||||
|
||||
(defun ebox-font-measurement-face (fact)
|
||||
"Return a detached measurement face from font FACT."
|
||||
(unless (ebox-font-fact-p fact)
|
||||
(signal 'wrong-type-argument (list 'ebox-font-fact-p fact)))
|
||||
(copy-tree
|
||||
(plist-get (ebox-font-fact--resolved-font-identity fact) :face)))
|
||||
|
||||
(defun ebox-font-paint-face (fact)
|
||||
"Return a detached final-paint face from the same font FACT."
|
||||
(unless (ebox-font-fact-p fact)
|
||||
(signal 'wrong-type-argument (list 'ebox-font-fact-p fact)))
|
||||
(copy-tree
|
||||
(plist-get (ebox-font-fact--resolved-font-identity fact) :face)))
|
||||
|
||||
(defun ebox-font-fact-neutral-p (fact)
|
||||
"Return non-nil when FACT adds no non-default font capability."
|
||||
(and (ebox-font-fact-p fact)
|
||||
(null (ebox-font-fact--requested-decipoints fact))
|
||||
(null (plist-get
|
||||
(ebox-font-fact--resolved-font-identity fact) :face))))
|
||||
|
||||
(provide 'ebox-font)
|
||||
;;; ebox-font.el ends here
|
||||
@ -125,14 +125,14 @@ Backend marker and buffer span data are intentionally excluded."
|
||||
signature))
|
||||
|
||||
(defconst ebox--paint-style-signature-keys
|
||||
'(:color :bgcolor
|
||||
:border-top-p :border-bottom-p
|
||||
'(:color :bgcolor :visibility
|
||||
:text-decoration-line :text-decoration-color :text-decoration-style
|
||||
:border-left-color :border-right-color
|
||||
:border-top-color :border-bottom-color)
|
||||
"Box properties that affect paint without changing layout geometry.")
|
||||
|
||||
(defconst ebox--typography-style-signature-keys
|
||||
'(:font :font-family :font-height :font-weight :font-slant)
|
||||
'(:ebox-font-fact)
|
||||
"Typography properties whose resolved metrics may change geometry.")
|
||||
|
||||
(defconst ebox--geometry-style-signature-keys
|
||||
@ -145,7 +145,10 @@ Backend marker and buffer span data are intentionally excluded."
|
||||
:padding-top-height :padding-bottom-height
|
||||
:margin-left-pixel :margin-right-pixel
|
||||
:margin-top-height :margin-bottom-height
|
||||
:border-left-pixel :border-right-pixel
|
||||
:border-left-pixel :border-left-style
|
||||
:border-right-pixel :border-right-style
|
||||
:border-top-pixel :border-top-style
|
||||
:border-bottom-pixel :border-bottom-style
|
||||
:text-align :vertical-align
|
||||
:overflow :wrap-mode
|
||||
:flex-props :flex-participation))
|
||||
|
||||
115
ebox-grid.el
115
ebox-grid.el
@ -18,6 +18,7 @@
|
||||
|
||||
(declare-function ebox--ensure-node-id "ebox" (node))
|
||||
(declare-function ebox--ensure-region-id "ebox" (box))
|
||||
(declare-function ebox-box-node-p "ebox-canonical" (node))
|
||||
(declare-function ebox-get "ebox" (box property))
|
||||
(declare-function ebox-node-factory--create-from-properties
|
||||
"ebox-node-factory" (&rest plist))
|
||||
@ -26,6 +27,10 @@
|
||||
(declare-function ebox-string-lines "ebox" (string))
|
||||
(declare-function ebox-string-height "ebox" (string))
|
||||
(declare-function ebox-lines-join "ebox" (lines))
|
||||
(declare-function ebox--side-pixel "ebox-layout" (box &optional side))
|
||||
(declare-function ebox--content-min-pixel "ebox-layout" (box))
|
||||
(declare-function ebox--content-max-pixel "ebox-layout" (box))
|
||||
(declare-function ebox--line-min-content-pixel "ebox-layout" (line))
|
||||
(declare-function ebox--lines-align-vertical "ebox-layout" (string height align))
|
||||
(declare-function ebox--flex-spacing "ebox-flex" (mode leftover count base-gap))
|
||||
(declare-function ebox--render-with-cache "ebox-incremental" (node &optional force cache-probe))
|
||||
@ -34,7 +39,7 @@
|
||||
(defconst ebox--grid-layout-prop-keys
|
||||
'(:grid-template-columns :grid-template-rows
|
||||
:grid-auto-columns :grid-auto-rows :grid-auto-flow
|
||||
:column-gap :row-gap :grid-column-gap :grid-row-gap :gap
|
||||
:column-gap :row-gap :gap
|
||||
:justify-items :align-items :justify-content :align-content)
|
||||
"Properties owned by the grid formatting context.")
|
||||
|
||||
@ -46,20 +51,20 @@
|
||||
"Canonical property order stored in a typed GridConfig.")
|
||||
|
||||
(defconst ebox--grid-justify-items-values
|
||||
'(stretch normal start end left right center)
|
||||
'(stretch start end center)
|
||||
"Supported canonical GridConfig inline item alignment values.")
|
||||
|
||||
(defconst ebox--grid-align-items-values
|
||||
'(stretch normal start end top bottom center)
|
||||
'(normal start end stretch flex-start flex-end center baseline)
|
||||
"Supported canonical GridConfig block item alignment values.")
|
||||
|
||||
(defconst ebox--grid-content-alignment-values
|
||||
'(stretch normal start end left right center
|
||||
flex-start flex-end space-between space-around space-evenly)
|
||||
'(normal start end stretch flex-start flex-end center
|
||||
space-between space-around space-evenly)
|
||||
"Supported canonical GridConfig content alignment values.")
|
||||
|
||||
(defconst ebox--grid-item-prop-keys
|
||||
'(:grid-column :grid-row :grid-column-span :grid-row-span)
|
||||
'(:grid-column :grid-row :justify-self)
|
||||
"Properties that describe a child's position in a grid.")
|
||||
|
||||
(defun ebox-grid--split-attrs (items)
|
||||
@ -99,7 +104,17 @@
|
||||
(unless (and (null (cdddr track))
|
||||
(integerp count) (> count 0) value)
|
||||
(error "ebox-grid: invalid repeat track %S" track))
|
||||
(make-list count value))
|
||||
(let ((body
|
||||
(cond
|
||||
((or (ebox-grid--track-form-p value)
|
||||
(numberp value) (symbolp value)
|
||||
(and (consp value) (numberp (car value))
|
||||
(null (cdr value))))
|
||||
(list value))
|
||||
((proper-list-p value) value)
|
||||
(t (error "ebox-grid: invalid repeat body %S" value)))))
|
||||
(cl-loop repeat count
|
||||
append (ebox-grid--expand-repeat body))))
|
||||
(list track)))
|
||||
tracks))
|
||||
|
||||
@ -110,14 +125,8 @@
|
||||
value))
|
||||
(cond
|
||||
((eq value 'auto) '(:kind auto :factor 0))
|
||||
((and (symbolp value)
|
||||
(string-match "\\`\\([0-9]+\\(?:\\.[0-9]+\\)?\\)fr\\'"
|
||||
(symbol-name value)))
|
||||
(let ((factor
|
||||
(string-to-number (match-string 1 (symbol-name value)))))
|
||||
(unless (> factor 0)
|
||||
(error "ebox-grid: fractional track must be positive: %S" value))
|
||||
(list :kind 'fr :factor factor)))
|
||||
((memq value '(min-content max-content))
|
||||
(list :kind value))
|
||||
((and (consp value) (eq (car value) 'fr))
|
||||
(unless (and (numberp (cadr value)) (> (cadr value) 0))
|
||||
(error "ebox-grid: invalid fractional track %S" value))
|
||||
@ -129,9 +138,11 @@
|
||||
(error "ebox-grid: invalid minmax track %S" value))
|
||||
(let ((minimum (ebox-grid--normalize-track (cadr value) axis))
|
||||
(maximum (ebox-grid--normalize-track (caddr value) axis)))
|
||||
(unless (memq (plist-get minimum :kind) '(fixed auto))
|
||||
(unless (memq (plist-get minimum :kind)
|
||||
'(fixed auto min-content max-content))
|
||||
(error "ebox-grid: minmax minimum must be fixed or auto: %S" value))
|
||||
(unless (memq (plist-get maximum :kind) '(fixed auto fr))
|
||||
(unless (memq (plist-get maximum :kind)
|
||||
'(fixed auto min-content max-content fr))
|
||||
(error "ebox-grid: minmax maximum cannot be nested: %S" value))
|
||||
(when (and (eq (plist-get maximum :kind) 'fixed)
|
||||
(eq (plist-get minimum :kind) 'fixed)
|
||||
@ -164,14 +175,10 @@
|
||||
(let* ((pair (ebox--flex-gap-pair (plist-get props :gap)))
|
||||
(row (cond
|
||||
((plist-member props :row-gap) (plist-get props :row-gap))
|
||||
((plist-member props :grid-row-gap)
|
||||
(plist-get props :grid-row-gap))
|
||||
(t (car pair))))
|
||||
(column (cond
|
||||
((plist-member props :column-gap)
|
||||
(plist-get props :column-gap))
|
||||
((plist-member props :grid-column-gap)
|
||||
(plist-get props :grid-column-gap))
|
||||
(t (cdr pair)))))
|
||||
(cons (or (ebox--flex-line-value row 0) 0)
|
||||
(or (ebox--nonnegative-horizontal-size-pixels column 0) 0))))
|
||||
@ -192,6 +199,13 @@
|
||||
|
||||
(defun ebox-grid--normalize-config-props (props)
|
||||
"Normalize GridConfig PROPS once to canonical layout-owned facts."
|
||||
(dolist (property '(:grid-template-columns :grid-template-rows))
|
||||
(when-let* ((value (plist-get props property)))
|
||||
(unless (and (proper-list-p value)
|
||||
value
|
||||
(not (ebox-grid--track-form-p value)))
|
||||
(error "Ebox GridConfig %S requires a nonempty track list: %S"
|
||||
property value))))
|
||||
(let ((gaps (ebox-grid--gap-pair props)))
|
||||
(list
|
||||
:grid-template-columns
|
||||
@ -221,11 +235,11 @@
|
||||
ebox--grid-align-items-values :align-items)
|
||||
:justify-content
|
||||
(ebox-grid--enum-value
|
||||
(plist-get props :justify-content) 'start
|
||||
(plist-get props :justify-content) 'flex-start
|
||||
ebox--grid-content-alignment-values :justify-content)
|
||||
:align-content
|
||||
(ebox-grid--enum-value
|
||||
(plist-get props :align-content) 'start
|
||||
(plist-get props :align-content) 'stretch
|
||||
ebox--grid-content-alignment-values :align-content))))
|
||||
|
||||
(defconst ebox--grid-default-config-props
|
||||
@ -238,6 +252,8 @@
|
||||
(memq (plist-get track :kind) allowed-kinds)
|
||||
(pcase (plist-get track :kind)
|
||||
('auto (equal track '(:kind auto :factor 0)))
|
||||
('min-content (equal track '(:kind min-content)))
|
||||
('max-content (equal track '(:kind max-content)))
|
||||
('fixed
|
||||
(let ((size (plist-get track :size)))
|
||||
(and (numberp size) (>= size 0)
|
||||
@ -250,13 +266,15 @@
|
||||
|
||||
(defun ebox-grid--canonical-track-p (track)
|
||||
"Return non-nil when TRACK is one canonical top-level track descriptor."
|
||||
(or (ebox-grid--canonical-atomic-track-p track '(fixed auto fr))
|
||||
(or (ebox-grid--canonical-atomic-track-p
|
||||
track '(fixed auto min-content max-content fr))
|
||||
(and (proper-list-p track)
|
||||
(eq (plist-get track :kind) 'minmax)
|
||||
(ebox-grid--canonical-atomic-track-p
|
||||
(plist-get track :min) '(fixed auto))
|
||||
(plist-get track :min) '(fixed auto min-content max-content))
|
||||
(ebox-grid--canonical-atomic-track-p
|
||||
(plist-get track :max) '(fixed auto fr))
|
||||
(plist-get track :max)
|
||||
'(fixed auto min-content max-content fr))
|
||||
(equal track
|
||||
(list :kind 'minmax
|
||||
:min (plist-get track :min)
|
||||
@ -313,8 +331,8 @@
|
||||
;;;###autoload
|
||||
(defun ebox-grid-layout-create (&rest plist)
|
||||
"Return a typed GridConfig from canonical evaluated PLIST.
|
||||
Author `:gap' sugar and legacy grid-gap aliases are normalized before this
|
||||
programmatic port and are therefore rejected here."
|
||||
Author `:gap' sugar is expanded before this programmatic port. Removed
|
||||
grid-gap aliases are rejected at the author schema boundary."
|
||||
(ebox-grid--validate-config-input plist)
|
||||
(let ((props (ebox-grid--normalize-config-props plist)))
|
||||
(unless (ebox-grid-layout-config-props-p props)
|
||||
@ -345,6 +363,11 @@ programmatic port and are therefore rejected here."
|
||||
(if (> value 0)
|
||||
(list value 1)
|
||||
(error "ebox-grid: placement starts at one: %S" value)))
|
||||
((and (proper-list-p value)
|
||||
(eq (car value) :span)
|
||||
(integerp (cadr value))
|
||||
(null (cddr value)))
|
||||
(list default (cadr value)))
|
||||
((and (consp value) (numberp (car value)))
|
||||
(let ((start (car value))
|
||||
(tail (cdr value)))
|
||||
@ -364,8 +387,8 @@ programmatic port and are therefore rejected here."
|
||||
(let* ((row (ebox-grid--placement-part (plist-get child :grid-row) 0))
|
||||
(column (ebox-grid--placement-part
|
||||
(plist-get child :grid-column) 0))
|
||||
(row-span (or (plist-get child :grid-row-span) (cadr row)))
|
||||
(column-span (or (plist-get child :grid-column-span) (cadr column))))
|
||||
(row-span (cadr row))
|
||||
(column-span (cadr column)))
|
||||
(dolist (part (list row column))
|
||||
(when (or (not (integerp (car part))) (< (car part) 0)
|
||||
(not (integerp (cadr part))) (< (cadr part) 1))
|
||||
@ -491,6 +514,29 @@ programmatic port and are therefore rejected here."
|
||||
(list (nreverse entries) matrix (length matrix)
|
||||
(length (aref matrix 0)))))
|
||||
|
||||
(defun ebox-grid--string-min-content-pixel (string)
|
||||
"Return STRING's widest unbreakable run in pixels."
|
||||
(let ((best 0))
|
||||
(dolist (line (ebox-string-lines string) best)
|
||||
(setq best (max best (ebox--line-min-content-pixel line))))))
|
||||
|
||||
(defun ebox-grid--entry-intrinsic-size (entry rendered axis mode)
|
||||
"Return ENTRY's intrinsic size from RENDERED for AXIS and MODE."
|
||||
(let* ((node (plist-get entry :node))
|
||||
(string (gethash node rendered)))
|
||||
(if (eq axis 'rows)
|
||||
(ebox-string-height string)
|
||||
(pcase mode
|
||||
('min-content
|
||||
(if (ebox-box-node-p node)
|
||||
(+ (ebox--side-pixel node) (ebox--content-min-pixel node))
|
||||
(ebox-grid--string-min-content-pixel string)))
|
||||
('max-content
|
||||
(if (ebox-box-node-p node)
|
||||
(+ (ebox--side-pixel node) (ebox--content-max-pixel node))
|
||||
(ebox--string-max-pixel-width string)))
|
||||
(_ (ebox--string-pixel-width string))))))
|
||||
|
||||
(defun ebox-grid--track-intrinsic (track axis entries rendered key slot-index)
|
||||
"Return intrinsic size for TRACK from RENDERED grid ENTRIES."
|
||||
(pcase (plist-get track :kind)
|
||||
@ -500,6 +546,7 @@ programmatic port and are therefore rejected here."
|
||||
(ebox-grid--track-intrinsic (plist-get track :min)
|
||||
axis entries rendered key slot-index))
|
||||
(_ (let ((position (1+ slot-index))
|
||||
(mode (plist-get track :kind))
|
||||
(best 0))
|
||||
(dolist (entry entries best)
|
||||
(when (and (<= (plist-get entry key) position)
|
||||
@ -509,11 +556,9 @@ programmatic port and are therefore rejected here."
|
||||
(if (eq key :column)
|
||||
:column-span
|
||||
:row-span)))))
|
||||
(let* ((node (plist-get entry :node))
|
||||
(string (gethash node rendered))
|
||||
(value (if (eq axis 'columns)
|
||||
(ebox--string-pixel-width string)
|
||||
(ebox-string-height string))))
|
||||
(let ((value
|
||||
(ebox-grid--entry-intrinsic-size
|
||||
entry rendered axis mode)))
|
||||
(setq best (max best value)))))))))
|
||||
|
||||
(defun ebox-grid--sum (values)
|
||||
|
||||
@ -6598,12 +6598,10 @@ recognized from its first untouched sibling without walking the whole tree."
|
||||
|
||||
(defun ebox-incremental--declaration-changed-keys (old new)
|
||||
"Return engine longhands changed between canonical OLD and NEW declarations."
|
||||
(let ((all-properties (lambda (_property) t)))
|
||||
(ebox-incremental--plist-changed-keys
|
||||
(ebox-style-expand-ebox-plist
|
||||
(ebox-style-declaration-properties old all-properties))
|
||||
(ebox-style-expand-ebox-plist
|
||||
(ebox-style-declaration-properties new all-properties)))))
|
||||
(delete-dups
|
||||
(cl-mapcan
|
||||
#'ebox-style--engine-projection-targets
|
||||
(ebox-incremental--plist-changed-keys old new))))
|
||||
|
||||
(defun ebox-incremental--candidate-source-record (source-index node)
|
||||
"Return NODE's borrowed immutable record from candidate SOURCE-INDEX.
|
||||
@ -9507,8 +9505,8 @@ role, and outside-complement compatibility before publication."
|
||||
(or (eq key :content)
|
||||
(eq key :surface-properties)
|
||||
(memq key ebox--paint-style-signature-keys)
|
||||
(memq key '(:font :font-family :font-height
|
||||
:font-weight :font-slant))))
|
||||
(memq key '(:font-family :font-size
|
||||
:font-weight :font-style))))
|
||||
(plist-get entry :changed-keys))))
|
||||
dirty-set)
|
||||
allocation-stable-p
|
||||
|
||||
@ -124,7 +124,8 @@ PROPS must already use the variant's normalized representation."
|
||||
:grid-auto-columns :grid-auto-rows :grid-auto-flow
|
||||
:row-gap :column-gap
|
||||
:justify-items :align-items :justify-content :align-content))
|
||||
((or 'box 'row 'column) nil)
|
||||
('box nil)
|
||||
((or 'row 'column) '(:item-gap :cross-align))
|
||||
(_ (error "Unknown Ebox Box author form: %S" tag))))
|
||||
|
||||
(defun ebox-layout-config-for-form (tag properties)
|
||||
|
||||
@ -58,8 +58,12 @@
|
||||
"ebox-buffer-backend" (string property region-id))
|
||||
(declare-function ebox--propertize-typography
|
||||
"ebox-buffer-backend" (string style))
|
||||
(declare-function ebox--propertize-text-decoration
|
||||
"ebox-buffer-backend" (string style))
|
||||
(declare-function ebox--propertize-underline
|
||||
"ebox-buffer-backend" (string &optional color))
|
||||
(declare-function ebox-buffer-validate-border-capability
|
||||
"ebox-buffer-backend" (style))
|
||||
|
||||
(defvar ebox--render-runtime-revision nil
|
||||
"Current buffer runtime revision for render-local cache validation.")
|
||||
@ -230,8 +234,8 @@ the generic box pipeline."
|
||||
(equal (ebox-get box :margin-bottom-height) 0)
|
||||
(equal (ebox-get box :border-left-pixel) 0)
|
||||
(equal (ebox-get box :border-right-pixel) 0)
|
||||
(not (ebox-get box :border-top-p))
|
||||
(not (ebox-get box :border-bottom-p))
|
||||
(= (ebox-get box :border-top-pixel) 0)
|
||||
(= (ebox-get box :border-bottom-pixel) 0)
|
||||
(null (and (ebox-style--text-paint-owner-p box)
|
||||
(ebox-get box :color)))
|
||||
(null (ebox-get box :bgcolor)))
|
||||
@ -296,8 +300,8 @@ and its formatted content exactly fills the used height."
|
||||
(= (floor (ebox-get box :padding-bottom-height)) 0)
|
||||
(= (ebox-get box :border-left-pixel) 0)
|
||||
(= (ebox-get box :border-right-pixel) 0)
|
||||
(not (ebox-get box :border-top-p))
|
||||
(not (ebox-get box :border-bottom-p)))))
|
||||
(= (ebox-get box :border-top-pixel) 0)
|
||||
(= (ebox-get box :border-bottom-pixel) 0))))
|
||||
(when (and lines-fit-p
|
||||
(or (= text-height content-height)
|
||||
underfilled-simple-scroll-p))
|
||||
@ -1203,7 +1207,8 @@ FALLBACK is used for nil, auto, or unavailable viewport-height values."
|
||||
;; typography contribution around already-rendered descendants.
|
||||
(if (eq (plist-get box :ebox-kind) 'box)
|
||||
content
|
||||
(ebox--propertize-typography content box))))
|
||||
(ebox--propertize-text-decoration
|
||||
(ebox--propertize-typography content box) box))))
|
||||
|
||||
(defun ebox--intrinsic-box-content (box)
|
||||
"Return BOX content materialized independently of its used width.
|
||||
@ -1556,9 +1561,9 @@ Internal implementation of `ebox-render' for box nodes."
|
||||
(border-left-color (ebox-get box :border-left-color))
|
||||
(border-right-pixel (ebox-get box :border-right-pixel))
|
||||
(border-right-color (ebox-get box :border-right-color))
|
||||
(border-top-p (ebox-get box :border-top-p))
|
||||
(border-top-p (> (ebox-get box :border-top-pixel) 0))
|
||||
(border-top-color (ebox-get box :border-top-color))
|
||||
(border-bottom-p (ebox-get box :border-bottom-p))
|
||||
(border-bottom-p (> (ebox-get box :border-bottom-pixel) 0))
|
||||
(border-bottom-color (ebox-get box :border-bottom-color))
|
||||
|
||||
;; Style and Layout
|
||||
@ -1887,7 +1892,9 @@ Internal implementation of `ebox-render' for box nodes."
|
||||
(let* ((region-id (or ebox--render-region-id
|
||||
(ebox--ensure-region-id node)))
|
||||
(value (copy-sequence (plist-get node :ebox-text-value)))
|
||||
(styled (ebox--propertize-typography value node))
|
||||
(styled
|
||||
(ebox--propertize-text-decoration
|
||||
(ebox--propertize-typography value node) node))
|
||||
(painted
|
||||
(if (or (ebox-get node :color) (ebox-get node :bgcolor))
|
||||
(ebox--propertize-colors
|
||||
@ -1921,7 +1928,8 @@ This function owns spatial layout only; it never creates a live TP surface."
|
||||
(or ebox--box-content-render-cache
|
||||
(make-hash-table :test 'eq))))
|
||||
(when (listp node)
|
||||
(ebox--ensure-node-id node))
|
||||
(ebox--ensure-node-id node)
|
||||
(ebox-buffer-validate-border-capability node))
|
||||
(let ((type (plist-get node :ebox-type)))
|
||||
(cond
|
||||
((eq (plist-get node :ebox-kind) 'text)
|
||||
@ -2815,15 +2823,12 @@ last cached leaf instead of walking the stack from the beginning."
|
||||
:margin-left-pixel :margin-right-pixel
|
||||
:margin-top-height :margin-bottom-height
|
||||
:border-left-pixel :border-right-pixel))
|
||||
(not (ebox-get node :border-top-p))
|
||||
(not (ebox-get node :border-bottom-p))
|
||||
(= (ebox-get node :border-top-pixel) 0)
|
||||
(= (ebox-get node :border-bottom-pixel) 0)
|
||||
(null (ebox-get node :color))
|
||||
(null (ebox-get node :bgcolor))
|
||||
(null (ebox-get node :font))
|
||||
(null (ebox-get node :font-family))
|
||||
(null (ebox-get node :font-height))
|
||||
(null (ebox-get node :font-weight))
|
||||
(null (ebox-get node :font-slant))
|
||||
(or (null (plist-get node :ebox-font-fact))
|
||||
(ebox-font-fact-neutral-p (plist-get node :ebox-font-fact)))
|
||||
(null (ebox-get node :surface-properties))
|
||||
(eq (ebox-get node :visibility) 'visible)
|
||||
(eq (ebox-get node :text-align) 'left)
|
||||
@ -2951,10 +2956,11 @@ no included vertical margin."
|
||||
0))
|
||||
(border-left-pixel (ebox-get box :border-left-pixel))
|
||||
(border-right-pixel (ebox-get box :border-right-pixel))
|
||||
(border-top-p (and include-top (ebox-get box :border-top-p)))
|
||||
(border-top-p
|
||||
(and include-top (> (ebox-get box :border-top-pixel) 0)))
|
||||
(border-top-color (ebox-get box :border-top-color))
|
||||
(border-bottom-p
|
||||
(and include-bottom (ebox-get box :border-bottom-p)))
|
||||
(and include-bottom (> (ebox-get box :border-bottom-pixel) 0)))
|
||||
(border-bottom-color (ebox-get box :border-bottom-color))
|
||||
(color (and (ebox-style--text-paint-owner-p box)
|
||||
(ebox-get box :color)))
|
||||
@ -3128,7 +3134,7 @@ only used for incomplete lazy prefixes."
|
||||
(border-right-pixel (ebox-get box :border-right-pixel))
|
||||
(border-right-color (ebox-get box :border-right-color))
|
||||
(border-top-p (and include-top
|
||||
(ebox-get box :border-top-p)))
|
||||
(> (ebox-get box :border-top-pixel) 0)))
|
||||
(border-top-color (ebox-get box :border-top-color))
|
||||
(color (and (ebox-style--text-paint-owner-p box)
|
||||
(ebox-get box :color)))
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'ebox-cache)
|
||||
(require 'ebox-font)
|
||||
|
||||
(declare-function ebox--register-render-owned-text-value
|
||||
"ebox-render-context" (property value))
|
||||
@ -251,21 +252,29 @@ switching those layers must not invalidate Ebox measurement caches."
|
||||
This is the public, text-scale-aware measurement used by Ebox layout code."
|
||||
(ebox--string-pixel-width string))
|
||||
|
||||
(defun ebox--display-signature ()
|
||||
"Return the current display signature for pixel measurement caches."
|
||||
(list (frame-parameter nil 'font)
|
||||
(frame-parameter nil 'font-backend)
|
||||
(frame-parameter nil 'line-spacing)
|
||||
(frame-parameter nil 'internal-border-width)
|
||||
(face-attribute 'default :family nil t)
|
||||
(face-attribute 'default :height nil t)
|
||||
(face-attribute 'default :weight nil t)
|
||||
(face-attribute 'default :slant nil t)
|
||||
(face-attribute 'default :width nil t)
|
||||
(face-attribute 'default :foreground nil t)
|
||||
(face-attribute 'default :background nil t)
|
||||
(defun ebox--display-signature-for-frame (frame)
|
||||
"Return the pixel-measurement capability signature for FRAME."
|
||||
(list frame
|
||||
(frame-parameter frame 'font)
|
||||
(frame-parameter frame 'font-backend)
|
||||
(frame-parameter frame 'line-spacing)
|
||||
(face-attribute 'default :family frame t)
|
||||
(face-attribute 'default :height frame t)
|
||||
(face-attribute 'default :weight frame t)
|
||||
(face-attribute 'default :slant frame t)
|
||||
(face-attribute 'default :width frame t)
|
||||
(ebox--measurement-face-remapping)
|
||||
(and (boundp 'text-scale-mode-amount) text-scale-mode-amount)))
|
||||
(and (boundp 'text-scale-mode-amount) text-scale-mode-amount)
|
||||
(and (boundp 'text-scale-mode-step) text-scale-mode-step)))
|
||||
|
||||
(defun ebox--display-signature-for-window (window)
|
||||
"Return the pixel-measurement capability signature for live WINDOW."
|
||||
(ebox--display-signature-for-frame
|
||||
(if (window-live-p window) (window-frame window) (selected-frame))))
|
||||
|
||||
(defun ebox--display-signature ()
|
||||
"Return the selected frame's pixel-measurement capability signature."
|
||||
(ebox--display-signature-for-frame (selected-frame)))
|
||||
|
||||
(defsubst ebox--current-display-signature ()
|
||||
"Return the render snapshot or sample the current display signature."
|
||||
@ -336,6 +345,7 @@ Nested uses reuse the outer validation."
|
||||
"Clear pixel width caches after font or named face metrics change."
|
||||
(clrhash ebox--char-width-cache)
|
||||
(ebox--clear-string-pixel-width-cache)
|
||||
(ebox-font-clear-cache)
|
||||
(setq ebox--space-pixel-cache nil)
|
||||
(setq ebox--face-height-width-cache nil)
|
||||
(setq ebox--display-signature-cache (ebox--display-signature)))
|
||||
|
||||
@ -53,6 +53,8 @@
|
||||
(declare-function ebox-layout-config-props "ebox-layout-config" (config))
|
||||
(declare-function ebox-tree-node-children "ebox-tree" (node))
|
||||
(declare-function ebox-buffer--font-face "ebox-buffer-backend" (style))
|
||||
(declare-function ebox--propertize-typography
|
||||
"ebox-buffer-backend" (string style))
|
||||
(declare-function ebox--ensure-node-id "ebox" (node))
|
||||
(declare-function ebox--buffer-render-state "ebox-incremental" (buffer))
|
||||
(declare-function ebox--ensure-region-id "ebox" (box))
|
||||
@ -77,7 +79,11 @@
|
||||
(declare-function ebox-pixel-space "ebox-measure" (pixel-width))
|
||||
(declare-function ebox-string-lines "ebox" (string))
|
||||
(declare-function ebox-string-pixel-width "ebox-measure" (string))
|
||||
(declare-function ebox-display-signature "ebox-measure" ())
|
||||
(declare-function ebox--current-display-signature "ebox-measure" ())
|
||||
(declare-function ebox--display-signature-for-window
|
||||
"ebox-measure" (window))
|
||||
(declare-function ebox-surface--buffer-display-window
|
||||
"ebox-surface" (buffer))
|
||||
(declare-function tp-property-value-copy "tp-core" (value))
|
||||
(declare-function tp-paint-slot-p "tp-style" (value))
|
||||
(declare-function tp-paint-slot-face "tp-style" (slot))
|
||||
@ -1640,8 +1646,8 @@ Rust still verifies the dynamic equal-width and nonempty-line proof per frame."
|
||||
:margin-left-pixel :margin-right-pixel
|
||||
:margin-top-height :margin-bottom-height
|
||||
:border-left-pixel :border-right-pixel))
|
||||
(not (ebox-get box :border-top-p))
|
||||
(not (ebox-get box :border-bottom-p))
|
||||
(= (ebox-get box :border-top-pixel) 0)
|
||||
(= (ebox-get box :border-bottom-pixel) 0)
|
||||
(null (ebox-get box :color))
|
||||
(null (ebox-get box :bgcolor))))
|
||||
|
||||
@ -1743,9 +1749,16 @@ cross-axis stretch and item gap remain explicit without legacy runtime nodes."
|
||||
(defun ebox-native-reflow--compile-text-node (node)
|
||||
"Compile canonical Text NODE to the lightweight native Text IR."
|
||||
(let* ((region-id (ebox--ensure-region-id node))
|
||||
(content
|
||||
(ebox--propertize-typography
|
||||
(or (plist-get node :ebox-text-value)
|
||||
(ebox-get node :content)
|
||||
"")
|
||||
node))
|
||||
(typography-style
|
||||
(ebox-native-reflow--compile-add-face-style
|
||||
(ebox-buffer--font-face node)))
|
||||
(append (ebox-buffer--font-face node)
|
||||
(ebox-buffer--text-decoration-face node))))
|
||||
(foreground-style
|
||||
(ebox-native-reflow--compile-add-face-style
|
||||
(ebox-native-reflow--compile-foreground-face
|
||||
@ -1757,10 +1770,7 @@ cross-axis stretch and item gap remain explicit without legacy runtime nodes."
|
||||
(list :type "text"
|
||||
:region-id region-id
|
||||
:content
|
||||
(ebox-native-reflow--compile-text
|
||||
(or (plist-get node :ebox-text-value)
|
||||
(ebox-get node :content)
|
||||
""))
|
||||
(ebox-native-reflow--compile-text content)
|
||||
:typography-style (or typography-style :null)
|
||||
:foreground-style (or foreground-style :null)
|
||||
:surface-template-id (or surface-template-id :null)
|
||||
@ -1800,6 +1810,14 @@ cross-axis stretch and item gap remain explicit without legacy runtime nodes."
|
||||
(margin-bottom (floor (or (ebox-get box :margin-bottom-height) 0)))
|
||||
(border-left (floor (or (ebox-get box :border-left-pixel) 0)))
|
||||
(border-right (floor (or (ebox-get box :border-right-pixel) 0)))
|
||||
(border-top (floor (or (ebox-get box :border-top-pixel) 0)))
|
||||
(border-bottom (floor (or (ebox-get box :border-bottom-pixel) 0)))
|
||||
(_border-capability
|
||||
(when (or (> border-top 1) (> border-bottom 1))
|
||||
(signal 'ebox-surface-capability-error
|
||||
(list :border '(top bottom)
|
||||
:declared-pixels (max border-top border-bottom)
|
||||
:supported-pixels '(0 1)))))
|
||||
(foreground-style
|
||||
(ebox-native-reflow--compile-add-face-style
|
||||
(ebox-native-reflow--compile-foreground-face
|
||||
@ -1808,7 +1826,8 @@ cross-axis stretch and item gap remain explicit without legacy runtime nodes."
|
||||
(typography-style
|
||||
(and (ebox-style--text-paint-owner-p box)
|
||||
(ebox-native-reflow--compile-add-face-style
|
||||
(ebox-buffer--font-face box))))
|
||||
(append (ebox-buffer--font-face box)
|
||||
(ebox-buffer--text-decoration-face box)))))
|
||||
(background-style
|
||||
(ebox-native-reflow--compile-add-face-style
|
||||
(let ((background (ebox-get box :bgcolor)))
|
||||
@ -1823,14 +1842,14 @@ cross-axis stretch and item gap remain explicit without legacy runtime nodes."
|
||||
(ebox-native-reflow--compile-border-face-style
|
||||
border-right (ebox-get box :border-right-color)))
|
||||
(border-top-style
|
||||
(and (ebox-get box :border-top-p)
|
||||
(and (> border-top 0)
|
||||
(ebox-native-reflow--compile-add-face-style
|
||||
(let ((color (ebox-get box :border-top-color)))
|
||||
(if (tp-paint-slot-p color)
|
||||
(tp-paint-slot-face color)
|
||||
(list :overline (or color t)))))))
|
||||
(border-bottom-style
|
||||
(and (ebox-get box :border-bottom-p)
|
||||
(and (> border-bottom 0)
|
||||
(ebox-native-reflow--compile-add-face-style
|
||||
(let ((color (ebox-get box :border-bottom-color)))
|
||||
(if (tp-paint-slot-p color)
|
||||
@ -2220,6 +2239,12 @@ PROPERTY-TEMPLATES are Emacs-owned plists addressed by opaque native ids."
|
||||
:styles (vconcat styles)
|
||||
:property-templates (vconcat property-templates)))
|
||||
|
||||
(defun ebox-native-reflow--buffer-display-signature (buffer)
|
||||
"Return BUFFER's live canonical Surface display capability."
|
||||
(with-current-buffer buffer
|
||||
(ebox--display-signature-for-window
|
||||
(ebox-surface--buffer-display-window buffer))))
|
||||
|
||||
(defun ebox-native-reflow-capture-layout-scene (buffer)
|
||||
"Capture BUFFER's render-owned flat native scene without walking its tree."
|
||||
(unless (buffer-live-p buffer)
|
||||
@ -2238,7 +2263,7 @@ PROPERTY-TEMPLATES are Emacs-owned plists addressed by opaque native ids."
|
||||
:node-postorder postorder
|
||||
:flex-content-min-widths
|
||||
(plist-get state :flex-content-min-widths)
|
||||
:display-signature (ebox-display-signature)
|
||||
:display-signature (plist-get state :display-signature)
|
||||
:runtime-revision (or (plist-get state :runtime-revision) 0)))))
|
||||
|
||||
(defun ebox-native-reflow-layout-scene-new-builder (scene)
|
||||
@ -2264,10 +2289,8 @@ PROPERTY-TEMPLATES are Emacs-owned plists addressed by opaque native ids."
|
||||
(and (buffer-live-p buffer)
|
||||
(eq (ebox--buffer-render-state buffer)
|
||||
(ebox-native-reflow-layout-scene-render-state scene))
|
||||
(with-current-buffer buffer
|
||||
(equal (ebox-display-signature)
|
||||
(ebox-native-reflow-layout-scene-display-signature
|
||||
scene))))))
|
||||
(equal (ebox-native-reflow--buffer-display-signature buffer)
|
||||
(ebox-native-reflow-layout-scene-display-signature scene)))))
|
||||
|
||||
(defun ebox-native-reflow-layout-builder-step (builder budget-seconds)
|
||||
"Advance flat layout BUILDER for at most BUDGET-SECONDS.
|
||||
@ -2359,7 +2382,7 @@ per call, and no call recursively visits the captured Ebox tree."
|
||||
(ebox-native-reflow--compile-property-template-ids
|
||||
(make-hash-table :test 'equal))
|
||||
(ebox-native-reflow--compile-display-signature
|
||||
(ebox-display-signature))
|
||||
(ebox--current-display-signature))
|
||||
(ebox-native-reflow--compile-root-node node))
|
||||
(cl-loop
|
||||
for template in ebox-native-reflow--compile-property-templates
|
||||
@ -2457,7 +2480,7 @@ per call, and no call recursively visits the captured Ebox tree."
|
||||
(ebox-native-reflow--compile-property-template-ids
|
||||
(make-hash-table :test 'equal))
|
||||
(ebox-native-reflow--compile-display-signature
|
||||
(ebox-display-signature))
|
||||
(ebox--current-display-signature))
|
||||
(ebox-native-reflow--compile-root-node node)
|
||||
(ebox--render-string-pixel-width-cache
|
||||
(make-hash-table :test 'equal))
|
||||
@ -4353,8 +4376,7 @@ creating and releasing a session for this preparation."
|
||||
(or layout-package
|
||||
(ebox-native-reflow--compile-layout-package root)))
|
||||
(source-revision (or (plist-get state :runtime-revision) 0))
|
||||
(display-signature
|
||||
(with-current-buffer buffer (ebox-display-signature)))
|
||||
(display-signature (plist-get state :display-signature))
|
||||
(context-hash
|
||||
(sxhash-equal
|
||||
(list display-signature kind region-id
|
||||
@ -4505,11 +4527,10 @@ When FULL-P is non-nil, query the complete restoration job."
|
||||
(equal
|
||||
(plist-get state :runtime-revision)
|
||||
(ebox-native-reflow-preparation-expected-revision preparation))
|
||||
(with-current-buffer buffer
|
||||
(equal
|
||||
(ebox-display-signature)
|
||||
(ebox-native-reflow--buffer-display-signature buffer)
|
||||
(ebox-native-reflow-preparation-display-signature
|
||||
preparation))))
|
||||
preparation)))
|
||||
(error "Preview runtime changed during native reflow preparation"))
|
||||
state))
|
||||
|
||||
|
||||
@ -18,18 +18,32 @@
|
||||
:padding-top-height 0 :padding-bottom-height 0
|
||||
:margin-left-pixel 0 :margin-right-pixel 0
|
||||
:margin-top-height 0 :margin-bottom-height 0
|
||||
:border-left-pixel 0 :border-left-color nil
|
||||
:border-right-pixel 0 :border-right-color nil
|
||||
:border-top-p nil :border-top-color nil
|
||||
:border-bottom-p nil :border-bottom-color nil
|
||||
:border-left-pixel 0 :border-left-style none :border-left-color nil
|
||||
:border-right-pixel 0 :border-right-style none :border-right-color nil
|
||||
:border-top-pixel 0 :border-top-style none :border-top-color nil
|
||||
:border-bottom-pixel 0 :border-bottom-style none :border-bottom-color nil
|
||||
:color nil :bgcolor nil
|
||||
:font nil :font-family nil :font-height nil
|
||||
:font-weight nil :font-slant nil
|
||||
:text-decoration-line none :text-decoration-color currentColor
|
||||
:text-decoration-style solid
|
||||
:text-align left :vertical-align top
|
||||
:overflow scroll :visibility visible
|
||||
:wrap-mode word :scroll-offset 0)
|
||||
"Default longhand fields for the current runtime box encoding.")
|
||||
|
||||
(defconst ebox-node-factory--style-projection-keys
|
||||
(let ((fields ebox--longhand)
|
||||
keys)
|
||||
(while fields
|
||||
(let ((key (pop fields)))
|
||||
(pop fields)
|
||||
(unless (memq key '(:content :scroll-offset))
|
||||
(push key keys))))
|
||||
(nreverse keys))
|
||||
"Private runtime fields materialized from canonical style facts.
|
||||
These fields are projections, never declarative source authority. Content and
|
||||
scroll offset remain outside this set because their lifecycle is not owned by
|
||||
style projection.")
|
||||
|
||||
(defconst ebox-node-factory--text-longhand
|
||||
'(:content ""
|
||||
:overflow visible
|
||||
@ -44,7 +58,10 @@
|
||||
:padding-top-height 0 :padding-bottom-height 0
|
||||
:margin-left-pixel 0 :margin-right-pixel 0
|
||||
:margin-top-height 0 :margin-bottom-height 0
|
||||
:border-left-pixel 0 :border-right-pixel 0
|
||||
:border-left-pixel 0 :border-left-style none
|
||||
:border-right-pixel 0 :border-right-style none
|
||||
:border-top-pixel 0 :border-top-style none
|
||||
:border-bottom-pixel 0 :border-bottom-style none
|
||||
:text-align left :vertical-align top
|
||||
:overflow scroll :visibility visible
|
||||
:wrap-mode word :scroll-offset 0)
|
||||
@ -87,7 +104,7 @@ leaving them absent would make the canonical node violate the Box contract.")
|
||||
(defun ebox-node-factory--create-from-properties (&rest plist)
|
||||
"Create one private runtime box from evaluated engine PLIST."
|
||||
(ebox-node-factory--create-expanded
|
||||
(ebox-style-expand-ebox-plist plist)))
|
||||
(ebox-style--expand-engine-plist plist)))
|
||||
|
||||
(provide 'ebox-node-factory)
|
||||
|
||||
|
||||
@ -216,6 +216,23 @@ Functions and records are opaque values and retain identity."
|
||||
copy))
|
||||
(t value)))
|
||||
|
||||
(defun ebox-source-stable-key-p (value)
|
||||
"Return non-nil when VALUE is one public stable source key."
|
||||
(and value (or (symbolp value) (stringp value) (integerp value))))
|
||||
|
||||
(defun ebox-source-semantic-id-p (value)
|
||||
"Return non-nil when VALUE is one public semantic id."
|
||||
(and value (or (symbolp value) (stringp value) (integerp value))))
|
||||
|
||||
(defun ebox-source-class-list-p (value)
|
||||
"Return non-nil when VALUE is a public class token or proper token list."
|
||||
(or (symbolp value)
|
||||
(stringp value)
|
||||
(and (proper-list-p value)
|
||||
(cl-every (lambda (token)
|
||||
(or (symbolp token) (stringp token)))
|
||||
value))))
|
||||
|
||||
(defun ebox-source--metadata-string (value)
|
||||
"Return VALUE normalized to a selector metadata string."
|
||||
(cond
|
||||
@ -299,6 +316,15 @@ Functions and records are opaque values and retain identity."
|
||||
"Bind normalized source facts in BUILDER and return an opaque handle.
|
||||
The returned handle contains identity only; all facts are owned by BUILDER
|
||||
until `ebox-source-builder-finish' seals them into a source index."
|
||||
(when (and key (not (ebox-source-stable-key-p key)))
|
||||
(error "Ebox source key must be a non-nil symbol, string, or integer: %S"
|
||||
key))
|
||||
(when (and id (not (ebox-source-semantic-id-p id)))
|
||||
(error "Ebox source id must be a non-nil symbol, string, or integer: %S"
|
||||
id))
|
||||
(unless (ebox-source-class-list-p class)
|
||||
(error "Ebox source class must be a token or proper token list: %S"
|
||||
class))
|
||||
(let ((handle
|
||||
(ebox-source--make-handle
|
||||
:identity (ebox-source--copy-data
|
||||
|
||||
1259
ebox-style.el
1259
ebox-style.el
File diff suppressed because it is too large
Load Diff
149
ebox-surface.el
149
ebox-surface.el
@ -13,6 +13,7 @@
|
||||
(require 'cl-lib)
|
||||
(require 'seq)
|
||||
(require 'ebox-buffer-backend)
|
||||
(require 'ebox-font)
|
||||
(require 'ebox-style)
|
||||
(require 'ebox-tree)
|
||||
(require 'ebox-layout)
|
||||
@ -49,6 +50,9 @@
|
||||
(defvar ebox-surface--inline-style-value-cache nil
|
||||
"Per-projection cache for selector-free computed inline styles.")
|
||||
|
||||
(defvar ebox-surface--font-window nil
|
||||
"Canonical target window frozen for one Surface projection.")
|
||||
|
||||
(defun ebox-surface--mixed-range-splice-p (state)
|
||||
"Return non-nil when STATE's mixed proof contains one Range splice."
|
||||
(cl-some
|
||||
@ -633,7 +637,8 @@ and resize paths must consume this same choice."
|
||||
:display-signature
|
||||
(value :display-signature
|
||||
(with-current-buffer buffer
|
||||
(ebox--current-display-signature)))
|
||||
(ebox--display-signature-for-window window)))
|
||||
:display-window window
|
||||
:scroll-offsets
|
||||
(ebox-surface--scroll-offsets scroll-table)))))
|
||||
|
||||
@ -1025,6 +1030,8 @@ FRAMEWORK-PARTICIPANT retains post-TP publication facts when non-nil."
|
||||
success)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(let ((ebox-surface--font-window
|
||||
(plist-get context-values :display-window)))
|
||||
(tp-with-transaction
|
||||
(ebox-surface--stage-signal-values signals context-values)
|
||||
(if surface
|
||||
@ -1041,7 +1048,7 @@ FRAMEWORK-PARTICIPANT retains post-TP publication facts when non-nil."
|
||||
(list :observers (list bridge)))))))
|
||||
(ebox-surface--publish-runtime-state
|
||||
buffer surface old-state report-base after-publication
|
||||
framework-participant observation-context))
|
||||
framework-participant observation-context)))
|
||||
(setq success t)
|
||||
(let ((scroll-keys
|
||||
(if framework-participant
|
||||
@ -1124,9 +1131,10 @@ FRAMEWORK-PARTICIPANT retains post-TP publication facts when non-nil."
|
||||
(let* ((old-width (plist-get state :viewport-width))
|
||||
(old-height (plist-get state :viewport-height))
|
||||
(target-height (or height old-height))
|
||||
(window (ebox-surface--buffer-display-window buffer))
|
||||
(display-signature
|
||||
(with-current-buffer buffer
|
||||
(ebox--current-display-signature)))
|
||||
(ebox--display-signature-for-window window)))
|
||||
(display-changed
|
||||
(not (equal display-signature
|
||||
(plist-get state :display-signature))))
|
||||
@ -1859,6 +1867,8 @@ FRAMEWORK-PARTICIPANT retains post-TP publication facts when non-nil."
|
||||
(setq on-mismatch 'root))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(let ((ebox-surface--font-window
|
||||
(plist-get context-values :display-window)))
|
||||
(tp-with-transaction
|
||||
(ebox-surface--stage-signal-values signals context-values)
|
||||
(tp-surface-update-scoped
|
||||
@ -1866,7 +1876,7 @@ FRAMEWORK-PARTICIPANT retains post-TP publication facts when non-nil."
|
||||
(list :on-mismatch on-mismatch :return-report nil))
|
||||
(ebox-surface--publish-runtime-state
|
||||
buffer surface old-state report-base after-publication
|
||||
framework-participant observation-context))
|
||||
framework-participant observation-context)))
|
||||
(setq success t)
|
||||
(let ((scroll-keys
|
||||
(if framework-participant
|
||||
@ -2043,6 +2053,7 @@ published generation so a failed transaction has no cache state to restore."
|
||||
(plist-put reused-state :selector-tree-token
|
||||
selector-tree-token)
|
||||
(plist-put reused-state :subject subject)
|
||||
(plist-put reused-state :node node)
|
||||
(plist-put reused-state :parent-style-state
|
||||
parent-style-state)
|
||||
reused-state)
|
||||
@ -2058,7 +2069,8 @@ published generation so a failed transaction has no cache state to restore."
|
||||
0
|
||||
(if parent-binding 1 0)))
|
||||
:stylesheet-signature stylesheet-signature
|
||||
:subject subject)))
|
||||
:subject subject
|
||||
:node node)))
|
||||
(plist-put
|
||||
new-state :compute
|
||||
(let ((captured new-state)
|
||||
@ -2142,6 +2154,38 @@ published generation so a failed transaction has no cache state to restore."
|
||||
(puthash key style ebox-surface--inline-style-value-cache)
|
||||
style)))))
|
||||
|
||||
(defun ebox-surface--project-font
|
||||
(node style &optional parent-font-fact snapshot)
|
||||
"Install NODE's font fact from STYLE, capability, and PARENT-FONT-FACT."
|
||||
(let ((snapshot (or snapshot (ebox-style--computed-snapshot style t))))
|
||||
(plist-put
|
||||
node :ebox-font-fact
|
||||
(ebox-font-project-values
|
||||
(car snapshot) (nth 2 snapshot)
|
||||
(ebox--current-display-signature)
|
||||
(or ebox-surface--font-window
|
||||
(ebox-surface--buffer-display-window (current-buffer)))
|
||||
parent-font-fact)))
|
||||
node)
|
||||
|
||||
(defun ebox-surface--refresh-font-projections (root)
|
||||
"Re-resolve retained computed font facts below candidate ROOT.
|
||||
This is a host-capability projection only: it reuses each immutable computed
|
||||
style and never re-runs selector matching or cascade."
|
||||
(cl-labels
|
||||
((visit (node parent-font-fact)
|
||||
(when (and (listp node) (not (stringp node)))
|
||||
(when-let* ((style (plist-get node :ebox-computed-style))
|
||||
((ecss-computed-style-p style)))
|
||||
(ebox-surface--project-font
|
||||
node style parent-font-fact
|
||||
(ebox-style--computed-snapshot style t)))
|
||||
(let ((fact (plist-get node :ebox-font-fact)))
|
||||
(ebox-tree-for-each-direct-child
|
||||
node (lambda (child) (visit child fact)))))))
|
||||
(visit root nil))
|
||||
root)
|
||||
|
||||
(defun ebox-surface--closed-canonical-node-p (source-index node)
|
||||
"Return non-nil when NODE in SOURCE-INDEX needs closed inheritance."
|
||||
(let ((declarations
|
||||
@ -2260,6 +2304,14 @@ source-handle-to-subject table when the requested local preparation completed."
|
||||
subject declarations
|
||||
parent-style)))))
|
||||
(when (and node style (not (eq node old-node)))
|
||||
(let ((snapshot
|
||||
(ebox-style--computed-snapshot style t)))
|
||||
(ebox-surface--project-font
|
||||
node style
|
||||
(and parent-id
|
||||
(plist-get (gethash parent-id new-nodes)
|
||||
:ebox-font-fact))
|
||||
snapshot)
|
||||
(cond
|
||||
((and source-stable-p
|
||||
(ebox-style--computed-projection-transferable-p
|
||||
@ -2269,9 +2321,10 @@ source-handle-to-subject table when the requested local preparation completed."
|
||||
((and (null (plist-get node :ebox-computed-style))
|
||||
(ebox-surface--closed-canonical-node-p
|
||||
new-source-index node))
|
||||
(ebox-style-apply-closed-inheritance node style))
|
||||
(ebox-style-apply-closed-inheritance
|
||||
node style snapshot))
|
||||
(t
|
||||
(ebox-style-apply-computed node style)))
|
||||
(ebox-style-apply-computed node style snapshot))))
|
||||
(plist-put node :ebox-computed-style style)
|
||||
(plist-put node :ebox-candidate-computed-style-p t))
|
||||
(remhash node-id visiting)
|
||||
@ -2303,6 +2356,16 @@ source-handle-to-subject table when the requested local preparation completed."
|
||||
(when-let* ((subject
|
||||
(ebox-source--subject-table-get
|
||||
subject-table (ebox-tree-node-source-handle node))))
|
||||
(let* ((parent-subject (ecss-subject-parent subject))
|
||||
(parent-state (and parent-subject
|
||||
(gethash parent-subject states-by-subject)))
|
||||
(parent-node (and parent-state (plist-get parent-state :node)))
|
||||
(parent-font-fact
|
||||
(and parent-node (plist-get parent-node :ebox-font-fact)))
|
||||
(precomputed-p
|
||||
(plist-get node :ebox-candidate-computed-style-p))
|
||||
(precomputed-style
|
||||
(and precomputed-p (plist-get node :ebox-computed-style))))
|
||||
(if (and (hash-table-p ebox-surface--inline-style-value-cache)
|
||||
(not (ebox-surface--node-style-binding-required-p
|
||||
source-index node)))
|
||||
@ -2312,9 +2375,16 @@ source-handle-to-subject table when the requested local preparation completed."
|
||||
(when-let* ((state (gethash parent states-by-subject)))
|
||||
(puthash subject state states-by-subject))
|
||||
(let ((style (tp-binding-read binding)))
|
||||
(unless (and precomputed-p (equal style precomputed-style))
|
||||
(let ((snapshot (ebox-style--computed-snapshot style t)))
|
||||
(ebox-surface--project-font
|
||||
node style parent-font-fact snapshot)
|
||||
(unless (equal style (plist-get node :ebox-computed-style))
|
||||
(ebox-style-apply-closed-inheritance node style))))
|
||||
(let ((style
|
||||
(ebox-style-apply-closed-inheritance
|
||||
node style snapshot)))))
|
||||
(when precomputed-p
|
||||
(cl-remf node :ebox-candidate-computed-style-p)))
|
||||
(let* ((style
|
||||
(tp-binding-read
|
||||
(ebox-surface--node-style-binding
|
||||
source-index object node subject
|
||||
@ -2322,12 +2392,15 @@ source-handle-to-subject table when the requested local preparation completed."
|
||||
style-state-table
|
||||
stylesheet-signature selector-tree-token
|
||||
stylesheet-subject-local-p))))
|
||||
(unless (and precomputed-p (equal style precomputed-style))
|
||||
(let ((snapshot (ebox-style--computed-snapshot style t)))
|
||||
(ebox-surface--project-font node style parent-font-fact snapshot)
|
||||
(unless (equal style (plist-get node :ebox-computed-style))
|
||||
(if (and (null (plist-get node :ebox-computed-style))
|
||||
(ebox-surface--closed-canonical-node-p
|
||||
source-index node))
|
||||
(ebox-style-apply-closed-inheritance node style)
|
||||
(ebox-style-apply-computed node style)))))))
|
||||
(ebox-style-apply-closed-inheritance node style snapshot)
|
||||
(ebox-style-apply-computed node style snapshot))))))))))
|
||||
|
||||
(defun ebox-surface--static-style-state-p (state &optional seen)
|
||||
"Return non-nil when STATE's inherited binding closure is static.
|
||||
@ -3611,8 +3684,8 @@ box with wrapper chrome falls back to the retained wrapper renderer."
|
||||
(= (or (ebox-get cached-box :margin-bottom-height) 0) 0)
|
||||
(= (or (ebox-get cached-box :border-left-pixel) 0) 0)
|
||||
(= (or (ebox-get cached-box :border-right-pixel) 0) 0)
|
||||
(not (ebox-get cached-box :border-top-p))
|
||||
(not (ebox-get cached-box :border-bottom-p))
|
||||
(= (or (ebox-get cached-box :border-top-pixel) 0) 0)
|
||||
(= (or (ebox-get cached-box :border-bottom-pixel) 0) 0)
|
||||
(eq (ebox-get cached-box :vertical-align) 'top)
|
||||
(null (ebox-get cached-box :surface-properties))))
|
||||
(visible
|
||||
@ -4459,6 +4532,9 @@ NODE-OBJECTS and REGION-OBJECTS supply retained ownership ranges."
|
||||
(when (ebox-style--text-paint-owner-p box)
|
||||
(when-let* ((font (ebox-buffer--font-face box)))
|
||||
(push font faces))
|
||||
(when-let* ((decoration
|
||||
(ebox-buffer--text-decoration-face box)))
|
||||
(push decoration faces))
|
||||
(when-let* ((color (plist-get box :color)))
|
||||
(push (ebox-surface--foreground-face color) faces)))
|
||||
(when-let* ((background (plist-get box :bgcolor)))
|
||||
@ -4482,12 +4558,14 @@ ANCESTOR-P means BOX wraps a more specific rendered surface."
|
||||
((or ancestor-p
|
||||
(cl-intersection roles '(content content-owner pt pb pl pr)))
|
||||
(setq faces (ebox-surface--box-face-contributions box))))
|
||||
(when (and (memq 'bt roles) (plist-get box :border-top-p))
|
||||
(when (and (memq 'bt roles)
|
||||
(> (or (plist-get box :border-top-pixel) 0) 0))
|
||||
(setq faces
|
||||
(append faces
|
||||
(list (list :overline
|
||||
(or (plist-get box :border-top-color) t))))))
|
||||
(when (and (memq 'bb roles) (plist-get box :border-bottom-p))
|
||||
(when (and (memq 'bb roles)
|
||||
(> (or (plist-get box :border-bottom-pixel) 0) 0))
|
||||
(setq faces
|
||||
(append
|
||||
faces
|
||||
@ -4698,10 +4776,15 @@ relative face contributions and performs the final merge during prepare."
|
||||
(when-let* ((node (gethash (plist-get dirty :node-id) nodes))
|
||||
(box (ebox-fragment-style-source-node node))
|
||||
(region-id (plist-get box :region-id)))
|
||||
(dolist (entry '((:border-top-p . bt) (:border-bottom-p . bb)))
|
||||
(when (memq (car entry) (plist-get dirty :changed-keys))
|
||||
(dolist (entry '(((:border-top-width :border-top-style)
|
||||
bt :border-top-pixel)
|
||||
((:border-bottom-width :border-bottom-style)
|
||||
bb :border-bottom-pixel)))
|
||||
(when (cl-intersection (car entry)
|
||||
(plist-get dirty :changed-keys))
|
||||
(ebox-surface--set-horizontal-border-role
|
||||
fragments region-id (cdr entry) (plist-get box (car entry)))))))))
|
||||
fragments region-id (cadr entry)
|
||||
(> (or (plist-get box (caddr entry)) 0) 0))))))))
|
||||
|
||||
(defun ebox-surface--repaint-fragment
|
||||
(fragment old-state new-state &optional old-face-cache new-face-cache)
|
||||
@ -4978,6 +5061,27 @@ published runtime and must be consumed without clearing those shared nodes."
|
||||
author-style-pending-p
|
||||
(plist-get previous-state :cascade-active-p)
|
||||
(plist-get previous-state :cascade-required-p))))
|
||||
(surface-display-signature
|
||||
(and signals
|
||||
(tp-signal-read
|
||||
(ebox-surface--signals-display signals))))
|
||||
;; Font resolution, text measurement, and final paint must see
|
||||
;; one immutable host capability for the whole transaction.
|
||||
(ebox--render-display-signature
|
||||
(or surface-display-signature
|
||||
ebox--render-display-signature
|
||||
(ebox--display-signature)))
|
||||
(ebox-surface--font-window
|
||||
(or ebox-surface--font-window
|
||||
(ebox-surface--buffer-display-window
|
||||
(current-buffer))))
|
||||
(_font-capability-projection
|
||||
(when (and previous-state surface-display-signature
|
||||
(not (equal
|
||||
surface-display-signature
|
||||
(plist-get previous-state
|
||||
:display-signature))))
|
||||
(ebox-surface--refresh-font-projections root)))
|
||||
(projection
|
||||
(ebox-surface--projection-start
|
||||
context root style-required-p projection-kind previous-state
|
||||
@ -5043,10 +5147,6 @@ published runtime and must be consumed without clearing those shared nodes."
|
||||
(funcall
|
||||
(if (cdr axes) #'tp-signal-read #'tp-signal-peek)
|
||||
(ebox-surface--signals-viewport-height signals))))
|
||||
(display-signature
|
||||
(and signals
|
||||
(tp-signal-read
|
||||
(ebox-surface--signals-display signals))))
|
||||
(scroll-offsets
|
||||
(and signals
|
||||
(tp-signal-peek
|
||||
@ -5055,7 +5155,7 @@ published runtime and must be consumed without clearing those shared nodes."
|
||||
(ebox-surface--projection-state
|
||||
root previous-state state-overrides projection-kind
|
||||
stylesheet-active-p cascade-required-p axes viewport-width
|
||||
viewport-height display-signature scroll-offsets)))
|
||||
viewport-height surface-display-signature scroll-offsets)))
|
||||
(plist-put state :source-index source-index)
|
||||
(when reuse-context-axes-p
|
||||
(plist-put state :viewport-dependent-node-ids
|
||||
@ -5140,7 +5240,8 @@ published runtime and must be consumed without clearing those shared nodes."
|
||||
(ebox-surface--projection-state
|
||||
root previous-state state-overrides nil
|
||||
stylesheet-active-p cascade-required-p axes
|
||||
viewport-width viewport-height display-signature
|
||||
viewport-width viewport-height
|
||||
surface-display-signature
|
||||
scroll-offsets))
|
||||
(plist-put state :source-index source-index)
|
||||
(plist-put state :style-binding-states
|
||||
|
||||
23
ebox-tree.el
23
ebox-tree.el
@ -13,6 +13,7 @@
|
||||
(require 'ecss-selector)
|
||||
(require 'ebox-source)
|
||||
(require 'ebox-style)
|
||||
(require 'ebox-node-factory)
|
||||
(require 'ebox-child-range)
|
||||
|
||||
(defvar ebox-tree--incoming-source-indexes nil
|
||||
@ -657,7 +658,7 @@ handles, reused node objects, and cycles without consulting node metadata."
|
||||
"Properties accepted only on a direct Flex child Box.")
|
||||
|
||||
(defconst ebox-tree--grid-participation-keys
|
||||
'(:grid-column :grid-row :grid-column-span :grid-row-span :justify-self)
|
||||
'(:grid-column :grid-row :justify-self)
|
||||
"Properties accepted only on a direct Grid child Box.")
|
||||
|
||||
(defconst ebox-tree--participation-keys
|
||||
@ -797,10 +798,12 @@ and all explicit identities are compared with `equal'. Return ROOT on success."
|
||||
(defconst ebox-tree--excluded-source-keys
|
||||
(append ebox-tree--runtime-source-keys
|
||||
ebox-tree--child-source-keys
|
||||
ebox-node-factory--style-projection-keys
|
||||
'(:ebox-style-overrides
|
||||
:ebox-source-handle
|
||||
:ebox-grid-config
|
||||
:ebox-computed-style :ebox-style-wrapper
|
||||
:ebox-font-fact
|
||||
:ebox-style-generated-wrapper
|
||||
:ebox-author-style-count :ebox-author-style-required-p
|
||||
:ebox-text-value :ebox-candidate-computed-style-p))
|
||||
@ -831,7 +834,7 @@ look like a geometry mutation."
|
||||
;; retained node may arrive once from an already-expanded style
|
||||
;; snapshot and later from fresh shorthand declarations; comparing
|
||||
;; those source spellings directly creates fake geometry dirtiness.
|
||||
(raw (ebox-style-expand-ebox-plist raw))
|
||||
(raw (ebox-style--expand-engine-plist raw))
|
||||
(table (make-hash-table :test #'eq)))
|
||||
(cl-loop for (key value) on raw by #'cddr
|
||||
unless (memq key '(:color :background-color :bgcolor :host-ref
|
||||
@ -840,19 +843,9 @@ look like a geometry mutation."
|
||||
(when (plist-member raw :gap)
|
||||
(let* ((gap (plist-get raw :gap))
|
||||
(row (if (consp gap) (car gap) gap))
|
||||
(column (if (and (consp gap) (cdr gap)) (cadr gap) row))
|
||||
;; Flex and Grid deliberately have separate canonical
|
||||
;; property vocabularies. Using Grid aliases for a Flex node
|
||||
;; makes an unchanged `gap' look like geometry dirtiness on
|
||||
;; the next retained generation.
|
||||
(row-key (if (eq (plist-get node :ebox-type) 'grid)
|
||||
:grid-row-gap
|
||||
:row-gap))
|
||||
(column-key (if (eq (plist-get node :ebox-type) 'grid)
|
||||
:grid-column-gap
|
||||
:column-gap)))
|
||||
(puthash row-key row table)
|
||||
(puthash column-key column table)
|
||||
(column (if (and (consp gap) (cdr gap)) (cadr gap) row)))
|
||||
(puthash :row-gap row table)
|
||||
(puthash :column-gap column table)
|
||||
(remhash :gap table)))
|
||||
(let (keys)
|
||||
(maphash (lambda (key _value) (push key keys)) table)
|
||||
|
||||
172
ebox.el
172
ebox.el
@ -23,7 +23,8 @@
|
||||
"Directory containing the active Ebox Lisp sources.")
|
||||
|
||||
(defconst ebox--compile-sources
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-style.el" "ebox-layout-config.el"
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-font.el" "ebox-style.el"
|
||||
"ebox-layout-config.el"
|
||||
"ebox-node-factory.el"
|
||||
"ebox-child-range.el" "ebox-tree.el" "ebox-measure.el"
|
||||
"ebox-fragment.el" "ebox-render-context.el" "ebox-layout.el"
|
||||
@ -42,6 +43,7 @@
|
||||
(require 'subr-x)
|
||||
(require 'ebox-cache)
|
||||
(require 'ebox-source)
|
||||
(require 'ebox-font)
|
||||
(require 'ebox-style)
|
||||
(require 'ebox-layout-config)
|
||||
(require 'ebox-node-factory)
|
||||
@ -741,78 +743,6 @@ indexes. This prepares component identity without exposing a component API."
|
||||
"Compile an Ebox author form into one opaque canonical input."
|
||||
(ebox-dsl-build-input dsl))
|
||||
|
||||
;;; ============================================================
|
||||
;;; Property Rules: 配置驱动的 Shorthand/Longhand 映射
|
||||
;;; ============================================================
|
||||
;;
|
||||
;; 规则格式自描述,无需 type 标记:
|
||||
;; - 值是 list → shorthand,展开到列表中的属性
|
||||
;; - 值是 (keyword . converter) → longhand,转换后写入目标
|
||||
;; - 值是 keyword → 直通,属性名不变
|
||||
|
||||
(defconst ebox-property-rules ebox-style-ebox-property-rules
|
||||
"Compatibility alias for Ebox public property expansion rules.")
|
||||
|
||||
;;; --- 值转换器 ---
|
||||
|
||||
(defun ebox--convert (value converter)
|
||||
"Convert VALUE using CONVERTER."
|
||||
(ebox-style--convert value converter))
|
||||
|
||||
;;; --- 值拆分器 ---
|
||||
|
||||
(defun ebox--split-trbl (value)
|
||||
"Split VALUE using CSS TRBL rules → (top right bottom left)."
|
||||
(ebox-style--split-trbl value))
|
||||
|
||||
(defun ebox--split-pair (value)
|
||||
"Split VALUE into pair → (start end)."
|
||||
(ebox-style--split-pair value))
|
||||
|
||||
(defconst ebox--border-styles
|
||||
'(none hidden dotted dashed solid double groove ridge inset outset)
|
||||
"Valid CSS border-style keywords.")
|
||||
|
||||
(defun ebox--wsc-classify (item)
|
||||
"Classify ITEM as width, style, or color. Returns (type . value)."
|
||||
(ebox-style--wsc-classify item))
|
||||
|
||||
(defun ebox--split-wsc (value)
|
||||
"Split VALUE into (width style color) using CSS type-based parsing.
|
||||
Values can appear in any order; each is identified by type."
|
||||
(ebox-style--split-wsc value))
|
||||
|
||||
(defun ebox--split-value (value mode n)
|
||||
"Split VALUE according to MODE for N targets."
|
||||
(ebox-style--split-value value mode n))
|
||||
|
||||
;;; --- 核心展开函数 ---
|
||||
|
||||
(defun ebox--get-rule (property)
|
||||
"Get rule for PROPERTY from `ebox-property-rules'."
|
||||
(ebox-style--get-ebox-rule property))
|
||||
|
||||
(defun ebox--expand-property (property value)
|
||||
"Expand PROPERTY with VALUE according to rules. Returns a plist."
|
||||
(ebox-style-expand-ebox-property property value))
|
||||
|
||||
;;; --- 辅助函数 ---
|
||||
|
||||
(defun ebox--atom-consp (list)
|
||||
"Check if LIST is a strict dotted pair (atom . atom)."
|
||||
(ebox-style--atom-consp list))
|
||||
|
||||
(defun ebox--parse-color (color)
|
||||
"Parse COLOR to a real color string.
|
||||
Supports: t (default fg), nil, string, (light . dark) cons."
|
||||
(ebox-style--parse-color color))
|
||||
|
||||
(defun ebox--expand-plist (plist)
|
||||
"Expand PLIST using `ebox-property-rules'."
|
||||
(ebox-style-expand-ebox-plist plist))
|
||||
|
||||
;;; --- 核心 API ---
|
||||
|
||||
;;; ============================================================
|
||||
;;; Buffer Runtime State & Scroll State
|
||||
;;; ============================================================
|
||||
@ -1308,8 +1238,8 @@ current visible slice. Prefix misses continue through the normal renderer."
|
||||
(= (or (ebox-get box :margin-bottom-height) 0) 0)
|
||||
(= (or (ebox-get box :border-left-pixel) 0) 0)
|
||||
(= (or (ebox-get box :border-right-pixel) 0) 0)
|
||||
(not (ebox-get box :border-top-p))
|
||||
(not (ebox-get box :border-bottom-p))
|
||||
(= (or (ebox-get box :border-top-pixel) 0) 0)
|
||||
(= (or (ebox-get box :border-bottom-pixel) 0) 0)
|
||||
(eq (ebox-get box :vertical-align) 'top)
|
||||
(null (ebox-get box :surface-properties))
|
||||
visible)))
|
||||
@ -1360,8 +1290,8 @@ proof becomes usable for an input event."
|
||||
(= (or (ebox-get box :margin-bottom-height) 0) 0)
|
||||
(= (or (ebox-get box :border-left-pixel) 0) 0)
|
||||
(= (or (ebox-get box :border-right-pixel) 0) 0)
|
||||
(not (ebox-get box :border-top-p))
|
||||
(not (ebox-get box :border-bottom-p))
|
||||
(= (or (ebox-get box :border-top-pixel) 0) 0)
|
||||
(= (or (ebox-get box :border-bottom-pixel) 0) 0)
|
||||
(eq (ebox-get box :vertical-align) 'top)
|
||||
(null (ebox-get box :surface-properties)))))
|
||||
|
||||
@ -3831,6 +3761,7 @@ Defaults to the current buffer."
|
||||
(nreverse result)))
|
||||
|
||||
(defconst ebox--region-update-longhand-props
|
||||
(append
|
||||
'(:box-sizing
|
||||
:width :min-width :max-width
|
||||
:height :min-height :max-height
|
||||
@ -3838,16 +3769,36 @@ Defaults to the current buffer."
|
||||
:padding-top-height :padding-bottom-height
|
||||
:margin-left-pixel :margin-right-pixel
|
||||
:margin-top-height :margin-bottom-height
|
||||
:border-left-pixel :border-left-color
|
||||
:border-right-pixel :border-right-color
|
||||
:border-top-p :border-top-color
|
||||
:border-bottom-p :border-bottom-color
|
||||
:border-left-pixel :border-left-style :border-left-color
|
||||
:border-right-pixel :border-right-style :border-right-color
|
||||
:border-top-pixel :border-top-style :border-top-color
|
||||
:border-bottom-pixel :border-bottom-style :border-bottom-color
|
||||
:color :bgcolor
|
||||
:text-align :vertical-align
|
||||
:overflow :wrap-mode
|
||||
:scroll-offset :visibility)
|
||||
ebox-style--item-projection-properties)
|
||||
"Longhand box properties accepted by `ebox-region-update'.")
|
||||
|
||||
(defconst ebox--region-update-control-properties
|
||||
'(:content :scroll-offset)
|
||||
"Surface controls accepted by `ebox-region-update' outside style schemas.")
|
||||
|
||||
(defun ebox--region-update-style-properties (props)
|
||||
"Return PROPS without region-owned content and scroll controls."
|
||||
(cl-loop for (property value) on props by #'cddr
|
||||
unless (memq property ebox--region-update-control-properties)
|
||||
append (list property value)))
|
||||
|
||||
(defun ebox--region-update-author-form (box)
|
||||
"Return the public author form whose closed schema applies to BOX."
|
||||
(if (eq (plist-get box :ebox-kind) 'text)
|
||||
'text
|
||||
(pcase (ebox-tree-layout-kind box)
|
||||
('normal 'box)
|
||||
((and kind (or 'row 'column 'flex 'grid)) kind)
|
||||
(_ 'box))))
|
||||
|
||||
(defun ebox--region-update-normalize-property (property)
|
||||
"Return the box longhand field for region-update PROPERTY."
|
||||
(pcase property
|
||||
@ -3884,6 +3835,8 @@ Defaults to the current buffer."
|
||||
((memq target-key ebox--region-update-longhand-props)
|
||||
(when (not (equal (ebox-get box target-key) value))
|
||||
(setq style-changed-p t)))
|
||||
((ebox-style-schema-id key)
|
||||
(setq style-changed-p t))
|
||||
(t
|
||||
(push key unknown-keys))))
|
||||
(when (and content-seen (not (stringp content-value)))
|
||||
@ -3928,8 +3881,35 @@ cannot be changed through the unpublished batch root."
|
||||
buffer region-id t))
|
||||
(ebox-tree-copy-node-structure (plist-get state :root-node)))))
|
||||
|
||||
(defun ebox--region-update-layout-delta (box declarations)
|
||||
"Apply LayoutConfig DECLARATIONS to BOX and return changed property names."
|
||||
(when-let* ((config (and (ebox-box-node-p box)
|
||||
(ebox-box-node-layout box)))
|
||||
(kind (ebox-layout-config-kind config))
|
||||
(author-kind (if (eq kind 'normal) 'box kind))
|
||||
(names (ebox-layout-config-property-names author-kind))
|
||||
(delta
|
||||
(ebox-style-declaration-properties
|
||||
declarations
|
||||
(lambda (property)
|
||||
(memq (plist-get property :name) names)))))
|
||||
(let* ((normalized
|
||||
(ebox-layout-config-props
|
||||
(ebox-layout-config-for-form author-kind delta)))
|
||||
(props (copy-tree (ebox-layout-config-props config)))
|
||||
changed)
|
||||
(cl-loop for (name _value) on delta by #'cddr
|
||||
for value = (plist-get normalized name)
|
||||
unless (equal (plist-get props name) value)
|
||||
do (setq props (plist-put props name value))
|
||||
and do (push name changed))
|
||||
(when changed
|
||||
(plist-put box :ebox-layout-config
|
||||
(ebox-layout-config-with-props config props)))
|
||||
(nreverse changed))))
|
||||
|
||||
(defun ebox--surface-region-apply-props
|
||||
(root source-index region-id expanded declarations)
|
||||
(root source-index region-id expanded declarations style-declarations)
|
||||
"Apply DECLARATIONS then EXPANDED fields to REGION-ID in ROOT."
|
||||
(let ((box (ebox--root-region-box root region-id))
|
||||
(candidate-source-index source-index)
|
||||
@ -3947,6 +3927,17 @@ cannot be changed through the unpublished batch root."
|
||||
source-index old-handle :declarations declarations)))
|
||||
(setq candidate-source-index (car binding))
|
||||
(plist-put box :ebox-source-handle (cdr binding))))
|
||||
(when (plist-member style-declarations 'ebox/outer)
|
||||
(let* ((outer (plist-get style-declarations 'ebox/outer))
|
||||
(display (plist-get box :display))
|
||||
(next (list outer (or (cadr display) 'flow))))
|
||||
(unless (equal display next)
|
||||
(plist-put box :display next)
|
||||
(push :outer changed-keys))))
|
||||
(setq changed-keys
|
||||
(nconc (ebox--region-update-layout-delta
|
||||
box style-declarations)
|
||||
changed-keys))
|
||||
(cl-loop for (key value) on expanded by #'cddr
|
||||
for target-key = (ebox--region-update-normalize-property key)
|
||||
when (or (eq target-key :content)
|
||||
@ -4195,12 +4186,23 @@ REGION-ID and CHANGED-KEYS describe one update; CHANGES describes a batch."
|
||||
(candidate-box
|
||||
(or (ebox--root-region-box published-root region-id)
|
||||
(user-error "Ebox region handle no longer resolves to a box")))
|
||||
(expanded (ebox--expand-plist props))
|
||||
(style-declarations
|
||||
(ebox-style-compile-form
|
||||
(ebox--region-update-author-form candidate-box)
|
||||
(ebox--region-update-style-properties props)))
|
||||
(declarations
|
||||
(ebox-style-merge-declarations
|
||||
(ebox-style-node-declarations
|
||||
source-index candidate-box)
|
||||
(ebox-style-compile-declarations props)))
|
||||
style-declarations))
|
||||
(expanded
|
||||
(append
|
||||
(cl-loop for (property value) on props by #'cddr
|
||||
when (memq property ebox--region-update-control-properties)
|
||||
append (list property value))
|
||||
(ebox-style--expand-engine-delta
|
||||
style-declarations declarations
|
||||
(plist-get candidate-box :ebox-computed-style))))
|
||||
(old-style-values
|
||||
(and (plist-member expanded :overflow)
|
||||
(list :overflow (ebox-get candidate-box :overflow))))
|
||||
@ -4235,7 +4237,7 @@ REGION-ID and CHANGED-KEYS describe one update; CHANGES describes a batch."
|
||||
(applied
|
||||
(ebox--surface-region-apply-props
|
||||
candidate-root source-index
|
||||
region-id expanded declarations))
|
||||
region-id expanded declarations style-declarations))
|
||||
(changed-keys (plist-get applied :changed-keys))
|
||||
(candidate-source-index (plist-get applied :source-index))
|
||||
(dirty-kind
|
||||
|
||||
@ -644,10 +644,12 @@
|
||||
(cl-labels
|
||||
((rows (selected)
|
||||
(cl-loop for id from 1 to 12
|
||||
for content = (ebox-test-text (format "row-%02d" id))
|
||||
collect
|
||||
(if (= id selected)
|
||||
(ebox-test-box
|
||||
:key id (ebox-test-text (format "row-%02d" id)) :width '(420)
|
||||
:color (and (= id selected) "#2F6B43"))))
|
||||
:key id :width '(420) :color "#2F6B43" content)
|
||||
(ebox-test-box :key id :width '(420) content))))
|
||||
(root ()
|
||||
(ebox-test-box
|
||||
:width '(900) :color "#172033" :wrap-mode 'none
|
||||
@ -976,7 +978,7 @@
|
||||
(let* ((buffer (generate-new-buffer " *ebox-range-no-flatten*"))
|
||||
(static
|
||||
(cl-loop for index below 100
|
||||
collect (ebox-test-box :key (list 'static index)
|
||||
collect (ebox-test-box :key (format "static-%d" index)
|
||||
(ebox-test-text (number-to-string index)))))
|
||||
(root (apply #'ebox-test-column
|
||||
(append static
|
||||
@ -1042,7 +1044,7 @@
|
||||
(apply #'ebox-test-column
|
||||
(append
|
||||
(cl-loop for index below static-count
|
||||
collect (ebox-test-box :key (list 'static index)
|
||||
collect (ebox-test-box :key (format "static-%d" index)
|
||||
(ebox-test-text "s")))
|
||||
(list (ebox-test-child-range
|
||||
'items (ebox-test-box :key 'old (ebox-test-text "o"))))))))
|
||||
@ -1238,7 +1240,7 @@
|
||||
(apply #'ebox-test-column
|
||||
(append
|
||||
(cl-loop for index below 500
|
||||
collect (ebox-test-box :key (list 'static index)
|
||||
collect (ebox-test-box :key (format "static-%d" index)
|
||||
(ebox-test-text "s")))
|
||||
(list (ebox-test-child-range 'items)))))
|
||||
report)
|
||||
@ -1250,7 +1252,7 @@
|
||||
candidate 'items
|
||||
(ebox-child-range-test--input
|
||||
(cl-loop for index below 64
|
||||
collect (ebox-test-box :key (list 'new index)
|
||||
collect (ebox-test-box :key (format "new-%d" index)
|
||||
(ebox-test-text "n")))))
|
||||
(cl-letf (((symbol-function
|
||||
'ebox-incremental--candidate-full-index-preparation)
|
||||
|
||||
@ -595,8 +595,8 @@
|
||||
"Repeated equivalent style declarations compile through ECSS once."
|
||||
(clrhash ebox-style--declaration-cache)
|
||||
(let ((calls 0)
|
||||
(original (symbol-function 'ecss-merge-declarations)))
|
||||
(cl-letf (((symbol-function 'ecss-merge-declarations)
|
||||
(original (symbol-function 'ecss-expand-declarations)))
|
||||
(cl-letf (((symbol-function 'ecss-expand-declarations)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf calls)
|
||||
(apply original arguments))))
|
||||
@ -1038,57 +1038,6 @@
|
||||
(kill-buffer buffer)))))
|
||||
|
||||
|
||||
(ert-deftest ebox-commit-keeps-root-owner-for-dynamic-cascade-source ()
|
||||
"A TP-dependent cascade binding must preserve root-owner fallback."
|
||||
(let* ((ebox-style-stylesheet (ecss-stylesheet-create))
|
||||
(color (tp-signal-create "#111111"))
|
||||
(calls 0)
|
||||
(buffer nil))
|
||||
(ebox-style-add-rule
|
||||
".card" (list :color (tp-computed (lambda () (tp-signal-read color))))
|
||||
:layer 'components)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(setq buffer
|
||||
(ebox-render-to-buffer
|
||||
(generate-new-buffer-name " *ebox-dynamic-style-proof*")
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'card :class "card" (ebox-test-text "Before")
|
||||
:width '(40))
|
||||
(ebox-test-box :key 'stable (ebox-test-text "Stable")
|
||||
:width '(40)))))
|
||||
(let* ((state (ebox--buffer-render-state buffer))
|
||||
(style-states (plist-get state :style-binding-states))
|
||||
(binding-state
|
||||
(cl-loop for value being the hash-values of style-states
|
||||
when (and (plist-get value :binding)
|
||||
(not (ebox-surface--static-style-state-p
|
||||
value)))
|
||||
return value)))
|
||||
(should binding-state)
|
||||
(should (> (tp-binding-dependency-count
|
||||
(plist-get binding-state :binding))
|
||||
(if (plist-get binding-state :parent-binding) 1 0)))
|
||||
(should-not (ebox-surface--static-style-state-p binding-state)))
|
||||
(let ((original
|
||||
(symbol-function 'ebox-incremental--layout-owner-plan)))
|
||||
(cl-letf (((symbol-function 'ebox-incremental--layout-owner-plan)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf calls)
|
||||
(apply original arguments))))
|
||||
(ebox-commit
|
||||
buffer
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'card :class "card" (ebox-test-text "After")
|
||||
:width '(40))
|
||||
(ebox-test-box :key 'stable (ebox-test-text "Stable")
|
||||
:width '(40))))
|
||||
(should (= calls 0)))))
|
||||
(when (buffer-live-p buffer)
|
||||
(kill-buffer buffer)))))
|
||||
|
||||
|
||||
|
||||
(ert-deftest ebox-commit-formatting-context-reflow-owns-variable-line-siblings ()
|
||||
"Two variable-line owners should publish through their nearest stack context.
|
||||
The context owns the complete local block; the root and untouched header/footer
|
||||
@ -1299,7 +1248,7 @@ remain retained identities."
|
||||
(ebox-test-box :key 'right :source-identity 'right (ebox-test-text "R")))
|
||||
(old-root
|
||||
(ebox-test-grid :key 'grid :width '(80) :grid-template-columns
|
||||
'((36) (36)) :column-gap '(0 (8))
|
||||
'((36) (36)) :column-gap '(8)
|
||||
(ebox-test-box :key 'left :source-identity 'left (ebox-test-text "left-old"))
|
||||
(ebox-test-box :key 'right :source-identity 'right (ebox-test-text "right-old"))))
|
||||
(buffer nil)
|
||||
@ -1320,7 +1269,10 @@ remain retained identities."
|
||||
'(span-patch owner-scoped)))
|
||||
(should-not (plist-get report :tp-full-root))
|
||||
(should-not (plist-get report :tp-scope-fallback))
|
||||
(should (= 1 (plist-get report :tp-text-operations)))
|
||||
;; The 8px Grid gap keeps the two changed slots disjoint. TP still
|
||||
;; publishes one scoped transaction, with one exact text operation
|
||||
;; per changed slot instead of replacing the unchanged gap.
|
||||
(should (= 2 (plist-get report :tp-text-operations)))
|
||||
(should (string-match-p "L"
|
||||
(ebox-commit-test--buffer-string buffer)))
|
||||
(setq fresh
|
||||
@ -1328,7 +1280,7 @@ remain retained identities."
|
||||
(generate-new-buffer-name
|
||||
" *ebox-multi-owner-variable-slots-fresh*")
|
||||
(ebox-test-grid :key 'grid :width '(80) :grid-template-columns
|
||||
'((36) (36)) :column-gap '(0 (8))
|
||||
'((36) (36)) :column-gap '(8)
|
||||
(ebox-test-box :key 'left :source-identity 'left (ebox-test-text "L"))
|
||||
(ebox-test-box :key 'right :source-identity 'right (ebox-test-text "R")))))
|
||||
(should (equal
|
||||
@ -1347,7 +1299,7 @@ remain retained identities."
|
||||
((root (left right)
|
||||
(ebox-test-grid :key 'grid :width '(80)
|
||||
:grid-template-columns '((36) (36))
|
||||
:column-gap '(0 (8))
|
||||
:column-gap '(8)
|
||||
(ebox-test-box :key 'left :source-identity 'left (ebox-test-text left))
|
||||
(ebox-test-box :key 'right :source-identity 'right (ebox-test-text right))))
|
||||
(candidate (buffer)
|
||||
@ -1682,7 +1634,7 @@ remain retained identities."
|
||||
:key 'geometry-context :width '(80)
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'left :source-identity 'left
|
||||
(ebox-test-text left) :font 'bold)
|
||||
(ebox-test-text left) :font-weight 'bold)
|
||||
(ebox-test-box :key 'right :source-identity 'right
|
||||
(ebox-test-text right))))
|
||||
(ebox-test-box :key 'paint-a :source-identity 'paint-a
|
||||
@ -1867,7 +1819,7 @@ remain retained identities."
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'left
|
||||
(ebox-test-box :key 'left :source-identity 'left
|
||||
(ebox-test-text left) :font 'bold))
|
||||
(ebox-test-text left) :font-weight 'bold))
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'right
|
||||
(ebox-test-box :key 'right :source-identity 'right
|
||||
@ -1991,9 +1943,23 @@ remain retained identities."
|
||||
(defun ebox-commit-test--allocation-closure-root
|
||||
(toast paint-a paint-b &optional width footer-overflow root-overflow)
|
||||
"Return a generic whole-line Flex allocation-closure fixture."
|
||||
(let* ((footer
|
||||
(apply
|
||||
#'ebox-test-flex
|
||||
(append
|
||||
(list :key 'footer :width (list (or width 800))
|
||||
:flex-wrap 'wrap :gap '(1 (4)))
|
||||
(when footer-overflow (list :overflow footer-overflow))
|
||||
(list
|
||||
(ebox-test-box
|
||||
:key 'root :width (list (or width 800))
|
||||
:height (and root-overflow 1) :overflow root-overflow
|
||||
:key 'toast-slot :width 'stretch :min-width 0
|
||||
:flex-grow 1 :flex-shrink 1 :flex-basis '(0)
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'toast :source-identity 'toast
|
||||
(ebox-test-text toast))))
|
||||
(ebox-test-box :key 'peer
|
||||
(ebox-test-text "database.sqlite"))))))
|
||||
(content
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'status :source-identity 'status
|
||||
(ebox-test-text "Theme: Light") :width '(200))
|
||||
@ -2003,16 +1969,13 @@ remain retained identities."
|
||||
(ebox-test-text "paint-b") :bgcolor paint-b)
|
||||
(ebox-test-box
|
||||
:key 'footer-owner :width (list (or width 800))
|
||||
(ebox-test-column
|
||||
(ebox-test-flex
|
||||
:key 'footer :width (list (or width 800)) :flex-wrap 'wrap
|
||||
:overflow footer-overflow :gap '(1 (4))
|
||||
(ebox-test-box
|
||||
:key 'toast-slot :width 'stretch :min-width 0
|
||||
:flex-grow 1 :flex-shrink 1 :flex-basis '(0)
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'toast :source-identity 'toast (ebox-test-text toast))))
|
||||
(ebox-test-box :key 'peer (ebox-test-text "database.sqlite"))))))))
|
||||
(ebox-test-column footer)))))
|
||||
(apply
|
||||
#'ebox-test-box
|
||||
(append
|
||||
(list :key 'root :width (list (or width 800)))
|
||||
(when root-overflow (list :height 1 :overflow root-overflow))
|
||||
(list content)))))
|
||||
|
||||
(defun ebox-commit-test--allocation-closure-candidate
|
||||
(buffer toast paint-a paint-b)
|
||||
|
||||
@ -380,41 +380,37 @@
|
||||
full-updates))
|
||||
|
||||
(defconst ebox-test--region-update-paint-cases
|
||||
'((:color "#123456" "#123456")
|
||||
(:bgcolor "#FDE68A" "#FDE68A")
|
||||
(:border-left-color "#111111" "#111111")
|
||||
(:border-right-color "#222222" "#222222")
|
||||
(:border-top-color "#333333" "#333333")
|
||||
(:border-bottom-color "#444444" "#444444")
|
||||
(:border-top-p t t)
|
||||
(:border-bottom-p t t))
|
||||
"Paint-only box longhand update cases: (PROPERTY VALUE EXPECTED).")
|
||||
'((:color "#123456" :color "#123456")
|
||||
(:bgcolor "#FDE68A" :bgcolor "#FDE68A")
|
||||
(:border-left-color "#111111" :border-left-color "#111111")
|
||||
(:border-right-color "#222222" :border-right-color "#222222")
|
||||
(:border-top-color "#333333" :border-top-color "#333333")
|
||||
(:border-bottom-color "#444444" :border-bottom-color "#444444")
|
||||
(:visibility hidden :visibility hidden))
|
||||
"Paint updates as (AUTHOR VALUE ENGINE-FIELD EXPECTED).")
|
||||
|
||||
(defconst ebox-test--region-update-geometry-cases
|
||||
'((:box-sizing content-box content-box owner-rerender)
|
||||
(:width (120) 120 owner-rerender)
|
||||
(:min-width (100) 100 owner-rerender)
|
||||
(:max-width (140) 140 owner-rerender)
|
||||
(:height 3 3 owner-rerender)
|
||||
(:min-height 3 3 owner-rerender)
|
||||
(:max-height 1 1 owner-rerender)
|
||||
(:padding-left-pixel 9 9 span-patch)
|
||||
(:padding-right-pixel 10 10 span-patch)
|
||||
(:padding-top-height 1 1 span-patch)
|
||||
(:padding-bottom-height 1 1 span-patch)
|
||||
(:margin-left-pixel 7 7 owner-rerender)
|
||||
(:margin-right-pixel 8 8 owner-rerender)
|
||||
(:margin-top-height 1 1 owner-rerender)
|
||||
(:margin-bottom-height 1 1 owner-rerender)
|
||||
(:border-left-pixel 5 5 span-patch)
|
||||
(:border-right-pixel 6 6 span-patch)
|
||||
(:text-align center center span-patch)
|
||||
(:vertical-align middle center span-patch)
|
||||
(:overflow hidden hidden owner-rerender)
|
||||
(:wrap-mode char char span-patch)
|
||||
(:scroll-offset 1 1 span-patch)
|
||||
(:visibility hidden hidden span-patch))
|
||||
"Geometry box longhand update cases: (PROPERTY VALUE EXPECTED STRATEGY).")
|
||||
'((:box-sizing content-box :box-sizing content-box owner-rerender)
|
||||
(:width (120) :width 120 owner-rerender)
|
||||
(:min-width (100) :min-width 100 owner-rerender)
|
||||
(:max-width (140) :max-width 140 owner-rerender)
|
||||
(:height 3 :height 3 owner-rerender)
|
||||
(:min-height 3 :min-height 3 owner-rerender)
|
||||
(:max-height 1 :max-height 1 owner-rerender)
|
||||
(:padding-left (9) :padding-left-pixel 9 span-patch)
|
||||
(:padding-right (10) :padding-right-pixel 10 span-patch)
|
||||
(:padding-top 1 :padding-top-height 1 span-patch)
|
||||
(:padding-bottom 1 :padding-bottom-height 1 span-patch)
|
||||
(:margin-left (7) :margin-left-pixel 7 owner-rerender)
|
||||
(:margin-right (8) :margin-right-pixel 8 owner-rerender)
|
||||
(:margin-top 1 :margin-top-height 1 owner-rerender)
|
||||
(:margin-bottom 1 :margin-bottom-height 1 owner-rerender)
|
||||
(:border-top-p t :border-top-pixel 1 span-patch)
|
||||
(:text-align center :text-align center span-patch)
|
||||
(:overflow hidden :overflow hidden owner-rerender)
|
||||
(:wrap-mode char :wrap-mode char span-patch)
|
||||
(:scroll-offset 1 :scroll-offset 1 span-patch))
|
||||
"Geometry updates as (AUTHOR VALUE ENGINE-FIELD EXPECTED STRATEGY).")
|
||||
|
||||
(defun ebox-test--region-update-case-properties (cases)
|
||||
"Return the update property names covered by CASES."
|
||||
@ -463,22 +459,15 @@
|
||||
(should (ebox-test--face-has-key-value-p
|
||||
face :background "#1E293B")))))
|
||||
|
||||
(ert-deftest ebox-ecss-value-resolver-uses-tp-computed-contract ()
|
||||
"ECSS should resolve tagged TP values while preserving literal functions."
|
||||
(let ((calls 0)
|
||||
(literal (lambda () 'literal)))
|
||||
(should
|
||||
(= (plist-get
|
||||
(ert-deftest ebox-font-size-rejects-runtime-and-function-values ()
|
||||
"Standalone Ebox font size accepts only positive CSS reference pixels."
|
||||
(should-error
|
||||
(ebox-style-compute
|
||||
(list :font-height
|
||||
(tp-computed (lambda () (cl-incf calls) 1.25))))
|
||||
:font-height)
|
||||
1.25))
|
||||
(should (= calls 1))
|
||||
(should
|
||||
(eq (plist-get (ebox-style-compute (list :font-height literal))
|
||||
:font-height)
|
||||
literal))))
|
||||
(list :font-size (tp-computed (lambda () 16))))
|
||||
:type 'error)
|
||||
(should-error
|
||||
(ebox-style-compute (list :font-size (lambda () 16)))
|
||||
:type 'error))
|
||||
|
||||
(ert-deftest ebox-ecss-computed-fact-retains-owner-impact-and-provenance ()
|
||||
"The Ebox package schema should remain attached to one computed fact."
|
||||
@ -513,6 +502,11 @@
|
||||
(paint-value (ecss-computed-property-fact-value fact)))
|
||||
(should (equal '(geometry paint)
|
||||
(ecss-computed-property-fact-impacts fact)))
|
||||
(should (equal '(measurement paint)
|
||||
(ecss-computed-property-fact-projections fact)))
|
||||
(should (eq (plist-get
|
||||
(ecss-computed-property-fact-metadata fact) :owner)
|
||||
'font-metrics-projection))
|
||||
(should (eq geometry-value paint-value))
|
||||
(should (equal geometry-value
|
||||
(ecss-computed-style-value style 'ebox/font-weight)))))
|
||||
@ -551,7 +545,9 @@
|
||||
(lambda (computed)
|
||||
(cl-incf provenance-reads)
|
||||
(funcall original-provenance computed))))
|
||||
(should (ebox-style--context-values style 'box t)))
|
||||
(should (ebox-style--context-values style 'box t))
|
||||
(should (ebox-style--context-values style 'box t))
|
||||
(should (ebox-style--context-values style 'box nil)))
|
||||
(should (= values-reads 1))
|
||||
(should (= active-reads 1))
|
||||
(should (= specified-reads 1))
|
||||
@ -590,31 +586,6 @@
|
||||
(should (= active-reads 1))
|
||||
(should (= specified-reads 1))))
|
||||
|
||||
(ert-deftest ebox-cascade-inherits-color-but-honors-explicit-nil ()
|
||||
"A child nil color should block inheritance without inheriting background."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((ebox-style-stylesheet (ecss-stylesheet-create))
|
||||
(root (ebox-test-box :class 'panel :wrap-mode 'none :width '(96)
|
||||
(ebox-test-box :id 'child :color nil :width '(96)
|
||||
(ebox-test-text "Child")))))
|
||||
(ebox-style-add-rule
|
||||
".panel" '(:color "#E2E8F0" :bgcolor "#0F172A")
|
||||
:layer 'components)
|
||||
(ebox-test--with-rendered-buffer root
|
||||
(let* ((runtime-root (ebox-test--runtime-root (current-buffer)))
|
||||
(runtime-child (car (ebox-tree-layout-children runtime-root)))
|
||||
(child-style (plist-get runtime-child :ebox-computed-style))
|
||||
(values (ecss-computed-style-values child-style))
|
||||
(active (ecss-computed-style-active-properties child-style)))
|
||||
(should (memq 'ebox/color active))
|
||||
(should (null (plist-get values 'ebox/color)))
|
||||
(should (null (plist-get values 'ebox/background-color)))
|
||||
(should-not (memq 'ebox/background-color active))
|
||||
(goto-char (point-min))
|
||||
(search-forward "Child")
|
||||
(let ((face (get-text-property (match-beginning 0) 'face)))
|
||||
(should (equal (car face) '(:inherit default))))))))
|
||||
|
||||
(ert-deftest ebox-default-foreground-reset-preserves-explicit-background ()
|
||||
"Resolve GUI default foreground without inheriting its background."
|
||||
(let ((original (symbol-function 'face-attribute)))
|
||||
@ -700,7 +671,7 @@
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((root
|
||||
(ebox-test-box :wrap-mode 'none :font-family "Parent Family"
|
||||
:font-height 1.25 :font-weight 'bold
|
||||
:font-size 16 :font-weight 'bold
|
||||
(ebox-test-box :id 'child :font-family "Child Family"
|
||||
(ebox-test-text "MMMM"))))
|
||||
measured-face)
|
||||
@ -716,18 +687,18 @@
|
||||
(values (ecss-computed-style-values style))
|
||||
(face (ebox-test--first-content-face (buffer-string))))
|
||||
(should (equal (plist-get values 'ebox/font-family) "Child Family"))
|
||||
(should (= (plist-get values 'ebox/font-height) 1.25))
|
||||
(should (= (plist-get values 'ebox/font-size) 16))
|
||||
(should (eq (plist-get values 'ebox/font-weight) 'bold))
|
||||
(should
|
||||
(equal (ebox-test--effective-face-value face :family)
|
||||
"Child Family"))
|
||||
(should (= (ebox-test--effective-face-value face :height) 1.25))
|
||||
(should (= (ebox-test--effective-face-value face :height) 120))
|
||||
(should (eq (ebox-test--effective-face-value face :weight) 'bold)))))
|
||||
(should measured-face)
|
||||
(should
|
||||
(equal (ebox-test--effective-face-value measured-face :family)
|
||||
"Child Family"))
|
||||
(should (= (ebox-test--effective-face-value measured-face :height) 1.25))))
|
||||
(should (= (ebox-test--effective-face-value measured-face :height) 120))))
|
||||
|
||||
(ert-deftest ebox-column-background-owns-blank-lines-with-child-override ()
|
||||
"A column background should fill its blank lines without hiding child paint."
|
||||
@ -1781,6 +1752,31 @@
|
||||
(should props)
|
||||
(should (plist-member props 'face))))
|
||||
|
||||
(ert-deftest ebox-text-decoration-projects-to-one-emacs-face ()
|
||||
"Canonical decoration facts should remain paint-only and visibly compose."
|
||||
(let* ((style
|
||||
(ebox-style-compute
|
||||
'(:text-decoration-line (underline overline line-through)
|
||||
:text-decoration-color "#336699"
|
||||
:text-decoration-style wavy)))
|
||||
(face (plist-get
|
||||
(ebox-buffer-paint-text-properties style 'content) 'face))
|
||||
(rendered
|
||||
(ebox-render
|
||||
(ebox-test-text
|
||||
"Decorated"
|
||||
:text-decoration-line '(underline overline line-through)
|
||||
:text-decoration-color "#336699"
|
||||
:text-decoration-style 'wavy)))
|
||||
(rendered-face (get-text-property 0 'face rendered)))
|
||||
(dolist (candidate (list face rendered-face))
|
||||
(should (ebox-test--face-has-key-value-p
|
||||
candidate :underline '(:color "#336699" :style wave)))
|
||||
(should (ebox-test--face-has-key-value-p
|
||||
candidate :overline "#336699"))
|
||||
(should (ebox-test--face-has-key-value-p
|
||||
candidate :strike-through "#336699")))))
|
||||
|
||||
(ert-deftest ebox-render-keeps-tp-paint-slot-address-stable ()
|
||||
"A TP paint slot should recolor retained output without property rewrites."
|
||||
(let* ((slot (tp-paint-slot-create '(:foreground "red")))
|
||||
@ -2120,7 +2116,7 @@
|
||||
"Detailed snapshots should expose explicit L2 footprint signatures."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((box (ebox-test-box (ebox-test-text "Footprint") :width '(120)
|
||||
:height 1 :border t))
|
||||
:height 1 :border '(1 solid)))
|
||||
(node-id (ebox--ensure-node-id (ebox-test-root box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(let* ((snapshot (ebox--ensure-layout-snapshot-details
|
||||
@ -2531,7 +2527,7 @@
|
||||
collect
|
||||
(ebox-test-box
|
||||
(ebox-test-text (format "Fluid %02d" index))
|
||||
:border t))))
|
||||
:border '(1 solid)))))
|
||||
buffer)
|
||||
(unwind-protect
|
||||
(progn
|
||||
@ -2571,7 +2567,7 @@
|
||||
collect
|
||||
(ebox-test-box
|
||||
(ebox-test-text (format "Fluid section %02d" index))
|
||||
:border t))))
|
||||
:border '(1 solid)))))
|
||||
buffer)
|
||||
(unwind-protect
|
||||
(progn
|
||||
@ -2611,7 +2607,7 @@
|
||||
(ebox-test-text content)
|
||||
:width 'viewport
|
||||
:wrap-mode 'word
|
||||
:border t))))
|
||||
:border '(1 solid)))))
|
||||
buffer)
|
||||
(unwind-protect
|
||||
(progn
|
||||
@ -2645,7 +2641,7 @@
|
||||
(ebox-test-text content)
|
||||
:width 'viewport
|
||||
:wrap-mode 'word
|
||||
:border t))))
|
||||
:border '(1 solid)))))
|
||||
(slot-probe-count 0)
|
||||
(original (symbol-function 'ebox-buffer--rendered-in-existing-slots))
|
||||
buffer)
|
||||
@ -3712,8 +3708,7 @@
|
||||
(ordinary-height-nodes
|
||||
(cl-loop for (property value)
|
||||
in '((:height (viewport-height))
|
||||
(:min-height (- (viewport-height) 1))
|
||||
(:max-height (+ (viewport-height) 1)))
|
||||
(:max-height (viewport-height)))
|
||||
collect
|
||||
(apply #'ebox-test-box
|
||||
(ebox-test-text "Ordinary")
|
||||
@ -3721,8 +3716,7 @@
|
||||
(flex-height-nodes
|
||||
(cl-loop for (property value)
|
||||
in '((:height (viewport-height))
|
||||
(:min-height (- (viewport-height) 1))
|
||||
(:max-height (+ (viewport-height) 1)))
|
||||
(:max-height (viewport-height)))
|
||||
collect
|
||||
(apply #'ebox-test-flex
|
||||
(append
|
||||
@ -4148,7 +4142,7 @@
|
||||
"Render cache plist signatures should be built linearly."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let ((node (ebox-test-root
|
||||
(ebox-test-box (ebox-test-text "Signature") :width 80 :border t)))
|
||||
(ebox-test-box (ebox-test-text "Signature") :width 80 :border '(1 solid))))
|
||||
(original-append (symbol-function 'append))
|
||||
(append-calls 0))
|
||||
(cl-letf (((symbol-function 'append)
|
||||
@ -4234,9 +4228,9 @@
|
||||
(ert-deftest ebox-transparent-preformatted-box-fast-path-is-exact ()
|
||||
"Transparent exact-width wrappers should match the generic box pipeline."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((child-a (ebox-test-box (ebox-test-text "A") :width '(viewport) :border t
|
||||
(let* ((child-a (ebox-test-box (ebox-test-text "A") :width '(viewport) :border '(1 solid)
|
||||
:region-id 'child-a))
|
||||
(child-b (ebox-test-box (ebox-test-text "B") :width '(viewport) :border t
|
||||
(child-b (ebox-test-box (ebox-test-text "B") :width '(viewport) :border '(1 solid)
|
||||
:region-id 'child-b))
|
||||
(parent (ebox-test-column
|
||||
:width '(viewport) :region-id 'parent child-a child-b))
|
||||
@ -4343,11 +4337,12 @@
|
||||
(ebox-test-box
|
||||
(ebox-test-text (nth 0 case)) :width '(120) :height 5
|
||||
:box-sizing 'border-box :overflow (nth 1 case)
|
||||
:vertical-align (nth 2 case) :wrap-mode 'none))
|
||||
:wrap-mode 'none))
|
||||
(box (ebox-test-root box-input))
|
||||
(calls 0)
|
||||
(original
|
||||
(symbol-function 'ebox--window-render-flat-wrapper-chunk-lines)))
|
||||
(plist-put box :vertical-align (nth 2 case))
|
||||
(plist-put box :ebox-content-width-exact-p t)
|
||||
(cl-letf (((symbol-function 'ebox--window-render-flat-wrapper-chunk-lines)
|
||||
(lambda (&rest arguments)
|
||||
@ -4756,12 +4751,12 @@
|
||||
(should-not (text-property-search-forward 'ebox-bb left-id t))
|
||||
(let ((full-rerenders
|
||||
(ebox-test--count-full-surface-updates
|
||||
(ebox-test--region-update left-id
|
||||
:border-bottom-p t
|
||||
:border-bottom-color "blue"))))
|
||||
(ebox-test--region-update
|
||||
left-id :border-bottom '(1 solid "blue")))))
|
||||
(should (= full-rerenders 0)))
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
(should (eq (plist-get report :strategy) 'paint-patch))
|
||||
(should (memq (plist-get report :strategy)
|
||||
'(span-patch owner-rerender)))
|
||||
(should-not (plist-get report :root-rerender)))
|
||||
(let (bottom-face)
|
||||
(save-excursion
|
||||
@ -4806,9 +4801,8 @@
|
||||
(save-restriction
|
||||
(goto-char (1+ (point-min)))
|
||||
(narrow-to-region (point) (point-max))
|
||||
(ebox-test--region-update left-id
|
||||
:border-bottom-p t
|
||||
:border-bottom-color "blue"))
|
||||
(ebox-test--region-update
|
||||
left-id :border-bottom '(1 solid "blue")))
|
||||
(should (equal old-metadata
|
||||
(plist-get old-state :surface-fragments)))
|
||||
(should (cl-every (lambda (fragment)
|
||||
@ -4818,9 +4812,9 @@
|
||||
(ert-deftest ebox-horizontal-border-removal-preserves-other-border-colors ()
|
||||
"Removing top/bottom border surfaces must not corrupt other border faces."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(dolist (case '((:border-top-p bt (bb bl br))
|
||||
(:border-bottom-p bb (bt bl br))))
|
||||
(let* ((property (nth 0 case))
|
||||
(dolist (case '(((:border-top-p nil) bt (bb bl br))
|
||||
((:border-bottom-width 0) bb (bt bl br))))
|
||||
(let* ((update (nth 0 case))
|
||||
(removed-role (nth 1 case))
|
||||
(kept-roles (nth 2 case))
|
||||
(layout (ebox-test-box (ebox-test-text "Border")
|
||||
@ -4841,34 +4835,46 @@
|
||||
(setq face (get-text-property (point) 'face)))
|
||||
(forward-char 1)))
|
||||
face)))
|
||||
(ebox-test--region-update region-id property nil)
|
||||
(apply #'ebox-test--region-update region-id update)
|
||||
(should-not (face-at-role removed-role))
|
||||
(dolist (role kept-roles)
|
||||
(should
|
||||
(ebox-test--border-face-has-color-p
|
||||
(face-at-role role) role "#D97757"))))))))
|
||||
|
||||
(ert-deftest ebox-side-border-color-patch-toggles-default-paint-exactly ()
|
||||
"Side color patches should match default and explicit full-render paint."
|
||||
(ert-deftest ebox-horizontal-border-capability-error-preserves-mounted-state ()
|
||||
"An unsupported horizontal width must fail before replacing publication."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((input
|
||||
(ebox-test-box :id "root" (ebox-test-text "Border")
|
||||
:border '(1 solid "#D97757")))
|
||||
(buffer (ebox-render-to-buffer
|
||||
(generate-new-buffer-name " *ebox-border-capability*")
|
||||
input))
|
||||
(state (ebox--buffer-render-state buffer))
|
||||
(text (with-current-buffer buffer (buffer-string))))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(should-error
|
||||
(ebox-selector-update-buffer
|
||||
buffer "#root" :border-top '(2 solid "#D97757")))
|
||||
(should (eq state (ebox--buffer-render-state buffer)))
|
||||
(should (equal-including-properties
|
||||
text (with-current-buffer buffer (buffer-string)))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-side-border-color-patch-replaces-paint-exactly ()
|
||||
"Side color replacements should match the full-render paint result."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((layout
|
||||
(ebox-test-box
|
||||
(ebox-test-text "Border") :width 80
|
||||
:border-left-pixel 2 :border-left-color "#AA0000"
|
||||
:border-right-pixel 2 :border-right-color "#AA0000"))
|
||||
:border-left-width 2 :border-left-style 'solid
|
||||
:border-left-color "#AA0000"
|
||||
:border-right-width 2 :border-right-style 'solid
|
||||
:border-right-color "#AA0000"))
|
||||
(region-id (car (ebox-region-ids layout))))
|
||||
(ebox-test--with-rendered-buffer layout
|
||||
(ebox-test--region-update region-id
|
||||
:border-left-color nil
|
||||
:border-right-color nil)
|
||||
(dolist (role '(bl br))
|
||||
(let ((face
|
||||
(ebox-test--face-at-property
|
||||
(buffer-string) (alist-get role ebox-region-types))))
|
||||
(should
|
||||
(ebox-test--face-has-key-value-p face :inverse-video t))
|
||||
(should-not (ebox-test--face-has-key-p face :background))))
|
||||
(should (ebox-test--buffer-visually-matches-runtime-render-p))
|
||||
(ebox-test--region-update region-id
|
||||
:border-left-color "#00AA00"
|
||||
:border-right-color "#00AA00")
|
||||
@ -4886,7 +4892,7 @@
|
||||
"Runtime rerender should resolve viewport-sized boxes with the new context."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((node (let ((ebox-viewport-width 240))
|
||||
(ebox-test-box (ebox-test-text "Viewport") :width '(viewport) :border t)))
|
||||
(ebox-test-box (ebox-test-text "Viewport") :width '(viewport) :border '(1 solid))))
|
||||
buffer)
|
||||
(unwind-protect
|
||||
(progn
|
||||
@ -5510,7 +5516,8 @@
|
||||
(let* ((layout
|
||||
(ebox-test-box :id "root" :height 4 :box-sizing 'border-box :overflow
|
||||
'scroll :padding-top 1 :padding-bottom 1 :border-top-p
|
||||
t :border-top-color "red" :border-bottom-p t
|
||||
t :border-top-color "red"
|
||||
:border-bottom-width 1 :border-bottom-style 'solid
|
||||
:border-bottom-color "blue"
|
||||
(ebox-test-column
|
||||
(ebox-test-box :id "a" (ebox-test-text "A") :height 1)
|
||||
@ -6175,7 +6182,7 @@
|
||||
:text-align 'left))
|
||||
(right (ebox-test-box (ebox-test-text "Right") :width '(80) :height 1))
|
||||
(layout (ebox-test-row left right))
|
||||
(left-id (car (ebox-region-ids layout))))
|
||||
(left-id (car (ebox-region-ids left))))
|
||||
(ebox-test--with-rendered-buffer layout
|
||||
(ebox-test--region-update left-id :text-align 'center)
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
@ -6189,6 +6196,51 @@
|
||||
(buffer-substring-no-properties (point-min) (point-max))))
|
||||
(should (ebox-test--buffer-propertized-matches-runtime-render-p)))))
|
||||
|
||||
(ert-deftest ebox-region-update-completes-border-width-style-pairs ()
|
||||
"A one-property border delta should retain its computed counterpart."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((input
|
||||
(ebox-test-box (ebox-test-text "Border") :width 80
|
||||
:border-left '(2 none "#AA0000")))
|
||||
(region-id (car (ebox-region-ids input))))
|
||||
(ebox-test--with-rendered-buffer input
|
||||
(ebox-test--region-update region-id :border-left-style 'solid)
|
||||
(let ((box (gethash region-id (ebox--buffer-region-box-table
|
||||
(current-buffer)))))
|
||||
(should (= (ebox-get box :border-left-pixel) 2)))
|
||||
(ebox-test--region-update region-id :border-left-width 3)
|
||||
(let ((box (gethash region-id (ebox--buffer-region-box-table
|
||||
(current-buffer)))))
|
||||
(should (= (ebox-get box :border-left-pixel) 3)))
|
||||
(ebox-test--region-update region-id :border-left-style 'none)
|
||||
(let ((box (gethash region-id (ebox--buffer-region-box-table
|
||||
(current-buffer)))))
|
||||
(should (= (ebox-get box :border-left-pixel) 0)))
|
||||
(should (ebox-test--buffer-propertized-matches-runtime-render-p)))))
|
||||
|
||||
(ert-deftest ebox-region-update-projects-outer-and-layout-config ()
|
||||
"Region updates should project structural and LayoutConfig facts once."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((input
|
||||
(ebox-test-row :id "axis" :outer 'inline :item-gap 1
|
||||
(ebox-test-box (ebox-test-text "A"))
|
||||
(ebox-test-box (ebox-test-text "B"))))
|
||||
(buffer (ebox-render-to-buffer
|
||||
(generate-new-buffer-name " *ebox-axis-update*") input)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(ebox-selector-update-buffer
|
||||
buffer "#axis" :outer 'block :item-gap 3)
|
||||
(let* ((root (plist-get (ebox--buffer-render-state buffer)
|
||||
:root-node))
|
||||
(props (ebox-layout-config-props
|
||||
(ebox-box-node-layout root))))
|
||||
(should (equal (plist-get root :display) '(block row)))
|
||||
(should (= (plist-get props :item-gap) 3))
|
||||
(should (with-current-buffer buffer
|
||||
(ebox-test--buffer-propertized-matches-runtime-render-p)))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-region-update-border-box-inline-change-uses-span-patch ()
|
||||
"Border-box left/right spacing can be internal when outer width is fixed."
|
||||
(ebox-test--reset-runtime-state)
|
||||
@ -6196,7 +6248,7 @@
|
||||
:box-sizing 'border-box))
|
||||
(region-id (car (ebox-region-ids box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(ebox-test--region-update region-id :padding-left-pixel 8)
|
||||
(ebox-test--region-update region-id :padding-left '(8))
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
(should (eq (plist-get report :strategy) 'span-patch))
|
||||
(should (equal (plist-get report :patch-ops) '(span-patch)))
|
||||
@ -6209,7 +6261,7 @@
|
||||
:box-sizing 'content-box))
|
||||
(region-id (car (ebox-region-ids box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(ebox-test--region-update region-id :padding-left-pixel 8)
|
||||
(ebox-test--region-update region-id :padding-left '(8))
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
(should (eq (plist-get report :strategy) 'owner-rerender))
|
||||
(should (equal (plist-get report :patch-ops) '(owner-rerender)))
|
||||
@ -6224,7 +6276,7 @@
|
||||
:wrap-mode 'char))
|
||||
(region-id (car (ebox-region-ids box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(ebox-test--region-update region-id :padding-left-pixel 100)
|
||||
(ebox-test--region-update region-id :padding-left '(100))
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
(should (memq (plist-get report :strategy)
|
||||
'(span-patch owner-rerender)))
|
||||
@ -6233,27 +6285,35 @@
|
||||
(should-not (plist-get report :tp-scope-fallback))
|
||||
(should (ebox-test--buffer-propertized-matches-runtime-render-p))))))
|
||||
|
||||
(ert-deftest ebox-region-update-matrix-covers-all-box-longhands ()
|
||||
"The update tests should classify every non-content box longhand."
|
||||
(let ((covered (append (ebox-test--region-update-case-properties
|
||||
(ert-deftest ebox-region-update-matrix-uses-only-public-properties ()
|
||||
"Region update coverage must not preserve private engine spellings."
|
||||
(dolist (property
|
||||
(append (ebox-test--region-update-case-properties
|
||||
ebox-test--region-update-paint-cases)
|
||||
(ebox-test--region-update-case-properties
|
||||
ebox-test--region-update-geometry-cases))))
|
||||
(should-not (cl-set-difference ebox--region-update-longhand-props
|
||||
covered
|
||||
:test #'eq))
|
||||
(should-not (cl-set-difference covered
|
||||
ebox--region-update-longhand-props
|
||||
:test #'eq))
|
||||
(should-not (cl-set-difference ebox--paint-style-signature-keys
|
||||
(ebox-test--region-update-case-properties
|
||||
ebox-test--region-update-geometry-cases)))
|
||||
(should (or (memq property ebox--region-update-control-properties)
|
||||
(ebox-style-schema-id property))))
|
||||
(dolist (property '(:padding-left-pixel :padding-right-pixel
|
||||
:padding-top-height :padding-bottom-height
|
||||
:margin-left-pixel :margin-right-pixel
|
||||
:margin-top-height :margin-bottom-height
|
||||
:border-left-pixel :border-right-pixel
|
||||
:border-top-pixel :border-bottom-pixel
|
||||
:border-left-style :border-right-style
|
||||
:border-top-style :border-bottom-style
|
||||
:vertical-align))
|
||||
(should-not
|
||||
(memq property
|
||||
(append (ebox-test--region-update-case-properties
|
||||
ebox-test--region-update-paint-cases)
|
||||
:test #'eq))))
|
||||
(ebox-test--region-update-case-properties
|
||||
ebox-test--region-update-geometry-cases))))))
|
||||
|
||||
(ert-deftest ebox-region-update-all-paint-longhands-use-paint-patch ()
|
||||
"Every paint longhand should patch text properties without replacing spans."
|
||||
(dolist (case ebox-test--region-update-paint-cases)
|
||||
(pcase-let ((`(,property ,value ,expected) case))
|
||||
(pcase-let ((`(,property ,value ,engine-property ,expected) case))
|
||||
(ert-info ((format "property %S" property))
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((box (ebox-test-box (ebox-test-text "Paint") :width '(120)
|
||||
@ -6270,7 +6330,8 @@
|
||||
(should (eq (gethash region-id ebox--region-box-table)
|
||||
runtime-box))
|
||||
(should-not (eq runtime-box box))
|
||||
(should (equal (ebox-get runtime-box property) expected)))
|
||||
(should (equal (ebox-get runtime-box engine-property)
|
||||
expected)))
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
(should (eq (plist-get report :strategy) 'paint-patch))
|
||||
(should (equal (plist-get report :patch-ops)
|
||||
@ -6281,7 +6342,8 @@
|
||||
(ert-deftest ebox-region-update-all-geometry-longhands-use-minimal-strategy ()
|
||||
"Every geometry longhand should use a correct scoped non-root strategy."
|
||||
(dolist (case ebox-test--region-update-geometry-cases)
|
||||
(pcase-let ((`(,property ,value ,expected ,_strategy) case))
|
||||
(pcase-let
|
||||
((`(,property ,value ,engine-property ,expected ,_strategy) case))
|
||||
(ert-info ((format "property %S" property))
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((box (ebox-test-box (ebox-test-text "Alpha\nBeta\nGamma\nDelta")
|
||||
@ -6301,10 +6363,11 @@
|
||||
(should (eq (gethash region-id ebox--region-box-table)
|
||||
runtime-box))
|
||||
(should-not (eq runtime-box box))
|
||||
(should (equal (ebox-get runtime-box property) expected)))
|
||||
(should (equal (ebox-get runtime-box engine-property)
|
||||
expected)))
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
(should (memq (plist-get report :strategy)
|
||||
'(span-patch owner-rerender)))
|
||||
'(paint-patch span-patch owner-rerender)))
|
||||
(should (equal (plist-get report :dirty-kinds) '(geometry)))
|
||||
(should (eq (plist-get report :owner-type) 'box))
|
||||
(should-not (plist-get report :tp-full-root))
|
||||
@ -6373,43 +6436,6 @@
|
||||
(should (equal (plist-get report :dirty-kinds) '(paint)))
|
||||
(should-not (plist-get report :full-rerender)))))))
|
||||
|
||||
(ert-deftest ebox-vertical-align-middle-alias-matches-center ()
|
||||
"Creation and updates should normalize middle to center before rendering."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let ((middle (ebox-test-box (ebox-test-text "X") :height 3
|
||||
:vertical-align 'middle)))
|
||||
(should (equal (ebox--expand-property :vertical-align 'middle)
|
||||
'(:vertical-align center)))
|
||||
(should (eq (ebox-get (ebox-test-root middle) :vertical-align)
|
||||
'center))
|
||||
(should (= (cl-position-if
|
||||
(lambda (line)
|
||||
(string-match-p "X" (substring-no-properties line)))
|
||||
(ebox-string-lines (ebox-render middle)))
|
||||
1))
|
||||
(let* ((updated (ebox-test-box (ebox-test-text "X") :height 3
|
||||
:vertical-align 'top))
|
||||
(region-id (car (ebox-region-ids updated))))
|
||||
(ebox-test--with-rendered-buffer updated
|
||||
(goto-char (point-min))
|
||||
(search-forward "X")
|
||||
(should (= (line-number-at-pos (match-beginning 0)) 1))
|
||||
(ebox-test--region-update region-id :vertical-align 'middle)
|
||||
(should (eq (ebox-style-node-specified-value
|
||||
updated :vertical-align)
|
||||
'top))
|
||||
(should (eq (ebox-get (gethash region-id
|
||||
(ebox--buffer-region-box-table
|
||||
(current-buffer)))
|
||||
:vertical-align)
|
||||
'center))
|
||||
(goto-char (point-min))
|
||||
(search-forward "X")
|
||||
(should (= (line-number-at-pos (match-beginning 0)) 2))
|
||||
(should (eq (plist-get (ebox-test--buffer-update-report) :strategy)
|
||||
'span-patch))
|
||||
(should (ebox-test--buffer-visually-matches-runtime-render-p))))))
|
||||
|
||||
(ert-deftest ebox-update-reuses-shorthand-property-expansion ()
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((box (ebox-test-box (ebox-test-text "Box") :width 60))
|
||||
@ -6417,7 +6443,7 @@
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(ebox-test--region-update region-id
|
||||
:padding '(1 2)
|
||||
:border '(2 "#abcdef")
|
||||
:border '(1 "#abcdef")
|
||||
:margin-inline 3)
|
||||
(let ((runtime-box
|
||||
(gethash region-id (ebox--buffer-region-box-table
|
||||
@ -6426,8 +6452,7 @@
|
||||
(should (= (ebox-get runtime-box :padding-top-height) 1))
|
||||
(should (= (ebox-get runtime-box :padding-right-pixel)
|
||||
(* 2 (ebox--space-pixel-width))))
|
||||
(should (= (ebox-get runtime-box :border-left-pixel)
|
||||
(* 2 (ebox--space-pixel-width))))
|
||||
(should (= (ebox-get runtime-box :border-left-pixel) 1))
|
||||
(should (equal (ebox-get runtime-box :border-right-color) "#abcdef"))
|
||||
(should (= (ebox-get runtime-box :margin-left-pixel)
|
||||
(* 3 (ebox--space-pixel-width))))))))
|
||||
@ -6461,8 +6486,7 @@
|
||||
(should (= (ebox-get runtime-box :padding-top-height) 1))
|
||||
(should (= (ebox-get runtime-box :padding-right-pixel)
|
||||
(* 2 (ebox--space-pixel-width))))
|
||||
(should (= (ebox-get runtime-box :border-left-pixel)
|
||||
(ebox--space-pixel-width)))
|
||||
(should (= (ebox-get runtime-box :border-left-pixel) 1))
|
||||
(should (equal (ebox-get runtime-box :border-right-color) "#abcdef"))
|
||||
(should (= (ebox-get runtime-box :margin-left-pixel)
|
||||
(* 3 (ebox--space-pixel-width)))))
|
||||
@ -6585,15 +6609,13 @@
|
||||
(ebox-test--region-update region-id :color "#fedcba")
|
||||
(ebox-test--assert-visible-overflow-paint
|
||||
(buffer-string) "#fedcba" "#123456" region-id)
|
||||
(let ((published (buffer-string)))
|
||||
(should-error
|
||||
(ebox-test--region-update region-id :color nil)
|
||||
(cl-loop for line in (ebox-string-lines (buffer-string))
|
||||
for glyph in '(?A ?B ?C)
|
||||
for glyph-pos = (cl-position glyph line)
|
||||
do (should (integerp glyph-pos))
|
||||
do (should-not
|
||||
(ebox-test--face-has-key-p
|
||||
(get-text-property glyph-pos 'face line)
|
||||
:foreground))))))
|
||||
:type 'error)
|
||||
(should (equal-including-properties published (buffer-string)))
|
||||
(ebox-test--assert-visible-overflow-paint
|
||||
(buffer-string) "#fedcba" "#123456" region-id)))))
|
||||
|
||||
(ert-deftest ebox-owner-rerender-keeps-visible-overflow-unowned ()
|
||||
"Owner rerenders should not restore box ownership to visible overflow."
|
||||
@ -6856,7 +6878,6 @@
|
||||
:width 140
|
||||
:height 5
|
||||
:overflow 'scroll
|
||||
:vertical-align 'top
|
||||
:color "#112233"
|
||||
:bgcolor "#ddeeff"))
|
||||
(region-id (ebox--ensure-region-id (ebox-test-root box)))
|
||||
@ -6864,7 +6885,6 @@
|
||||
:width 140
|
||||
:height 5
|
||||
:overflow 'hidden
|
||||
:vertical-align 'top
|
||||
:color "#112233"
|
||||
:bgcolor "#ddeeff"))
|
||||
(expected (progn
|
||||
@ -6937,7 +6957,7 @@
|
||||
(ebox-test-box (ebox-test-text "") :width '(viewport)
|
||||
:height 1)
|
||||
(ebox-test-box :id "root" :width '(viewport) :height
|
||||
'(- (viewport-height) 2) :overflow
|
||||
'(viewport-height) :overflow
|
||||
'scroll
|
||||
(apply #'ebox-test-column
|
||||
(append (list :width '(viewport) :padding '(1 (8)))
|
||||
@ -6979,7 +6999,7 @@
|
||||
:rendered-lines
|
||||
(length (plist-get state
|
||||
:rendered-content-lines)))))
|
||||
(should (= (plist-get state :content-height) 12)))
|
||||
(should (= (plist-get state :content-height) 14)))
|
||||
(when-let* ((materialize
|
||||
(plist-get state :materialize-content-lines)))
|
||||
(plist-put
|
||||
@ -7008,7 +7028,7 @@
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((layout
|
||||
(ebox-test-box :id "root" :width '(viewport) :height
|
||||
'(- (viewport-height) 2) :overflow 'scroll
|
||||
'(viewport-height) :overflow 'scroll
|
||||
(apply #'ebox-test-column
|
||||
(append (list :width '(viewport))
|
||||
(cl-loop for idx below 40 collect
|
||||
@ -7026,7 +7046,7 @@
|
||||
(ebox--intrinsic-layout-measurement t))
|
||||
(setq rendered (ebox-render layout)))
|
||||
(should (= calls 0))
|
||||
(should (= (ebox-string-height rendered) 10))
|
||||
(should (= (ebox-string-height rendered) 12))
|
||||
(let ((ebox-viewport-width 240)
|
||||
(ebox-viewport-height 12)
|
||||
(ebox--intrinsic-layout-measurement nil))
|
||||
@ -7038,7 +7058,7 @@
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((layout
|
||||
(ebox-test-box :id "root" :width '(viewport) :height
|
||||
'(- (viewport-height) 10) :overflow 'scroll
|
||||
'(viewport-height) :overflow 'scroll
|
||||
(apply #'ebox-test-column
|
||||
(append (list :width '(viewport))
|
||||
(cl-loop for idx below 80 collect
|
||||
@ -7084,7 +7104,7 @@
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((layout
|
||||
(ebox-test-box :id "root" :width '(viewport) :height
|
||||
'(- (viewport-height) 10) :overflow 'scroll
|
||||
'(viewport-height) :overflow 'scroll
|
||||
(apply #'ebox-test-column
|
||||
(append (list :width '(viewport))
|
||||
(cl-loop for idx below 80 collect
|
||||
@ -7148,7 +7168,7 @@
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((layout
|
||||
(ebox-test-box :id "root" :width '(viewport) :height
|
||||
'(- (viewport-height) 10) :overflow 'scroll
|
||||
'(viewport-height) :overflow 'scroll
|
||||
(apply #'ebox-test-column
|
||||
(append (list :width '(viewport))
|
||||
(cl-loop for idx below 80 collect
|
||||
@ -7335,36 +7355,6 @@
|
||||
(should (equal expected-signatures actual-signatures))
|
||||
(should-not producer)))
|
||||
|
||||
(ert-deftest ebox-flex-window-aligned-wrapper-falls-back-to-full-prefix ()
|
||||
"Non-left-aligned flex prefixes should use the full-render fallback."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((node
|
||||
(apply #'ebox-test-flex
|
||||
(append
|
||||
(list :width '(100) :text-align 'center :flex-flow '(row wrap)
|
||||
:row-gap 0 :column-gap 0)
|
||||
(cl-loop for char across "ABCDEF" collect
|
||||
(ebox-test-box (ebox-test-text (char-to-string char)) :width
|
||||
'(50) :height 1)))))
|
||||
(full-lines (ebox-string-lines (ebox-render node)))
|
||||
(expected-signatures
|
||||
(mapcar #'ebox-test--visual-line-signature
|
||||
(seq-take full-lines 3)))
|
||||
(ebox--render-cache-table (make-hash-table :test 'equal))
|
||||
(ebox--render-cache-signature-cache (make-hash-table :test 'eq))
|
||||
(first (ebox-test--call-input-node
|
||||
#'ebox--render-stack-window-lines node 1))
|
||||
(extended
|
||||
(ebox-test--call-input-node
|
||||
#'ebox--render-stack-window-lines node 3 (plist-get first :cache)))
|
||||
(producer (ebox-test--call-input-node
|
||||
#'ebox--render-node-window-lines node 3))
|
||||
(actual-signatures
|
||||
(mapcar #'ebox-test--visual-line-signature
|
||||
(plist-get extended :lines))))
|
||||
(should (equal expected-signatures actual-signatures))
|
||||
(should-not producer)))
|
||||
|
||||
(ert-deftest ebox-canonical-column-scroll-window-renders-a-bounded-prefix ()
|
||||
"A canonical Column should expose only the requested scroll prefix."
|
||||
(ebox-test--reset-runtime-state)
|
||||
@ -7809,27 +7799,29 @@
|
||||
(= 1
|
||||
(ebox--flex-window-completion-prefix-tail-rewrite-count
|
||||
(list :box (ebox-test-root
|
||||
(ebox-test-box :border-bottom-p t
|
||||
(ebox-test-box :border-bottom-width 1
|
||||
:border-bottom-style 'solid
|
||||
:padding-bottom 0))))))
|
||||
(should
|
||||
(= 1
|
||||
(ebox--flex-window-completion-prefix-tail-rewrite-count
|
||||
(list :box (ebox-test-root
|
||||
(ebox-test-box :border-bottom-p t
|
||||
(ebox-test-box :border-bottom-width 1
|
||||
:border-bottom-style 'solid
|
||||
:padding-bottom 0
|
||||
:margin-bottom 2))))))
|
||||
(should
|
||||
(= 0
|
||||
(ebox--flex-window-completion-prefix-tail-rewrite-count
|
||||
(list :box (ebox-test-root
|
||||
(ebox-test-box :border-bottom-p t
|
||||
(ebox-test-box :border-bottom-width 1
|
||||
:border-bottom-style 'solid
|
||||
:padding-bottom 2))))))
|
||||
(should
|
||||
(= 0
|
||||
(ebox--flex-window-completion-prefix-tail-rewrite-count
|
||||
(list :box (ebox-test-root
|
||||
(ebox-test-box :border-bottom-p nil
|
||||
:padding-bottom 0)))))))
|
||||
(ebox-test-box :padding-bottom 0)))))))
|
||||
|
||||
(ert-deftest ebox-flex-window-cache-rejects-short-incomplete-exact-hit ()
|
||||
"An underfilled exact-limit cache must resume from its prefix seed."
|
||||
@ -11253,8 +11245,8 @@ explicit visible-window handoff may admit its full-content publication."
|
||||
(ebox-test-box :id "width-only" :width '(viewport))
|
||||
(ebox-test-box :id "height-only" :height
|
||||
'(viewport-height))
|
||||
(ebox-test-flex :id "flex-height" :min-height
|
||||
'(- (viewport-height) 1)
|
||||
(ebox-test-flex :id "flex-height" :height
|
||||
'(viewport-height)
|
||||
(ebox-test-box (ebox-test-text "Flex")))))))
|
||||
(let* ((state (ebox--buffer-render-state buffer))
|
||||
(root (plist-get state :root-node))
|
||||
@ -12475,18 +12467,18 @@ explicit visible-window handoff may admit its full-content publication."
|
||||
(let* ((box (ebox-test-box (ebox-test-text "Atomic")
|
||||
:width '(120)
|
||||
:bgcolor "#111111"
|
||||
:padding-left-pixel 2))
|
||||
:padding-left 2))
|
||||
(region-id (car (ebox-region-ids box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(should-error
|
||||
(ebox-test--region-update region-id
|
||||
:bgcolor "#222222"
|
||||
:padding-left-pixel -1)
|
||||
:padding-left -1)
|
||||
:type 'error)
|
||||
(should (equal (ebox-style-node-specified-value box :bgcolor)
|
||||
"#111111"))
|
||||
(should (= (ebox-style-node-specified-value
|
||||
box :padding-left-pixel)
|
||||
box :padding-left)
|
||||
2)))))
|
||||
|
||||
(ert-deftest ebox-region-update-no-op-skips-rollback-snapshot ()
|
||||
@ -12724,7 +12716,7 @@ explicit visible-window handoff may admit its full-content publication."
|
||||
(ebox-test-box (ebox-test-text "Border")
|
||||
:width 60
|
||||
:padding '(1 1)
|
||||
:border t)))
|
||||
:border '(1 solid))))
|
||||
(left-face (ebox-test--face-at-property rendered 'ebox-bl))
|
||||
(right-face (ebox-test--face-at-property rendered 'ebox-br))
|
||||
(top-face (ebox-test--face-at-property rendered 'ebox-bt))
|
||||
@ -13034,7 +13026,7 @@ publication with properties intact."
|
||||
(ebox-test-box (ebox-test-text "Header line") :id 'header
|
||||
:width (list 300))
|
||||
(ebox-test-box (ebox-test-text "Secret content") :id 'secret
|
||||
:width (list 300) :border t
|
||||
:width (list 300) :border '(1 solid)
|
||||
:bgcolor "#224466" :color "#FFFFFF")
|
||||
(ebox-test-box (ebox-test-text "Footer line") :id 'footer
|
||||
:width (list 300))))))
|
||||
@ -13072,11 +13064,11 @@ face patch must match that exactly (issue014)."
|
||||
(lambda (parent-color)
|
||||
(ebox-test-box
|
||||
:id 'parent :width (list 400)
|
||||
:border (list '(4) 'solid parent-color)
|
||||
:border (list 1 'solid parent-color)
|
||||
:padding '(1 (12))
|
||||
(ebox-test-column
|
||||
(ebox-test-box :id 'child (ebox-test-text "CHILD") :width (list 200)
|
||||
:border '((4) solid "#00AA00"))))))
|
||||
:border '(1 solid "#00AA00"))))))
|
||||
(visual-runs
|
||||
(lambda (buffer role)
|
||||
(with-current-buffer buffer
|
||||
|
||||
@ -113,7 +113,7 @@
|
||||
sum (ebox-dsl-test--tree-count child predicate)))))
|
||||
|
||||
(ert-deftest ebox-style-expands-ebox-aliases-to-canonical-longhands ()
|
||||
"Existing Ebox aliases should normalize to CSS-like longhand properties."
|
||||
"Approved aliases and shorthands should produce canonical longhands."
|
||||
(let ((style (ebox-style-compute
|
||||
(list :bgcolor "#ffffff"
|
||||
:padding '(1 2)
|
||||
@ -124,7 +124,7 @@
|
||||
(should (= (plist-get style :padding-inline-end) 2))
|
||||
(should (= (plist-get style :padding-block-end) 1))
|
||||
(should (= (plist-get style :padding-inline-start) 2))
|
||||
(should (= (plist-get style :margin-inline-start) 3))
|
||||
(should (= (plist-get style :margin-left) 3))
|
||||
(should (equal (plist-get style :border-color) "#333333"))))
|
||||
|
||||
(ert-deftest ebox-style-classifies-incremental-effects ()
|
||||
@ -134,8 +134,8 @@
|
||||
(should (eq (ebox-style-dirty-kind :padding-inline-start) 'geometry))
|
||||
(should (eq (ebox-style-dirty-kind :width) 'geometry))
|
||||
(should (eq (ebox-style-dirty-kind :outer) 'structure))
|
||||
(should (eq (ebox-style-dirty-kind :layout) 'structure))
|
||||
(should (eq (ebox-style-dirty-kind :display) 'structure)))
|
||||
(should-not (ebox-style-dirty-kind :layout))
|
||||
(should-not (ebox-style-dirty-kind :display)))
|
||||
|
||||
(ert-deftest ebox-typed-form-projects-outer-and-layout-to-display ()
|
||||
"A typed Box form should project its two axes to one display pair."
|
||||
@ -143,6 +143,14 @@
|
||||
(should (equal (ebox--computed-display (ebox-dsl-test--root node))
|
||||
'(inline flex)))))
|
||||
|
||||
(ert-deftest ebox-inline-outer-participates-in-the-cascade ()
|
||||
"An inline outer declaration should outrank a matching stylesheet rule."
|
||||
(let ((ebox-style-stylesheet (ecss-stylesheet-create)))
|
||||
(ebox-style-add-rule ".card" '(:outer block))
|
||||
(let ((node (ebox-dsl-test--root
|
||||
(ebox-build '(box :class "card" :outer inline "A")))))
|
||||
(should (equal (ebox--computed-display node) '(inline flow))))))
|
||||
|
||||
(ert-deftest ebox-style-unrelated-facts-preserve-typed-layout-config ()
|
||||
"Unrelated computed facts must not replace direct typed layout values."
|
||||
(dolist (case '((flex
|
||||
@ -174,6 +182,80 @@
|
||||
(equal (ebox-style-compile-declarations '(:bgcolor "red") t)
|
||||
(ebox-style-compile-declarations '(:background-color "red") t))))
|
||||
|
||||
(ert-deftest ebox-canonical-property-vocabulary-is-the-only-author-path ()
|
||||
"The frozen G4b vocabulary should have one canonical spelling and owner."
|
||||
(dolist (entry '((:font-size . ebox/font-size)
|
||||
(:font-style . ebox/font-style)
|
||||
(:text-decoration-line . ebox/text-decoration-line)
|
||||
(:text-decoration-color . ebox/text-decoration-color)
|
||||
(:text-decoration-style . ebox/text-decoration-style)
|
||||
(:item-gap . ebox/item-gap)
|
||||
(:cross-align . ebox/cross-align)
|
||||
(:justify-self . ebox/justify-self)))
|
||||
(should (eq (ebox-style-schema-id (car entry)) (cdr entry))))
|
||||
(should (eq (ebox-style-schema-id :bgcolor)
|
||||
(ebox-style-schema-id :background-color)))
|
||||
(should (eq (ebox-style-schema-id :font-slant)
|
||||
(ebox-style-schema-id :font-style)))
|
||||
(dolist (entry '((:padding-top . ebox/padding-top)
|
||||
(:padding-right . ebox/padding-right)
|
||||
(:padding-bottom . ebox/padding-bottom)
|
||||
(:padding-left . ebox/padding-left)
|
||||
(:margin-top . ebox/margin-top)
|
||||
(:margin-right . ebox/margin-right)
|
||||
(:margin-bottom . ebox/margin-bottom)
|
||||
(:margin-left . ebox/margin-left)))
|
||||
(should (eq (ebox-style-schema-id (car entry)) (cdr entry))))
|
||||
(should-not (eq (ebox-style-schema-id :padding-top)
|
||||
(ebox-style-schema-id :padding-block-start)))
|
||||
(should-not (eq (ebox-style-schema-id :margin-left)
|
||||
(ebox-style-schema-id :margin-inline-start)))
|
||||
(dolist (name '(:font :font-height :border-bottom-p
|
||||
:grid-row-gap :grid-column-gap :vertical-align
|
||||
:display :layout :content :surface-properties :face
|
||||
:padding-top-height :padding-right-pixel
|
||||
:padding-bottom-height :padding-left-pixel
|
||||
:margin-top-height :margin-right-pixel
|
||||
:margin-bottom-height :margin-left-pixel
|
||||
:border-right-pixel :border-left-pixel))
|
||||
(should-not (ebox-style-schema-id name)))
|
||||
(dolist (name '(:border-top-p :grid-column-span :grid-row-span))
|
||||
(should (plist-get (ebox-style-property name) :shorthand))))
|
||||
|
||||
(ert-deftest ebox-normal-text-align-is-not-a-layout-wide-property ()
|
||||
"Only the Normal Box form owns text alignment author input."
|
||||
(should (ebox-build '(box :text-align center "A")))
|
||||
(dolist (tag '(row column flex grid))
|
||||
(should-error
|
||||
(ebox-build (list tag :text-align 'center "A"))
|
||||
:type 'error)))
|
||||
|
||||
(ert-deftest ebox-edge-projection-uses-one-ecss-winner-order ()
|
||||
"Logical and physical edges conflict locally and cascade across sources."
|
||||
(should-error
|
||||
(ebox-style-compile-declarations
|
||||
'(:padding-left (4) :padding-inline-start (8)) t)
|
||||
:type 'ecss-invalid-declaration)
|
||||
(let* ((base (ebox-style-compile-declarations '(:padding (0 (8))) t))
|
||||
(override
|
||||
(ebox-style-compile-declarations '(:padding-left (24)) t))
|
||||
(style
|
||||
(ebox-style-compute-subject
|
||||
(ecss-subject-create :type "box")
|
||||
(ebox-style-merge-declarations base override))))
|
||||
(should (equal (plist-get (ebox-style--box-values style) :padding-left)
|
||||
'(24))))
|
||||
(let ((ebox-style-stylesheet (ecss-stylesheet-create)))
|
||||
(ebox-style-add-rule ".card" '(:margin-inline-start (2)))
|
||||
(ebox-style-add-rule "#target" '(:margin-left (6)))
|
||||
(let ((style
|
||||
(ebox-style-compute-subject
|
||||
(ecss-subject-create
|
||||
:type "box" :id "target" :classes '("card"))
|
||||
nil)))
|
||||
(should (equal (plist-get (ebox-style--box-values style) :margin-left)
|
||||
'(6))))))
|
||||
|
||||
(ert-deftest ebox-style-rejects-canonical-and-alias-duplicates ()
|
||||
"One declaration source should not spell one property twice."
|
||||
(should-error
|
||||
@ -185,22 +267,24 @@
|
||||
'(:background-color "red" :bgcolor "blue") t)
|
||||
:type 'error))
|
||||
|
||||
(ert-deftest ebox-style-preserves-ordered-shorthand-longhand-normalization ()
|
||||
"Ebox author pairs remain ordered normalization groups before cutover."
|
||||
(let ((border-then-top
|
||||
(ebox-style-compile-declarations
|
||||
'(:border "red" :border-top none) t))
|
||||
(top-then-border
|
||||
(ebox-style-compile-declarations
|
||||
'(:border-top none :border "red") t)))
|
||||
(should (eq 'none
|
||||
(plist-get border-then-top 'ebox/border-top-style)))
|
||||
(should (equal "red"
|
||||
(plist-get border-then-top 'ebox/border-right-color)))
|
||||
(should (eq 'solid
|
||||
(plist-get top-then-border 'ebox/border-top-style)))
|
||||
(should (equal "red"
|
||||
(plist-get top-then-border 'ebox/border-top-color)))))
|
||||
(ert-deftest ebox-author-declaration-is-one-atomic-normalization-group ()
|
||||
"One author group rejects overlaps while later cascade groups may override."
|
||||
(dolist (declarations
|
||||
'((:padding 1 :padding-left 2)
|
||||
(:border "red" :border-top none)
|
||||
(:gap 1 :row-gap 2)
|
||||
(:flex-flow (row wrap) :flex-wrap nowrap)
|
||||
(:flex (1 1 auto) :flex-grow 2)
|
||||
(:border-top-p t :border-top-style none)))
|
||||
(should-error
|
||||
(ebox-style-compile-declarations declarations t)
|
||||
:type 'ecss-invalid-declaration))
|
||||
(should
|
||||
(equal
|
||||
(ebox-style-merge-declarations
|
||||
(ebox-style-compile-declarations '(:gap 1) t)
|
||||
(ebox-style-compile-declarations '(:row-gap 2) t))
|
||||
'(ebox/row-gap 2 ebox/column-gap 1))))
|
||||
|
||||
(ert-deftest ebox-style-property-registry-rejects-alias-collisions ()
|
||||
"An alias should never shadow another canonical property."
|
||||
@ -226,7 +310,7 @@
|
||||
(setcar (plist-get metadata :contexts) 'damaged)
|
||||
(should (eq :background-color
|
||||
(plist-get (ebox-style-property :bgcolor) :name)))
|
||||
(should (equal '(box)
|
||||
(should (equal '(text box)
|
||||
(plist-get (ebox-style-property :bgcolor) :contexts)))))
|
||||
|
||||
(ert-deftest ebox-style-border-present-is-a-typed-shorthand-not-an-alias ()
|
||||
@ -239,8 +323,49 @@
|
||||
'(ebox/border-top-width 0 ebox/border-top-style none)))
|
||||
(should (eq (ebox-style-canonical-name :border-top-p) :border-top-p)))
|
||||
|
||||
(ert-deftest ebox-style-exclusive-check-does-not-reexpand-ordinary-shorthands ()
|
||||
"Conflict validation should leave the declaration cache effective."
|
||||
(ert-deftest ebox-border-width-unit-is-always-integer-pixels ()
|
||||
"Border width should not reuse Box edge column/pixel wrapper grammar."
|
||||
(should
|
||||
(equal
|
||||
(ebox-style-compile-declarations '(:border (1 solid "#687386")) t)
|
||||
'(ebox/border-top-width 1 ebox/border-top-style solid
|
||||
ebox/border-top-color "#687386"
|
||||
ebox/border-right-width 1 ebox/border-right-style solid
|
||||
ebox/border-right-color "#687386"
|
||||
ebox/border-bottom-width 1 ebox/border-bottom-style solid
|
||||
ebox/border-bottom-color "#687386"
|
||||
ebox/border-left-width 1 ebox/border-left-style solid
|
||||
ebox/border-left-color "#687386")))
|
||||
(should-error
|
||||
(ebox-style-compile-declarations
|
||||
'(:border ((1) solid "#687386")) t)
|
||||
:type 'error))
|
||||
|
||||
(ert-deftest ebox-border-engine-projection-keeps-exact-pixels ()
|
||||
"Border projection must not reinterpret pixels as character columns."
|
||||
(cl-letf (((symbol-function 'ebox--space-pixel-width) (lambda () 7)))
|
||||
(should
|
||||
(equal
|
||||
(ebox-style--expand-engine-plist
|
||||
'(:border-left-width 1 :border-left-style solid
|
||||
:border-right-width 2 :border-right-style solid
|
||||
:padding-left 1 :margin-right (3)))
|
||||
'(:border-left-pixel 1 :border-left-style solid
|
||||
:border-right-pixel 2 :border-right-style solid
|
||||
:padding-left-pixel 7 :margin-right-pixel 3))))
|
||||
(should
|
||||
(equal
|
||||
(ebox-style--expand-engine-plist
|
||||
'(:border-left-width 2 :border-left-style none))
|
||||
'(:border-left-pixel 0 :border-left-style none)))
|
||||
(should
|
||||
(equal
|
||||
(ebox-style--expand-engine-plist
|
||||
'(:border-top-width 2 :border-top-style solid))
|
||||
'(:border-top-pixel 2 :border-top-style solid))))
|
||||
|
||||
(ert-deftest ebox-style-declaration-cache-expands-shorthand-once ()
|
||||
"A cached declaration should not expand its shorthand again."
|
||||
(let ((calls 0)
|
||||
(original (symbol-function 'ebox-style--padding-shorthand)))
|
||||
(clrhash ebox-style--declaration-cache)
|
||||
@ -862,7 +987,7 @@
|
||||
"The typed Grid constructor should emit one canonical closed property set."
|
||||
(let* ((layout
|
||||
(ebox-grid-layout-create
|
||||
:grid-template-columns '((20) 1fr)
|
||||
:grid-template-columns '((20) (fr 1))
|
||||
:grid-template-rows '(1 auto)
|
||||
:grid-auto-columns '((30))
|
||||
:grid-auto-rows '(2)
|
||||
@ -897,15 +1022,28 @@
|
||||
(dolist (value ebox--grid-content-alignment-values)
|
||||
(should (ebox-grid-layout-create :justify-content value))
|
||||
(should (ebox-grid-layout-create :align-content value)))
|
||||
(dolist (value '(min-content max-content))
|
||||
(should (ebox-grid-layout-create :grid-template-columns (list value)))
|
||||
(should (ebox-grid-layout-create :grid-template-rows (list value)))
|
||||
(should (ebox-grid-layout-create :grid-auto-columns value))
|
||||
(should (ebox-grid-layout-create :grid-auto-rows value)))
|
||||
(should
|
||||
(ebox-grid-layout-create
|
||||
:grid-template-columns '((repeat 2 (auto (fr 1))))))
|
||||
(dolist (plist '((:grid-auto-flow diagonal)
|
||||
(:grid-template-columns (bogus))
|
||||
(:grid-template-rows (bogus))
|
||||
(:grid-template-columns (0fr))
|
||||
(:grid-template-columns (0.0fr))
|
||||
(:grid-template-columns (1fr))
|
||||
(:grid-template-columns (fr 1))
|
||||
(:grid-template-columns min-content)
|
||||
(:grid-template-columns ((fr 0)))
|
||||
(:grid-template-columns ((minmax (fr 1) auto)))
|
||||
(:grid-template-columns
|
||||
((minmax (minmax (10) (20)) auto)))
|
||||
(:grid-template-columns ((repeat 0 auto)))
|
||||
(:grid-auto-columns ((repeat 2 auto)))
|
||||
(:grid-auto-columns ((10) (20)))
|
||||
(:row-gap -1)
|
||||
(:column-gap (-1))
|
||||
@ -1161,7 +1299,41 @@
|
||||
(should-error (ebox-build form) :type 'error))
|
||||
(should-error
|
||||
(ebox-build (ebox-test-box (ebox-test-text "raw runtime node")))
|
||||
:type 'error)
|
||||
(should-error
|
||||
(ebox-build
|
||||
(list 'box :color (tp-computed (lambda () "#112233")) "dynamic"))
|
||||
:type 'error)
|
||||
(should-error
|
||||
(ebox-build
|
||||
(list 'box :color
|
||||
(tp-paint-slot-create '(:foreground "#112233")) "slot"))
|
||||
:type 'error))
|
||||
|
||||
(ert-deftest ebox-build-rejects-explicit-nil-outside-boolean-grammar ()
|
||||
"Internal nil initial values must not widen the public author grammar."
|
||||
(dolist (form
|
||||
'((text :color nil "A")
|
||||
(text :font-size nil "A")
|
||||
(grid :grid-template-columns nil "A")
|
||||
(grid :grid-template-rows nil "A")
|
||||
(grid :grid-auto-columns nil "A")
|
||||
(grid :grid-auto-rows nil "A")))
|
||||
(should-error (ebox-build form) :type 'error))
|
||||
(should (ebox-build '(box :border-top-p nil "A"))))
|
||||
|
||||
(ert-deftest ebox-source-metadata-uses-the-frozen-public-grammar ()
|
||||
"Key, class, and id should remain source facts with closed value domains."
|
||||
(should
|
||||
(ebox-build
|
||||
'(box :key stable :id 42 :class (card selected) "Valid")))
|
||||
(dolist (form '((box :key nil "bad")
|
||||
(box :key (not stable) "bad")
|
||||
(box :id nil "bad")
|
||||
(box :id 1.5 "bad")
|
||||
(box :class (card 2) "bad")
|
||||
(box :class (card . selected) "bad")))
|
||||
(should-error (ebox-build form) :type 'error)))
|
||||
|
||||
(provide 'ebox-dsl-tests)
|
||||
;;; ebox-dsl-tests.el ends here
|
||||
|
||||
@ -260,8 +260,8 @@
|
||||
(should (= (apply #'+ widths) 150))
|
||||
(should (> (car widths) (cadr widths)))))
|
||||
|
||||
(ert-deftest ebox-flex-basis-supports-content-and-intrinsic-keywords ()
|
||||
"Flex basis content and intrinsic keywords should use content-derived sizes."
|
||||
(ert-deftest ebox-flex-basis-supports-intrinsic-keywords ()
|
||||
"Flex basis intrinsic keywords should use content-derived sizes."
|
||||
(let* ((content "aa bbbb")
|
||||
(max-content (ebox--string-pixel-width content))
|
||||
(min-content (ebox--string-pixel-width "bbbb"))
|
||||
@ -269,7 +269,7 @@
|
||||
(ebox-test-flex :width '(300)
|
||||
(ebox-test-flex-item
|
||||
(ebox-test-box (ebox-test-text content) :width '(160) :wrap-mode 'word)
|
||||
:flex-basis 'content)
|
||||
:flex-basis 'max-content)
|
||||
(ebox-test-flex-item
|
||||
(ebox-test-box (ebox-test-text content) :width '(160) :wrap-mode 'word)
|
||||
:flex-basis 'min-content)))
|
||||
@ -733,8 +733,8 @@
|
||||
(let* ((layout
|
||||
(ebox-test-flex :width '(120)
|
||||
(ebox-test-row
|
||||
(ebox-test-box (ebox-test-text "Left") :border t)
|
||||
(ebox-test-box (ebox-test-text "Right") :border t))))
|
||||
(ebox-test-box (ebox-test-text "Left") :border '(1 solid))
|
||||
(ebox-test-box (ebox-test-text "Right") :border '(1 solid)))))
|
||||
(widths (mapcar #'ebox--string-pixel-width
|
||||
(ebox-string-lines (ebox-render layout)))))
|
||||
(should (cl-every (lambda (width) (<= width 120)) widths))))
|
||||
@ -1032,13 +1032,13 @@
|
||||
:height 3
|
||||
:box-sizing 'border-box
|
||||
:padding '(0 (8))
|
||||
:border t
|
||||
:border '(1 solid)
|
||||
:wrap-mode 'none)
|
||||
(ebox-test-box (ebox-test-text "sample box three")
|
||||
:height 3
|
||||
:box-sizing 'border-box
|
||||
:padding '(0 (8))
|
||||
:border t
|
||||
:border '(1 solid)
|
||||
:wrap-mode 'none)))
|
||||
(plain (substring-no-properties (ebox-flex-test--render layout))))
|
||||
(should-not (string-match-p "REWRAPPED" plain))
|
||||
@ -1090,7 +1090,7 @@
|
||||
(ebox-test-box (ebox-test-text "Bottom") :width '(80)))
|
||||
:width '(180)
|
||||
:box-sizing 'border-box
|
||||
:border t)))
|
||||
:border '(1 solid))))
|
||||
(plain (substring-no-properties (ebox-flex-test--render layout))))
|
||||
(should-not (string-match-p "REWRAPPED" plain))
|
||||
(should (string-match-p "Top" plain))
|
||||
@ -1126,7 +1126,7 @@
|
||||
"One child Box should own both frame and Flex participation facts."
|
||||
(let* ((left (ebox-test-box (ebox-test-text "A") :width '(60)
|
||||
:flex-grow 1 :flex-basis '(60)
|
||||
:box-sizing 'border-box :border t))
|
||||
:box-sizing 'border-box :border '(1 solid)))
|
||||
(right (ebox-test-box (ebox-test-text "B") :width '(40)))
|
||||
(layout (ebox-test-flex :width '(160) left right))
|
||||
(left-id (car (ebox-region-ids (ebox-test-root left))))
|
||||
@ -1176,20 +1176,22 @@
|
||||
(ebox-test-box (ebox-test-text "A") :width '(60) :height 1)
|
||||
(ebox-test-box (ebox-test-text "B") :width '(60) :height 1)
|
||||
(ebox-test-box (ebox-test-text "C") :width '(60) :height 1))))))
|
||||
(dolist (justify '(flex-start flex-end center space-between space-around space-evenly start end left right normal))
|
||||
(dolist (justify '(normal start end flex-start flex-end center stretch
|
||||
space-between space-around space-evenly))
|
||||
(should (stringp
|
||||
(ebox-render
|
||||
(ebox-test-flex :width '(220) :justify-content justify
|
||||
(ebox-test-box (ebox-test-text "A") :width '(60))
|
||||
(ebox-test-box (ebox-test-text "B") :width '(60)))))))
|
||||
(dolist (align '(stretch flex-start flex-end center baseline normal start end self-start self-end))
|
||||
(dolist (align '(normal start end stretch flex-start flex-end center baseline))
|
||||
(should (stringp
|
||||
(ebox-render
|
||||
(ebox-test-flex :width '(220) :height 4
|
||||
:align-items align
|
||||
(ebox-test-box (ebox-test-text "A") :width '(60) :height 1)
|
||||
(ebox-test-box (ebox-test-text "B") :width '(60) :height 1))))))
|
||||
(dolist (align '(stretch flex-start flex-end center space-between space-around space-evenly baseline normal start end))
|
||||
(dolist (align '(normal start end stretch flex-start flex-end center
|
||||
space-between space-around space-evenly))
|
||||
(should (stringp
|
||||
(ebox-render
|
||||
(ebox-test-flex :width '(130) :height 5 :flex-wrap 'wrap
|
||||
|
||||
152
tests/ebox-font-tests.el
Normal file
152
tests/ebox-font-tests.el
Normal file
@ -0,0 +1,152 @@
|
||||
;;; ebox-font-tests.el --- Font projection contracts -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'ebox)
|
||||
|
||||
(ert-deftest ebox-canonical-property-cutover-shares-one-resolved-font-fact ()
|
||||
"Real Surface measurement and paint share one capability-resolved fact."
|
||||
(let* ((ebox-font--cache (make-hash-table :test #'equal))
|
||||
(resolver-calls 0)
|
||||
(ebox-font--resolver
|
||||
(lambda (face _window)
|
||||
(cl-incf resolver-calls)
|
||||
(list :identity (list 'resolved-font (copy-tree face))
|
||||
:metrics (vector 'metrics (copy-tree face))))))
|
||||
(let* ((below
|
||||
(ebox-font--project
|
||||
:size 1.39 :weight 'normal :style 'normal
|
||||
:capability-signature 'display-a))
|
||||
(above
|
||||
(ebox-font--project
|
||||
:size 1.4 :weight 'normal :style 'normal
|
||||
:capability-signature 'display-a))
|
||||
(same
|
||||
(ebox-font--project
|
||||
:size 1.4 :weight 'normal :style 'normal
|
||||
:capability-signature 'display-a))
|
||||
(changed-capability
|
||||
(ebox-font--project
|
||||
:size 1.4 :weight 'normal :style 'normal
|
||||
:capability-signature 'display-b)))
|
||||
(should (= (ebox-font-fact-requested-decipoints below) 10))
|
||||
(should (= (ebox-font-fact-requested-decipoints above) 11))
|
||||
(should (eq above same))
|
||||
(should-not (eq above changed-capability))
|
||||
(let ((identity (ebox-font-fact-resolved-font-identity above))
|
||||
(metrics (ebox-font-fact-measurement-metrics above)))
|
||||
(setf (plist-get identity :css-weight) 900)
|
||||
(aset metrics 0 'damaged)
|
||||
(should (= (plist-get
|
||||
(ebox-font-fact-resolved-font-identity above)
|
||||
:css-weight)
|
||||
400))
|
||||
(should (eq (aref (ebox-font-fact-measurement-metrics above) 0)
|
||||
'metrics)))
|
||||
(should (= resolver-calls 3)))
|
||||
(setq resolver-calls 0
|
||||
ebox-font--cache (make-hash-table :test #'equal))
|
||||
(let ((capability 'display-a)
|
||||
(buffer (generate-new-buffer " *ebox-font-contract*")))
|
||||
(unwind-protect
|
||||
(cl-letf (((symbol-function 'ebox--display-signature-for-window)
|
||||
(lambda (_window) capability)))
|
||||
(ebox-render-to-buffer
|
||||
buffer
|
||||
(ebox-build
|
||||
'(box :width (viewport)
|
||||
(text :font-size 16 :font-style italic "A"))))
|
||||
(let* ((state-a (ebox--buffer-render-state buffer))
|
||||
(node-a
|
||||
(car (ebox-tree-node-children
|
||||
(plist-get state-a :root-node))))
|
||||
(fact-a (plist-get node-a :ebox-font-fact))
|
||||
(measurement-fact nil)
|
||||
(paint-fact nil)
|
||||
(measurement-function
|
||||
(symbol-function 'ebox-font-measurement-face))
|
||||
(paint-function (symbol-function 'ebox-font-paint-face))
|
||||
measured face)
|
||||
(cl-letf (((symbol-function 'ebox-font-measurement-face)
|
||||
(lambda (projection)
|
||||
(setq measurement-fact projection)
|
||||
(funcall measurement-function projection)))
|
||||
((symbol-function 'ebox-font-paint-face)
|
||||
(lambda (projection)
|
||||
(setq paint-fact projection)
|
||||
(funcall paint-function projection))))
|
||||
(setq measured (ebox--propertize-typography "A" node-a)
|
||||
face (ebox-buffer--font-face node-a)))
|
||||
(should (ebox-font-fact-p fact-a))
|
||||
(should (= resolver-calls 1))
|
||||
(should (= (hash-table-count ebox-font--cache) 1))
|
||||
(should (= (ebox-font-fact-requested-decipoints fact-a) 120))
|
||||
(should (eq fact-a measurement-fact))
|
||||
(should (eq fact-a paint-fact))
|
||||
(should (integerp (plist-get face :height)))
|
||||
(should (= (plist-get face :height) 120))
|
||||
(should (eq (plist-get face :slant) 'italic))
|
||||
(should (numberp (ebox-string-pixel-width measured)))
|
||||
(setq capability 'display-b)
|
||||
(let ((tp--surface-publication-step-function
|
||||
(lambda (step _surface)
|
||||
(when (eq step 'client-state)
|
||||
(error "Reject font capability publication")))))
|
||||
(should-error
|
||||
(ebox-surface-update-buffer-viewport
|
||||
buffer (plist-get state-a :viewport-width)
|
||||
(plist-get state-a :viewport-height))))
|
||||
(let* ((rolled-back (ebox--buffer-render-state buffer))
|
||||
(rolled-back-node
|
||||
(car (ebox-tree-node-children
|
||||
(plist-get rolled-back :root-node)))))
|
||||
(should (eq rolled-back state-a))
|
||||
(should (eq (plist-get rolled-back-node :ebox-font-fact)
|
||||
fact-a)))
|
||||
(ebox-surface-update-buffer-viewport
|
||||
buffer (plist-get state-a :viewport-width)
|
||||
(plist-get state-a :viewport-height))
|
||||
(let* ((state-b (ebox--buffer-render-state buffer))
|
||||
(node-b
|
||||
(car (ebox-tree-node-children
|
||||
(plist-get state-b :root-node))))
|
||||
(fact-b (plist-get node-b :ebox-font-fact)))
|
||||
(should-not (eq fact-a fact-b))
|
||||
(should (eq (ebox-font-fact-capability-signature fact-b)
|
||||
'display-b))
|
||||
(should (= resolver-calls 2))
|
||||
(should (= (hash-table-count ebox-font--cache) 2)))))
|
||||
(when (buffer-live-p buffer)
|
||||
(kill-buffer buffer))))))
|
||||
|
||||
(ert-deftest ebox-relative-font-weight-resolves-before-emacs-paint ()
|
||||
"CSS relative weight should not leak into an Emacs face attribute."
|
||||
(let ((ebox-font--cache (make-hash-table :test #'equal))
|
||||
(ebox-font--resolver
|
||||
(lambda (face _window)
|
||||
(list :identity (list :face face) :metrics nil)))
|
||||
(buffer (generate-new-buffer " *ebox-font-relative*")))
|
||||
(unwind-protect
|
||||
(cl-letf (((symbol-function 'ebox--display-signature-for-window)
|
||||
(lambda (_window) 'display-a)))
|
||||
(ebox-render-to-buffer
|
||||
buffer
|
||||
(ebox-build
|
||||
'(box :font-weight 400
|
||||
(text :font-weight bolder "Bold"))))
|
||||
(let* ((root (plist-get (ebox--buffer-render-state buffer)
|
||||
:root-node))
|
||||
(child (car (ebox-tree-node-children root)))
|
||||
(fact (plist-get child :ebox-font-fact)))
|
||||
(should (= (plist-get
|
||||
(ebox-font-fact-resolved-font-identity fact)
|
||||
:css-weight)
|
||||
700))
|
||||
(should (eq (plist-get (ebox-font-paint-face fact) :weight)
|
||||
'bold))))
|
||||
(when (buffer-live-p buffer)
|
||||
(kill-buffer buffer)))))
|
||||
|
||||
(provide 'ebox-font-tests)
|
||||
;;; ebox-font-tests.el ends here
|
||||
@ -30,17 +30,44 @@
|
||||
"Fractional tracks should fill a definite grid width."
|
||||
(let* ((node (ebox-test-grid
|
||||
:width '(120)
|
||||
:grid-template-columns '(1fr 2fr)
|
||||
:grid-template-columns '((fr 1) (fr 2))
|
||||
(ebox-test-box (ebox-test-text "A"))
|
||||
(ebox-test-box (ebox-test-text "B"))))
|
||||
(lines (ebox-string-lines (ebox-render node))))
|
||||
(should (= (length lines) 1))
|
||||
(should (= (ebox--string-pixel-width (car lines)) 120))))
|
||||
|
||||
(ert-deftest ebox-grid-intrinsic-track-keywords-use-distinct-measures ()
|
||||
"min-content and max-content should reuse Ebox intrinsic measurements."
|
||||
(let* ((left (ebox-test-root (ebox-test-text "alpha beta")))
|
||||
(right (ebox-test-root (ebox-test-text "gamma delta epsilon")))
|
||||
(entries (list (list :node left :column 1 :column-span 1)
|
||||
(list :node right :column 2 :column-span 1)))
|
||||
(rendered (make-hash-table :test #'eq))
|
||||
(tracks (ebox-grid--normalize-tracks
|
||||
'(min-content max-content) 'columns)))
|
||||
(puthash left "alpha beta" rendered)
|
||||
(puthash right "gamma delta epsilon" rendered)
|
||||
(let ((sizes (ebox-grid--resolve-sizes
|
||||
tracks 2 'columns entries rendered 0 nil)))
|
||||
(should
|
||||
(= (car sizes) (ebox--line-min-content-pixel "alpha beta")))
|
||||
(should
|
||||
(= (cadr sizes) (ebox--string-pixel-width "gamma delta epsilon")))
|
||||
(should (< (car sizes) (ebox--string-pixel-width "alpha beta"))))))
|
||||
|
||||
(ert-deftest ebox-grid-repeat-expands-a-complete-track-list ()
|
||||
"repeat should duplicate its ordered track-list body."
|
||||
(should
|
||||
(equal
|
||||
(ebox-grid--normalize-tracks '((repeat 2 (auto (fr 1)))) 'columns)
|
||||
'((:kind auto :factor 0) (:kind fr :factor 1)
|
||||
(:kind auto :factor 0) (:kind fr :factor 1)))))
|
||||
|
||||
(ert-deftest ebox-grid-repeated-render-reuses-construction-time-config ()
|
||||
"Grid render and resize paths should not reparse canonical track config."
|
||||
(let* ((node (ebox-test-grid :width '(120)
|
||||
:grid-template-columns '(1fr 2fr)
|
||||
:grid-template-columns '((fr 1) (fr 2))
|
||||
(ebox-test-box (ebox-test-text "A"))
|
||||
(ebox-test-box (ebox-test-text "B"))))
|
||||
(calls 0)
|
||||
@ -102,8 +129,8 @@
|
||||
:width '(120)
|
||||
:grid-template-columns '((120))
|
||||
(ebox-test-row
|
||||
(ebox-test-box (ebox-test-text "Left") :border t)
|
||||
(ebox-test-box (ebox-test-text "Right") :border t))))
|
||||
(ebox-test-box (ebox-test-text "Left") :border '(1 solid))
|
||||
(ebox-test-box (ebox-test-text "Right") :border '(1 solid)))))
|
||||
(widths (mapcar #'ebox--string-pixel-width
|
||||
(ebox-string-lines (ebox-render node)))))
|
||||
(should (equal widths '(120)))))
|
||||
@ -148,7 +175,7 @@
|
||||
"Grid wrapper borders should stay on one right edge across child lines."
|
||||
(let* ((node (ebox-test-grid
|
||||
:width '(120)
|
||||
:grid-template-columns '((30) auto 1fr)
|
||||
:grid-template-columns '((30) auto (fr 1))
|
||||
:grid-template-rows '(2)
|
||||
:gap '(1 (4))
|
||||
:justify-content 'space-between
|
||||
@ -165,7 +192,7 @@
|
||||
(let* ((header (ebox-test-box (ebox-test-text "Header") :width '(720)))
|
||||
(grid (ebox-test-grid
|
||||
:width '(718)
|
||||
:grid-template-columns '((220) 1fr)
|
||||
:grid-template-columns '((220) (fr 1))
|
||||
:gap '(1 (12))
|
||||
:border "#C97252"
|
||||
(ebox-test-box (ebox-test-text "Fixed") :width '(196)
|
||||
|
||||
@ -129,7 +129,7 @@
|
||||
(should (commandp 'ebox-byte-compile))
|
||||
(should
|
||||
(equal ebox--compile-sources
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-style.el"
|
||||
'("ebox-cache.el" "ebox-source.el" "ebox-font.el" "ebox-style.el"
|
||||
"ebox-layout-config.el"
|
||||
"ebox-node-factory.el"
|
||||
"ebox-child-range.el"
|
||||
|
||||
@ -177,7 +177,11 @@
|
||||
(should pixelwise)
|
||||
(if (eq window 'current-frame-window) 901 333)))
|
||||
((symbol-function 'window-body-height)
|
||||
(lambda (_window &optional _pixelwise) 31)))
|
||||
(lambda (_window &optional _pixelwise) 31))
|
||||
((symbol-function 'ebox--display-signature-for-window)
|
||||
(lambda (window)
|
||||
(should (eq window 'current-frame-window))
|
||||
'current-frame-capability)))
|
||||
(let ((values (ebox-surface--context-values buffer nil nil)))
|
||||
(should (= 899 (plist-get values :viewport-width)))
|
||||
(should (= 31 (plist-get values :viewport-height)))))
|
||||
@ -366,7 +370,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
(cons 'grid
|
||||
(lambda ()
|
||||
(ebox-test-grid
|
||||
:width '(220) :grid-template-columns '((70) 1fr)
|
||||
:width '(220) :grid-template-columns '((70) (fr 1))
|
||||
:grid-template-rows '(2) :gap '(1 (8))
|
||||
:border "#475569"
|
||||
(ebox-test-box :key 'grid-left (ebox-test-text "A\nAA")
|
||||
@ -602,9 +606,10 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
(input
|
||||
(ebox-test-box
|
||||
:width 20
|
||||
(ebox-test-text
|
||||
"native" :color 'ebox/default-foreground)))
|
||||
(ebox-test-text "native")))
|
||||
(node (ebox-test-root input))
|
||||
(text-node (car (ebox-box-node-children node)))
|
||||
(_ (plist-put text-node :color 'ebox/default-foreground))
|
||||
(package (ebox-native-reflow--compile-layout-package node))
|
||||
(document (plist-get package :document))
|
||||
(styles (plist-get document :styles))
|
||||
@ -820,7 +825,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
(ebox-surface-test--render-fresh
|
||||
(lambda ()
|
||||
(ebox-test-box
|
||||
:font-height 1.25
|
||||
:font-size 16
|
||||
(ebox-test-box (ebox-test-text "Inherited"))))
|
||||
t))
|
||||
(should (> calls 0))))
|
||||
@ -1253,7 +1258,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
collect
|
||||
(ebox-test-flex-item
|
||||
(ebox-test-box
|
||||
:key (list 'viewport-reflow-item index)
|
||||
:key (format "viewport-reflow-item-%d" index)
|
||||
(ebox-test-text (format "item-%02d" index))
|
||||
:width '(70)
|
||||
:padding '(0 1)
|
||||
@ -1671,7 +1676,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
(ebox-render-to-buffer
|
||||
buffer
|
||||
(ebox-test-box
|
||||
:font-height 1.25
|
||||
:font-size 16
|
||||
:width '(viewport)
|
||||
(ebox-test-box (ebox-test-text "Inherited"))))
|
||||
(cl-letf (((symbol-function 'ebox-surface--ensure-node-tree)
|
||||
@ -2014,8 +2019,8 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
ebox-surface--context-signals))
|
||||
(revision (tp-surface-revision surface))
|
||||
(next-signature '(ebox-test-theme dark)))
|
||||
(cl-letf (((symbol-function 'ebox--display-signature)
|
||||
(lambda () next-signature)))
|
||||
(cl-letf (((symbol-function 'ebox--display-signature-for-window)
|
||||
(lambda (_window) next-signature)))
|
||||
(ebox-rerender-buffer-with-context buffer 120 4))
|
||||
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||
(should (equal
|
||||
@ -2034,10 +2039,12 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
|
||||
(ert-deftest ebox-inline-style-reuse-projects-onto-fresh-text-node ()
|
||||
"A reused computed style must still materialize a fresh Text projection."
|
||||
(let* ((builder (ebox-source-builder-create))
|
||||
(let* ((foreground "#0F172A")
|
||||
(background "#F8FAFC")
|
||||
(builder (ebox-source-builder-create))
|
||||
(parent-declarations
|
||||
'(ebox/color foreground-slot
|
||||
ebox/background-color background-slot))
|
||||
(list 'ebox/color foreground
|
||||
'ebox/background-color background))
|
||||
(parent-handle
|
||||
(ebox-source-builder-bind
|
||||
builder :identity 'parent :declarations parent-declarations))
|
||||
@ -2098,12 +2105,12 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
||||
(ebox-surface-prepare-inline-candidate-styles
|
||||
old-state candidate '(1 2))
|
||||
(should (eq child-style (plist-get new-child :ebox-computed-style)))
|
||||
(should (eq 'foreground-slot (plist-get new-child :color)))
|
||||
(should (equal foreground (plist-get new-child :color)))
|
||||
(should (equal "New" (ebox-text-node-value new-child)))
|
||||
(should (eq child-handle (ebox-node-source-handle new-child)))
|
||||
(dolist (property ebox-tree--participation-keys)
|
||||
(should-not (plist-member new-child property)))
|
||||
(should (eq 'background-slot (plist-get parent :bgcolor)))))
|
||||
(should (equal background (plist-get parent :bgcolor)))))
|
||||
|
||||
(ert-deftest ebox-style-theme-delta-reuses-cascade-facts ()
|
||||
"Copy a paint-only Theme delta without rerunning ECSS cascade computation."
|
||||
@ -2177,7 +2184,7 @@ changes. Geometry and non-inherited computed values must remain identical."
|
||||
(ecss-computed-style-values delta)))
|
||||
(let ((parent-font-new
|
||||
(ebox-style-compute-subject
|
||||
parent '(ebox/color "#222222" ebox/font-height 2.0))))
|
||||
parent '(ebox/color "#222222" ebox/font-size 20))))
|
||||
(should-not
|
||||
(ebox-style--theme-parent-delta-computed
|
||||
style declarations parent-old parent-font-new)))))
|
||||
@ -2194,16 +2201,16 @@ changes. Geometry and non-inherited computed values must remain identical."
|
||||
(should-not (memq :ebox-content-width-exact-p
|
||||
(ebox-tree-node-local-changed-keys old new)))))
|
||||
|
||||
(ert-deftest ebox-tree-grid-source-signature-canonicalizes-layout-aliases ()
|
||||
"Equivalent Grid gap/paint aliases must not become geometry dirtiness."
|
||||
(ert-deftest ebox-tree-grid-source-signature-canonicalizes-gap-shorthand ()
|
||||
"Equivalent Grid gap forms must not become geometry dirtiness."
|
||||
(let ((old (list :ebox-type 'grid
|
||||
:raw-props '(:width stretch
|
||||
:grid-template-columns (1fr 1fr)
|
||||
:grid-row-gap 1 :grid-column-gap (12)
|
||||
:grid-template-columns ((fr 1) (fr 1))
|
||||
:row-gap 1 :column-gap (12)
|
||||
:color "#252A2E" :background-color "#F8F5EE")))
|
||||
(new (list :ebox-type 'grid
|
||||
:raw-props '(:width stretch
|
||||
:grid-template-columns (1fr 1fr)
|
||||
:grid-template-columns ((fr 1) (fr 1))
|
||||
:gap (1 (12))
|
||||
:color "#F2EEE4" :bgcolor "#1B1F20"))))
|
||||
(should-not (memq :props (ebox-tree-node-local-changed-keys old new)))
|
||||
@ -2218,10 +2225,10 @@ changes. Geometry and non-inherited computed values must remain identical."
|
||||
:padding-inline-end 2
|
||||
:padding-block-end 0
|
||||
:padding-inline-start 2
|
||||
:border-top-width (1)
|
||||
:border-right-width (1)
|
||||
:border-bottom-width (1)
|
||||
:border-left-width (1)
|
||||
:border-top-width 1
|
||||
:border-right-width 1
|
||||
:border-bottom-width 1
|
||||
:border-left-width 1
|
||||
:border-top-style solid
|
||||
:border-right-style solid
|
||||
:border-bottom-style solid
|
||||
@ -2237,7 +2244,7 @@ changes. Geometry and non-inherited computed values must remain identical."
|
||||
:raw-props '(:width stretch
|
||||
:gap (1 (12))
|
||||
:padding (0 2)
|
||||
:border ((1) solid "#687386")
|
||||
:border (1 solid "#687386")
|
||||
:align-items center
|
||||
:color "#F2EEE4"
|
||||
:bgcolor "#1B1F20"))))
|
||||
@ -2250,10 +2257,10 @@ changes. Geometry and non-inherited computed values must remain identical."
|
||||
(child (ecss-subject-create :type "box" :parent parent))
|
||||
(parent-old
|
||||
(ebox-style-compute-subject
|
||||
parent '(ebox/font-height 1.0)))
|
||||
parent '(ebox/font-size 16)))
|
||||
(parent-new
|
||||
(ebox-style-compute-subject
|
||||
parent '(ebox/font-height 2.0)))
|
||||
parent '(ebox/font-size 20)))
|
||||
(declarations '(ebox/color "#ffffff"))
|
||||
(style
|
||||
(ebox-style-compute-subject child declarations parent-old)))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user