img.has-text-siblings forced vertical-align: baseline on every inline image with text siblings, out-specifying a book's own value (for example a CJK glyph-substitution image nudged with vertical-align: -0.15em). Because baseline is the CSS initial value, the declaration only ever mattered when it clobbered an authored value. Keep baseline as a default only: move it to a new has-text-siblings-baseline class that applyImageStyle adds only when the image has no author-set vertical-align (detected via getComputedStyle). Refactor applyImageStyle to a two-phase read-then-write pass to avoid a getComputedStyle-after-write style recalc. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -472,6 +472,12 @@ const getPageLayoutStyles = (
|
||||
}
|
||||
img.has-text-siblings {
|
||||
${vertical ? 'width: 1em;' : 'height: 1em;'}
|
||||
}
|
||||
/* Baseline is only Readest's default: applyImageStyle adds this class when the
|
||||
book leaves vertical-align at its initial value, so an author-set value
|
||||
(e.g. a CJK glyph-substitution image nudged with vertical-align: -0.15em)
|
||||
keeps winning. See #4866. */
|
||||
img.has-text-siblings-baseline {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
:is(div) > img.has-text-siblings[style*="object-fit"] {
|
||||
@@ -1148,37 +1154,60 @@ export const applyScrollbarStyle = (document: Document, hideScrollbar: boolean)
|
||||
};
|
||||
|
||||
export const applyImageStyle = (document: Document) => {
|
||||
document.querySelectorAll('img').forEach((img) => {
|
||||
const win = document.defaultView ?? window;
|
||||
// Two-phase (read then write): reading getComputedStyle after a class/style
|
||||
// write forces a style recalc, so gather every decision first and apply the
|
||||
// mutations afterwards (same reasoning as keepTextAlignment's split).
|
||||
const plans = Array.from(document.querySelectorAll('img')).map((img) => {
|
||||
const widthAttr = img.getAttribute('width');
|
||||
if (widthAttr && (widthAttr.endsWith('%') || widthAttr.endsWith('vw'))) {
|
||||
const percentage = parseFloat(widthAttr);
|
||||
if (!isNaN(percentage)) {
|
||||
img.style.width = `${(percentage / 100) * window.innerWidth}px`;
|
||||
img.removeAttribute('width');
|
||||
}
|
||||
}
|
||||
|
||||
const percentWidth =
|
||||
widthAttr && (widthAttr.endsWith('%') || widthAttr.endsWith('vw'))
|
||||
? parseFloat(widthAttr)
|
||||
: NaN;
|
||||
const heightAttr = img.getAttribute('height');
|
||||
if (heightAttr && (heightAttr.endsWith('%') || heightAttr.endsWith('vh'))) {
|
||||
const percentage = parseFloat(heightAttr);
|
||||
if (!isNaN(percentage)) {
|
||||
img.style.height = `${(percentage / 100) * window.innerHeight}px`;
|
||||
img.removeAttribute('height');
|
||||
const percentHeight =
|
||||
heightAttr && (heightAttr.endsWith('%') || heightAttr.endsWith('vh'))
|
||||
? parseFloat(heightAttr)
|
||||
: NaN;
|
||||
|
||||
let inlineWithText = false;
|
||||
let keepBaseline = false;
|
||||
const parent = img.parentNode;
|
||||
if (parent && parent.nodeType === Node.ELEMENT_NODE) {
|
||||
const childNodes = Array.from(parent.childNodes);
|
||||
const hasTextSiblings = childNodes.some(
|
||||
(node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim(),
|
||||
);
|
||||
const isInline = childNodes.every(
|
||||
(node) => node.nodeType !== Node.ELEMENT_NODE || (node as Element).tagName !== 'BR',
|
||||
);
|
||||
inlineWithText = hasTextSiblings && isInline;
|
||||
if (inlineWithText) {
|
||||
// Only supply Readest's baseline default when the book leaves
|
||||
// vertical-align at its initial value; an author-set value (e.g. a CJK
|
||||
// glyph-substitution image nudged with `vertical-align: -0.15em`) must
|
||||
// win. Empty string covers environments that report unset props as ''.
|
||||
const valign = win.getComputedStyle(img).verticalAlign;
|
||||
keepBaseline = valign === '' || valign === 'baseline';
|
||||
}
|
||||
}
|
||||
|
||||
const parent = img.parentNode;
|
||||
if (!parent || parent.nodeType !== Node.ELEMENT_NODE) return;
|
||||
const hasTextSiblings = Array.from(parent.childNodes).some(
|
||||
(node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim(),
|
||||
);
|
||||
const isInline = Array.from(parent.childNodes).every(
|
||||
(node) => node.nodeType !== Node.ELEMENT_NODE || (node as Element).tagName !== 'BR',
|
||||
);
|
||||
if (hasTextSiblings && isInline) {
|
||||
img.classList.add('has-text-siblings');
|
||||
}
|
||||
return { img, percentWidth, percentHeight, inlineWithText, keepBaseline };
|
||||
});
|
||||
|
||||
for (const { img, percentWidth, percentHeight, inlineWithText, keepBaseline } of plans) {
|
||||
if (!isNaN(percentWidth)) {
|
||||
img.style.width = `${(percentWidth / 100) * window.innerWidth}px`;
|
||||
img.removeAttribute('width');
|
||||
}
|
||||
if (!isNaN(percentHeight)) {
|
||||
img.style.height = `${(percentHeight / 100) * window.innerHeight}px`;
|
||||
img.removeAttribute('height');
|
||||
}
|
||||
if (inlineWithText) {
|
||||
img.classList.add('has-text-siblings');
|
||||
if (keepBaseline) img.classList.add('has-text-siblings-baseline');
|
||||
}
|
||||
}
|
||||
document.querySelectorAll('hr').forEach((hr) => {
|
||||
const computedStyle = window.getComputedStyle(hr);
|
||||
if (computedStyle.backgroundImage && computedStyle.backgroundImage !== 'none') {
|
||||
|
||||
Reference in New Issue
Block a user