Skip to content

Commit

Permalink
feat: Support extended CSS Page float values such as float: top right
Browse files Browse the repository at this point in the history
- Supports the new float property syntax proposal w3c/csswg-drafts#1251
- Improves fit-content inline size determination for inline-start/end or two sides (e.g. top right) page floats.
- Resolves #543
  • Loading branch information
MurakamiShinyu committed Jan 17, 2025
1 parent f9751ba commit 09a31fc
Show file tree
Hide file tree
Showing 20 changed files with 2,205 additions and 59 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/vivliostyle/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ crop-offset = auto | LENGTH;
/* CSS Page Floats */
float-reference = inline | column | region | page;
float = block-start | block-end | inline-start | inline-end | snap-block | snap-inline | left | right | top | bottom | none | footnote;
float = none | footnote | [ block-start || block-end || inline-start || inline-end || snap-block || snap-inline || left || right || top || bottom ];
float-min-wrap-block = PPLENGTH;
/* CSS Ruby */
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/vivliostyle/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ export const ident: { [key: string]: Ident } = {
same: getName("same"),
scale: getName("scale"),
snap_block: getName("snap-block"),
snap_inline: getName("snap-inline"),
solid: getName("solid"),
spread: getName("spread"),
_static: getName("static"),
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/vivliostyle/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ export function isRunning(position: Css.Val): boolean {
export function getComputedDisplayValue(
display: Css.Ident,
position: Css.Ident,
float: Css.Ident,
float: Css.Val,
isRoot: boolean,
): { display: Css.Ident; position: Css.Ident; float: Css.Ident } {
): { display: Css.Ident; position: Css.Ident; float: Css.Val } {
if (display === Css.ident.none) {
// no need to convert values when 'display' is 'none'
} else if (isAbsolutelyPositioned(position)) {
Expand All @@ -107,7 +107,7 @@ export function getComputedDisplayValue(
export function isBlock(
display: Css.Ident,
position: Css.Ident,
float: Css.Ident,
float: Css.Val,
isRoot: boolean,
): boolean {
return (
Expand Down Expand Up @@ -149,7 +149,7 @@ export function isRubyInternalDisplay(display: Css.Ident | string): boolean {
export function establishesBFC(
display: Css.Ident,
position: Css.Ident,
float: Css.Ident,
float: Css.Val,
overflow: Css.Ident,
writingMode?: Css.Ident,
parentWritingMode?: Css.Ident,
Expand Down
190 changes: 143 additions & 47 deletions packages/core/src/vivliostyle/page-floats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,14 +805,65 @@ export class PageFloatLayoutContext
return CssLogicalUtil.toPhysical(side, writingMode, direction);
}

private toLogicalFloatSides(floatSide: string): string[] {
const sides = floatSide.split(" ");

// Convert to logical sides and remove duplicates
const logicalSides: string[] = [];
for (const side of sides) {
const logicalSide = this.toLogical(side);
if (!logicalSides.includes(logicalSide)) {
logicalSides.push(logicalSide);
}
}

// Convert "block-start block-end" to "snap-block" and
// "inline-start inline-end" to "snap-inline".
// More precisely, when multiple "*block*" values are found
// convert the first one to "snap-block" and remove the rest,
// and when multiple "*inline*" values are found convert the
// first one to "snap-inline" and remove the rest.
const logicalFloatSides: string[] = [];
let foundSnapBlock = false;
let foundSnapInline = false;
for (let i = 0; i < logicalSides.length; i++) {
const side = logicalSides[i];
if (side.includes("block")) {
if (!foundSnapBlock) {
// Convert to "snap-block" if another block side is found
if (logicalSides.slice(i + 1).some((s) => s.includes("block"))) {
logicalFloatSides.push("snap-block");
foundSnapBlock = true;
} else {
logicalFloatSides.push(side);
}
}
} else if (side.includes("inline")) {
if (!foundSnapInline) {
// Convert to "snap-inline" if another inline side is found
if (logicalSides.slice(i + 1).some((s) => s.includes("inline"))) {
logicalFloatSides.push("snap-inline");
foundSnapInline = true;
} else {
logicalFloatSides.push(side);
}
}
}
}

return logicalFloatSides;
}

removeEndFloatFragments(floatSide: string) {
const logicalFloatSide = this.toLogical(floatSide);
const logicalFloatSide = this.toLogicalFloatSides(floatSide)[0];
if (logicalFloatSide === "block-end" || logicalFloatSide === "inline-end") {
let i = 0;
while (i < this.floatFragments.length) {
const fragment = this.floatFragments[i];
const logicalFloatSide2 = this.toLogical(fragment.floatSide);
if (logicalFloatSide2 === logicalFloatSide) {
const fragmentFloatSide = this.toLogicalFloatSides(
fragment.floatSide,
)[0];
if (fragmentFloatSide === logicalFloatSide) {
this.removePageFloatFragment(fragment);
} else {
i++;
Expand All @@ -827,20 +878,25 @@ export class PageFloatLayoutContext
this.getParent(floatReference).stashEndFloatFragments(float);
return;
}
const logicalFloatSide = this.toLogical(float.floatSide);
const logicalFloatSide = this.toLogicalFloatSides(float.floatSide)[0];
if (
logicalFloatSide === "block-end" ||
logicalFloatSide === "snap-block" ||
logicalFloatSide === "inline-end"
logicalFloatSide === "inline-end" ||
logicalFloatSide === "snap-inline"
) {
let i = 0;
while (i < this.floatFragments.length) {
const fragment = this.floatFragments[i];
const fragmentFloatSide = this.toLogical(fragment.floatSide);
const fragmentFloatSide = this.toLogicalFloatSides(
fragment.floatSide,
)[0];
if (
(fragmentFloatSide === logicalFloatSide ||
(logicalFloatSide === "snap-block" &&
fragmentFloatSide === "block-end")) &&
fragmentFloatSide === "block-end") ||
(logicalFloatSide === "snap-inline" &&
fragmentFloatSide === "inline-end")) &&
fragment.shouldBeStashedBefore(float)
) {
this.stashedFloatFragments.push(fragment);
Expand Down Expand Up @@ -999,7 +1055,7 @@ export class PageFloatLayoutContext
if (condition && !condition(f, this)) {
return l;
}
const logicalFloatSide = this.toLogical(f.floatSide);
const logicalFloatSide = this.toLogicalFloatSides(f.floatSide)[0];
const area = f.area;
const floatMinWrapBlock = f.continuations[0].float.floatMinWrapBlock;
let top = l.top;
Expand Down Expand Up @@ -1112,13 +1168,17 @@ export class PageFloatLayoutContext
condition,
);
}
let logicalFloatSide = this.toLogical(floatSide);
if (logicalFloatSide === "snap-block") {
const logicalFloatSides = this.toLogicalFloatSides(floatSide);
if (logicalFloatSides[0] === "snap-block") {
if (!condition["block-start"] && !condition["block-end"]) {
return null;
}
} else if (logicalFloatSides[0] === "snap-inline") {
if (!condition["inline-start"] && !condition["inline-end"]) {
return null;
}
} else {
if (!condition[logicalFloatSide]) {
if (!condition[logicalFloatSides[0]]) {
return null;
}
}
Expand Down Expand Up @@ -1194,9 +1254,10 @@ export class PageFloatLayoutContext
)
: new GeometryUtil.Rect(inlineStart, blockStart, inlineEnd, blockEnd);
if (
logicalFloatSide === "block-start" ||
logicalFloatSide === "snap-block" ||
logicalFloatSide === "inline-start"
logicalFloatSides[0] === "block-start" ||
logicalFloatSides[0] === "snap-block" ||
logicalFloatSides[0] === "inline-start" ||
logicalFloatSides[0] === "snap-inline"
) {
if (
!limitBlockStartEndValueWithOpenRect(
Expand All @@ -1208,9 +1269,10 @@ export class PageFloatLayoutContext
}
}
if (
logicalFloatSide === "block-end" ||
logicalFloatSide === "snap-block" ||
logicalFloatSide === "inline-end"
logicalFloatSides[0] === "block-end" ||
logicalFloatSides[0] === "snap-block" ||
logicalFloatSides[0] === "inline-end" ||
logicalFloatSides[0] === "snap-inline"
) {
if (
!limitBlockStartEndValueWithOpenRect(
Expand All @@ -1232,10 +1294,10 @@ export class PageFloatLayoutContext
blockSize = area.computedBlockSize;
outerBlockSize = blockSize + area.getInsetBefore() + area.getInsetAfter();
const availableBlockSize = (blockEnd - blockStart) * area.getBoxDir();
if (logicalFloatSide === "snap-block") {
if (logicalFloatSides[0] === "snap-block") {
if (anchorEdge === null) {
// Deferred from previous container
logicalFloatSide = "block-start";
logicalFloatSides[0] = "block-start";
} else {
const containerRect = this.container.getPaddingRect();
const fromStart =
Expand All @@ -1248,14 +1310,28 @@ export class PageFloatLayoutContext
anchorEdge -
outerBlockSize);
if (fromStart <= fromEnd) {
logicalFloatSide = "block-start";
logicalFloatSides[0] = "block-start";
} else {
logicalFloatSide = "block-end";
logicalFloatSides[0] = "block-end";
}
}
if (!condition[logicalFloatSide]) {
if (!condition[logicalFloatSides[0]]) {
if (condition["block-end"]) {
logicalFloatSide = "block-end";
logicalFloatSides[0] = "block-end";
} else {
return null;
}
}
} else if (logicalFloatSides[0] === "snap-inline") {
if (anchorEdge === null) {
// Deferred from previous container
logicalFloatSides[0] = "inline-start";
} else {
// FIXME: snap-inline should be resolved based on the anchor's inline position
if (condition["inline-start"]) {
logicalFloatSides[0] = "inline-start";
} else if (condition["inline-end"]) {
logicalFloatSides[0] = "inline-end";
} else {
return null;
}
Expand All @@ -1265,9 +1341,12 @@ export class PageFloatLayoutContext
return null;
}
if (
logicalFloatSide === "inline-start" ||
logicalFloatSide === "inline-end"
logicalFloatSides[0] === "inline-start" ||
logicalFloatSides[0] === "inline-end" ||
logicalFloatSides[1]
) {
// If the page float is "inline-start" or "inline-end" or has two sides
// (e.g. "block-start inline-end"), the inline size is determined by the content size.
inlineSize = Sizing.getSize(area.clientLayout, area.element, [
Sizing.Size.FIT_CONTENT_INLINE_SIZE,
])[Sizing.Size.FIT_CONTENT_INLINE_SIZE];
Expand All @@ -1287,28 +1366,45 @@ export class PageFloatLayoutContext
blockEnd -= blockOffset;
inlineStart -= inlineOffset;
inlineEnd -= inlineOffset;
switch (logicalFloatSide) {
case "inline-start":
case "block-start":
case "snap-block":
area.setInlinePosition(inlineStart, inlineSize);
area.setBlockPosition(blockStart, blockSize);
break;
case "inline-end":
case "block-end":
area.setInlinePosition(
inlineEnd - outerInlineSize * area.getInlineDir(),
inlineSize,
);
area.setBlockPosition(
blockEnd - outerBlockSize * area.getBoxDir(),
blockSize,
);
break;
default:
throw new Error(`unknown float direction: ${floatSide}`);

if (
logicalFloatSides.some(
(s) => s === "inline-start" || s === "snap-inline",
) ||
(logicalFloatSides.length === 1 &&
(logicalFloatSides[0] === "block-start" ||
logicalFloatSides[0] === "snap-block"))
) {
area.setInlinePosition(inlineStart, inlineSize);
} else if (
logicalFloatSides.some((s) => s === "inline-end") ||
(logicalFloatSides.length === 1 && logicalFloatSides[0] === "block-end")
) {
area.setInlinePosition(
inlineEnd - outerInlineSize * area.getInlineDir(),
inlineSize,
);
}
if (
logicalFloatSides.some(
(s) => s === "block-start" || s === "snap-block",
) ||
(logicalFloatSides.length === 1 &&
(logicalFloatSides[0] === "inline-start" ||
logicalFloatSides[0] === "snap-inline"))
) {
area.setBlockPosition(blockStart, blockSize);
} else if (
logicalFloatSides.some((s) => s === "block-end") ||
(logicalFloatSides.length === 1 && logicalFloatSides[0] === "inline-end")
) {
area.setBlockPosition(
blockEnd - outerBlockSize * area.getBoxDir(),
blockSize,
);
}
return logicalFloatSide;

return logicalFloatSides.join(" ");
}

getFloatFragmentExclusions(): GeometryUtil.Shape[] {
Expand Down Expand Up @@ -1428,7 +1524,7 @@ export class PageFloatLayoutContext
if (!clearSide) {
return result;
}
const logicalFloatSide = this.toLogical(floatSide);
const logicalFloatSide = this.toLogicalFloatSides(floatSide)[0];
const logicalClearSide = this.toLogical(clearSide);
let logicalSides: string[];
if (logicalClearSide === "all") {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/vivliostyle/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export type ResolveFormattingContextHook = (
p2: boolean,
p3: Css.Ident,
p4: Css.Ident,
p5: Css.Ident,
p5: Css.Val,
p6: boolean,
) => Vtree.FormattingContext;

Expand Down
27 changes: 26 additions & 1 deletion packages/core/src/vivliostyle/sizing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export function getSize(
height: (element as any).style.height as string,
maxHeight: (element as any).style.maxHeight as string,
minHeight: (element as any).style.minHeight as string,
top: (element as any).style.top as string,
right: (element as any).style.right as string,
bottom: (element as any).style.bottom as string,
left: (element as any).style.left as string,
};
const doc = element.ownerDocument;
const parent = element.parentNode;
Expand Down Expand Up @@ -92,9 +96,30 @@ export function getSize(
const blockSizeName = isVertical ? "width" : "height";

function getFillAvailableInline(): string {
Base.setCSSProperty(container, "width", original.width);
Base.setCSSProperty(container, "max-width", original.maxWidth);
Base.setCSSProperty(container, "min-width", original.minWidth);
Base.setCSSProperty(container, "height", original.height);
Base.setCSSProperty(container, "max-height", original.maxHeight);
Base.setCSSProperty(container, "min-height", original.minHeight);
Base.setCSSProperty(container, "top", original.top);
Base.setCSSProperty(container, "right", original.right);
Base.setCSSProperty(container, "bottom", original.bottom);
Base.setCSSProperty(container, "left", original.left);
Base.setCSSProperty(element, "display", "block");
Base.setCSSProperty(element, "position", "static");
return getComputedValue(inlineSizeName);
const r = getComputedValue(inlineSizeName);
Base.setCSSProperty(container, "width", "");
Base.setCSSProperty(container, "max-width", "");
Base.setCSSProperty(container, "min-width", "");
Base.setCSSProperty(container, "height", "");
Base.setCSSProperty(container, "max-height", "");
Base.setCSSProperty(container, "min-height", "");
Base.setCSSProperty(container, "top", "");
Base.setCSSProperty(container, "right", "");
Base.setCSSProperty(container, "bottom", "");
Base.setCSSProperty(container, "left", "");
return r;
}

// Inline size of an inline-block element is the fit-content
Expand Down
Loading

0 comments on commit 09a31fc

Please sign in to comment.