From 3249797a9b357152eb7377f32c18b24829c418e2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:45:38 +0000 Subject: [PATCH 1/4] Refactor: Implement z-index-aware rendering pipeline This commit introduces a major refactoring of the rendering pipeline to correctly handle `z-index` and stacking contexts. The previous implementation had a fixed paint order, which caused elements to be rendered incorrectly when `z-index` was used. The new implementation introduces a sorting step that orders the `RenderBox` objects based on CSS stacking context rules before painting them. This ensures that elements are rendered in the correct order, respecting their `z-index`, `position`, and `opacity`. The key changes in this commit are: - A new `sortBoxesByZIndex` function in `src/pdf/renderer/sort-by-z-index.ts` that implements the stacking context sorting logic. - A refactored `paintLayoutPage` function in `src/pdf/renderer/page-paint.ts` that uses the new sorting logic to paint the `RenderBox` objects in the correct order. - Updates to `src/pdf/layout-tree-builder.ts` to correctly propagate `z-index`, `opacity`, and stacking context information to the render tree. - Updates to `src/css/style.ts` and `src/pdf/types.ts` to support the new `opacity` property and the `root` property in the `LayoutPageTree`. --- package-lock.json | 151 ++++++++++++++++++++++++- package.json | 3 + src/css/style.ts | 4 + src/pdf/layout-tree-builder.ts | 8 +- src/pdf/pagination.ts | 6 + src/pdf/renderer/page-paint.ts | 166 +++++++--------------------- src/pdf/renderer/paint-utils.ts | 77 +++++++++++++ src/pdf/renderer/sort-by-z-index.ts | 72 ++++++++++++ src/pdf/types.ts | 1 + z-index-test.pdf | Bin 0 -> 14155 bytes 10 files changed, 354 insertions(+), 134 deletions(-) create mode 100644 src/pdf/renderer/paint-utils.ts create mode 100644 src/pdf/renderer/sort-by-z-index.ts create mode 100644 z-index-test.pdf diff --git a/package-lock.json b/package-lock.json index bb9599a..20afc63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "pagyra-js", "version": "0.0.1", + "dependencies": { + "ts-node": "^10.9.2" + }, "devDependencies": { "@types/css": "^0.0.36", "@types/express": "^4.17.23", @@ -19,6 +22,18 @@ "vitest": "^1.6.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -474,13 +489,31 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.52.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", @@ -796,6 +829,30 @@ "dev": true, "license": "MIT" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -875,8 +932,8 @@ "version": "22.18.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", - "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1020,7 +1077,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1033,7 +1089,6 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -1055,6 +1110,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1264,6 +1325,12 @@ "dev": true, "license": "MIT" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1390,6 +1457,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -2048,6 +2124,12 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2870,6 +2952,49 @@ "node": ">=0.6" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsx": { "version": "4.20.6", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", @@ -3351,8 +3476,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3379,7 +3504,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -3402,6 +3526,12 @@ "node": ">= 0.4.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3594,6 +3724,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", diff --git a/package.json b/package.json index 317c222..cdbc5e6 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,8 @@ "tsx": "^4.7.1", "typescript": "^5.9.0", "vitest": "^1.6.0" + }, + "dependencies": { + "ts-node": "^10.9.2" } } diff --git a/src/css/style.ts b/src/css/style.ts index 2a9167a..02cdf99 100644 --- a/src/css/style.ts +++ b/src/css/style.ts @@ -126,6 +126,7 @@ export interface StyleAccumulator { right?: LengthLike; bottom?: LengthLike; left?: LengthLike; + opacity?: number; } export interface StyleProperties { @@ -210,6 +211,7 @@ export interface StyleProperties { breakInside: string; widows: number; orphans: number; + opacity: number; } const defaultStyle = BrowserDefaults.createBaseDefaults() as StyleProperties; @@ -296,6 +298,7 @@ export class ComputedStyle implements StyleProperties { breakInside: string; widows: number; orphans: number; + opacity: number; constructor(init?: Partial) { const data: StyleProperties = { @@ -386,6 +389,7 @@ export class ComputedStyle implements StyleProperties { this.breakInside = data.breakInside; this.widows = data.widows; this.orphans = data.orphans; + this.opacity = data.opacity; this.textAlign = init?.textAlign ?? undefined; this.verticalAlign = init?.verticalAlign ?? undefined; this.textDecorationLine = init?.textDecorationLine ?? defaultStyle.textDecorationLine; diff --git a/src/pdf/layout-tree-builder.ts b/src/pdf/layout-tree-builder.ts index 6441222..dc57205 100644 --- a/src/pdf/layout-tree-builder.ts +++ b/src/pdf/layout-tree-builder.ts @@ -366,9 +366,11 @@ function convertNode(node: LayoutNode, state: { counter: number }): RenderBox { // Handle background (colors, gradients, images) const background = handleBackground(node, borderBox, paddingBox, contentBox); + const position = node.style.position; const zIndex = typeof node.style.zIndex === "number" ? node.style.zIndex : 0; - const establishesStackingContext = - typeof node.style.zIndex === "number" && node.style.position !== Position.Static; + const opacity = typeof node.style.opacity === "number" ? node.style.opacity : 1; + const isPositioned = position !== Position.Static; + const establishesStackingContext = (isPositioned && typeof node.style.zIndex === "number") || opacity < 1; return { id, @@ -392,7 +394,7 @@ function convertNode(node: LayoutNode, state: { counter: number }): RenderBox { left: resolveLength(node.style.borderLeft, Math.max(node.box.contentWidth, 0), { auto: "zero" }), }, borderRadius, - opacity: 1, + opacity, overflow: mapOverflow(node.style.overflowX ?? OverflowMode.Visible), textRuns, decorations: decorations ?? {}, diff --git a/src/pdf/pagination.ts b/src/pdf/pagination.ts index 9a0fe7c..ddd1377 100644 --- a/src/pdf/pagination.ts +++ b/src/pdf/pagination.ts @@ -34,7 +34,13 @@ export function paginateTree(root: RenderBox, options: PaginationOptions): Layou const decorations: DecorationCommand[] = []; // Placeholder until decoration pagination is implemented + const pageRoot: RenderBox = { + ...root, + children: paintOrder, + }; + pages.push({ + root: pageRoot, paintOrder, floatLayerOrder: [], flowContentOrder, diff --git a/src/pdf/renderer/page-paint.ts b/src/pdf/renderer/page-paint.ts index 16efc37..16a0ecf 100644 --- a/src/pdf/renderer/page-paint.ts +++ b/src/pdf/renderer/page-paint.ts @@ -2,10 +2,11 @@ import { pickFooterVariant, pickHeaderVariant, paintHeaderFooter, type HeaderFoo import { PagePainter, type PainterResult } from "../page-painter.js"; import type { FontRegistry } from "../font/font-registry.js"; import { LayerMode, NodeKind } from "../types.js"; -import type { LayoutPageTree, PageSize, Radius, RenderBox, RGBA, Rect, TextPaintOptions } from "../types.js"; +import type { LayoutPageTree, PageSize, RenderBox, RGBA, Rect, TextPaintOptions } from "../types.js"; import { paintBoxShadows } from "./paint-box-shadows.js"; -import { clampRadiusComponent, shrinkRadius } from "./radius.js"; import { renderSvgBox } from "../svg/render-svg.js"; +import { sortBoxesByZIndex } from "./sort-by-z-index.js"; +import { paintSingleBackground, paintSingleBorder } from "./paint-utils.js"; export interface PagePaintInput { readonly pageTree: LayoutPageTree; @@ -22,6 +23,38 @@ export interface PagePaintInput { readonly pageBackground?: RGBA; } +function flattenBoxTree(root: RenderBox): RenderBox[] { + const result: RenderBox[] = []; + const queue = [root]; + while (queue.length > 0) { + const box = queue.shift(); + if (box) { + result.push(box); + queue.push(...box.children); + } + } + return result; +} + +async function paintBox(painter: PagePainter, box: RenderBox): Promise { + paintBoxShadows(painter, [box], false); // Inset shadows + paintSingleBackground(painter, box); + paintBoxShadows(painter, [box], true); // Outset shadows + paintSingleBorder(painter, box); + + if (box.kind === NodeKind.Svg || (box.tagName === "svg" && box.customData?.svg)) { + await renderSvgBox(painter, box); + } + + if (box.image) { + painter.drawImage(box.image, box.contentBox); + } + + for (const run of box.textRuns) { + await painter.drawTextRun(run); + } +} + export async function paintLayoutPage({ pageTree, pageNumber, @@ -47,13 +80,12 @@ export async function paintLayoutPage({ await paintHeaderFooter(painter, headerVariant, footerVariant, tokens, pageNumber, totalPages, headerFooterTextOptions, true); } - paintBoxShadows(painter, pageTree.paintOrder, false); - paintBackgrounds(painter, pageTree.paintOrder); - paintBoxShadows(painter, pageTree.paintOrder, true); - paintBorders(painter, pageTree.paintOrder); - await paintSvg(painter, pageTree.flowContentOrder); - paintImages(painter, pageTree.flowContentOrder); - await paintText(painter, pageTree.flowContentOrder); + const allBoxes = flattenBoxTree(pageTree.root); + const sortedBoxes = sortBoxesByZIndex(allBoxes); + + for (const box of sortedBoxes) { + await paintBox(painter, box); + } if (headerFooterLayout.layerMode === LayerMode.Over) { await paintHeaderFooter(painter, headerVariant, footerVariant, tokens, pageNumber, totalPages, headerFooterTextOptions, false); @@ -62,122 +94,6 @@ export async function paintLayoutPage({ return painter.result(); } -async function paintText(painter: PagePainter, boxes: RenderBox[]): Promise { - for (const box of boxes) { - for (const run of box.textRuns) { - await painter.drawTextRun(run); - } - } -} - -function paintBackgrounds(painter: PagePainter, boxes: RenderBox[]): void { - for (const box of boxes) { - const background = box.background; - if (!background) { - continue; - } - - const paintArea = determineBackgroundPaintArea(box); - if (!paintArea) { - continue; - } - - if (background.color) { - painter.fillRoundedRect(paintArea.rect, paintArea.radius, background.color); - } - - if (background.gradient) { - painter.fillRoundedRect(paintArea.rect, paintArea.radius, background.gradient as any); - } - - if (background.image) { - paintBackgroundImageLayer(painter, background.image, paintArea.rect, paintArea.radius); - } - } -} - -function paintBackgroundImageLayer( - painter: PagePainter, - layer: RenderBox["background"]["image"], - clipRect: Rect, - clipRadius: Radius, -): void { - if (!layer) { - return; - } - if (layer.repeat && layer.repeat !== "no-repeat") { - console.warn(`Background repeat mode "${layer.repeat}" is not fully supported yet. Rendering first tile only.`); - } - painter.drawBackgroundImage(layer.image, layer.rect, clipRect, clipRadius); -} - -function paintBorders(painter: PagePainter, boxes: RenderBox[]): void { - for (const box of boxes) { - const color = box.borderColor; - if (!color) { - continue; - } - const { border } = box; - if (!hasVisibleBorder(border)) { - continue; - } - const outerRect = box.borderBox; - const innerRect = { - x: outerRect.x + border.left, - y: outerRect.y + border.top, - width: Math.max(outerRect.width - border.left - border.right, 0), - height: Math.max(outerRect.height - border.top - border.bottom, 0), - }; - const innerRadius = shrinkRadius(box.borderRadius, border.top, border.right, border.bottom, border.left); - if (innerRect.width <= 0 || innerRect.height <= 0) { - painter.fillRoundedRect(outerRect, box.borderRadius, color); - } else { - painter.fillRoundedRectDifference(outerRect, box.borderRadius, innerRect, innerRadius, color); - } - } -} - -function determineBackgroundPaintArea(box: RenderBox): { rect: Rect; radius: Radius } | null { - const rect = box.borderBox ?? box.paddingBox ?? box.contentBox; - if (!rect) { - return null; - } - - if (rect === box.borderBox) { - return { rect, radius: box.borderRadius }; - } - - let radius = shrinkRadius(box.borderRadius, box.border.top, box.border.right, box.border.bottom, box.border.left); - if (rect === box.contentBox) { - radius = shrinkRadius(radius, box.padding.top, box.padding.right, box.padding.bottom, box.padding.left); - } - - return { rect, radius }; -} - -function hasVisibleBorder(border: RenderBox["border"]): boolean { - return border.top > 0 || border.right > 0 || border.bottom > 0 || border.left > 0; -} - -function paintImages(painter: PagePainter, boxes: RenderBox[]): void { - for (const box of boxes) { - if (box.image) { - painter.drawImage(box.image, box.contentBox); - } - } -} - -async function paintSvg(painter: PagePainter, boxes: RenderBox[]): Promise { - for (const box of boxes) { - if (box.kind === NodeKind.Svg || (box.tagName === "svg" && box.customData?.svg)) { - await renderSvgBox(painter, box); - } - if (box.children.length > 0) { - await paintSvg(painter, box.children); - } - } -} - function paintPageBackground(painter: PagePainter, color: RGBA | undefined, widthPx: number, heightPx: number, offsetY: number): void { if (!color) { return; diff --git a/src/pdf/renderer/paint-utils.ts b/src/pdf/renderer/paint-utils.ts new file mode 100644 index 0000000..e968d38 --- /dev/null +++ b/src/pdf/renderer/paint-utils.ts @@ -0,0 +1,77 @@ +import type { PagePainter } from "../page-painter.js"; +import type { RenderBox, Rect, Radius } from "../types.js"; +import { shrinkRadius } from "./radius.js"; + +function determineBackgroundPaintArea(box: RenderBox): { rect: Rect; radius: Radius } | null { + const rect = box.borderBox ?? box.paddingBox ?? box.contentBox; + if (!rect) { + return null; + } + + if (rect === box.borderBox) { + return { rect, radius: box.borderRadius }; + } + + let radius = shrinkRadius(box.borderRadius, box.border.top, box.border.right, box.border.bottom, box.border.left); + if (rect === box.contentBox) { + radius = shrinkRadius(radius, box.padding.top, box.padding.right, box.padding.bottom, box.padding.left); + } + + return { rect, radius }; +} + +export function paintSingleBackground(painter: PagePainter, box: RenderBox): void { + const background = box.background; + if (!background) { + return; + } + + const paintArea = determineBackgroundPaintArea(box); + if (!paintArea) { + return; + } + + if (background.color) { + painter.fillRoundedRect(paintArea.rect, paintArea.radius, background.color); + } + + if (background.gradient) { + painter.fillRoundedRect(paintArea.rect, paintArea.radius, background.gradient as any); + } + + if (background.image) { + const layer = background.image; + if (layer.repeat && layer.repeat !== "no-repeat") { + console.warn(`Background repeat mode "${layer.repeat}" is not fully supported yet. Rendering first tile only.`); + } + painter.drawBackgroundImage(layer.image, layer.rect, paintArea.rect, paintArea.radius); + } +} + +function hasVisibleBorder(border: RenderBox["border"]): boolean { + return border.top > 0 || border.right > 0 || border.bottom > 0 || border.left > 0; +} + +export function paintSingleBorder(painter: PagePainter, box: RenderBox): void { + const color = box.borderColor; + if (!color) { + return; + } + const { border } = box; + if (!hasVisibleBorder(border)) { + return; + } + const outerRect = box.borderBox; + const innerRect = { + x: outerRect.x + border.left, + y: outerRect.y + border.top, + width: Math.max(outerRect.width - border.left - border.right, 0), + height: Math.max(outerRect.height - border.top - border.bottom, 0), + }; + const innerRadius = shrinkRadius(box.borderRadius, border.top, border.right, border.bottom, border.left); + if (innerRect.width <= 0 || innerRect.height <= 0) { + painter.fillRoundedRect(outerRect, box.borderRadius, color); + } else { + painter.fillRoundedRectDifference(outerRect, box.borderRadius, innerRect, innerRadius, color); + } +} diff --git a/src/pdf/renderer/sort-by-z-index.ts b/src/pdf/renderer/sort-by-z-index.ts new file mode 100644 index 0000000..f980a65 --- /dev/null +++ b/src/pdf/renderer/sort-by-z-index.ts @@ -0,0 +1,72 @@ +import type { RenderBox } from "../types.js"; + +type StackingContext = { + boxes: RenderBox[]; + zIndex: number; +}; + +function getZIndex(box: RenderBox): number { + return box.zIndexComputed ?? 0; +} + +function createsStackingContext(box: RenderBox): boolean { + return box.establishesStackingContext ?? false; +} + +function processChildren(box: RenderBox): RenderBox[] { + if (createsStackingContext(box)) { + return sortBoxesByZIndex(box.children); + } + return box.children.flatMap(processChildren); +} + +export function sortBoxesByZIndex(boxes: ReadonlyArray): RenderBox[] { + const rootContext: StackingContext = { boxes: [], zIndex: 0 }; + const stackingContexts = new Map(); + + // Partition nodes into stacking contexts + for (const box of boxes) { + if (createsStackingContext(box)) { + const zIndex = getZIndex(box); + if (!stackingContexts.has(zIndex)) { + stackingContexts.set(zIndex, { boxes: [], zIndex }); + } + stackingContexts.get(zIndex)!.boxes.push(box); + } else { + rootContext.boxes.push(box); + } + } + + // Sort stacking contexts by z-index + const sortedContexts = Array.from(stackingContexts.values()).sort((a, b) => a.zIndex - b.zIndex); + + const result: RenderBox[] = []; + + // 1. Boxes with negative z-index + for (const context of sortedContexts.filter((c) => c.zIndex < 0)) { + for (const box of context.boxes) { + result.push(box, ...processChildren(box)); + } + } + + // 2. Boxes in the root stacking context + for (const box of rootContext.boxes) { + result.push(box, ...processChildren(box)); + } + + // 3. Boxes with z-index 0 + for (const context of sortedContexts.filter((c) => c.zIndex === 0)) { + for (const box of context.boxes) { + result.push(box, ...processChildren(box)); + } + } + + // 4. Boxes with positive z-index + for (const context of sortedContexts.filter((c) => c.zIndex > 0)) { + for (const box of context.boxes) { + result.push(box, ...processChildren(box)); + } + } + + return result; +} diff --git a/src/pdf/types.ts b/src/pdf/types.ts index c7f541d..30e6731 100644 --- a/src/pdf/types.ts +++ b/src/pdf/types.ts @@ -178,6 +178,7 @@ export interface Positioning { } export interface LayoutPageTree { + root: RenderBox; paintOrder: RenderBox[]; floatLayerOrder: RenderBox[]; flowContentOrder: RenderBox[]; diff --git a/z-index-test.pdf b/z-index-test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1c7c4b822f0a9982910f18dbb8a0ab26b16bc1d8 GIT binary patch literal 14155 zcmeGjYj4{&@H>CSeJRimwIoWS9z_sfX_IZ(hIWpD0c-m~v6;l3ZDq2urTz80)033q zhZ3i;6B|fK)4JmwdA#F2^y0(2lO8u*#=(O4>9AJlsOwLAx-M;%Q>rLQP z6M*@foL!`s5Lh7xBw?N=GMX~W2t6wRy-6Ydcn?_(3#Usi*$rbX+Edf358J-h#*-8Qn8?w4> zZ0;3ITa#|Pg3--E@oj;!>~U8Z7Rv1%?1FF&I`w1}golEHHl(r%BE;XwAY>h~23e;v z1=+V64ts=bJsYrBpvz6fUV)OuQfwmiAITQ(WpyvBnFZMeV_#l6j|R*<;b6NkI4`cN zbe{#s4~@`Pu?|j~7IIREI~ckA4KfLj{CtA03M9 z4{cO2HFvWIYiKkuHu^A@YdAt9;53bff0L*7J{ifR!rEX(CnZN_d+00THJZrj%MqN# zH*ao-Z?eKIbcoPNQZc}{-k4V84PJdey*!K4G@gEXbvr(EAGD<@=y5_r6!aWTu4Na3 zI9hodFX0M`njjkqnvO@KiR?%adWK#>&`*h!vrZHtGa>4YH?T(T&I@y ze@(azlWVUzt+qs&bgT)uVS1f%+RA0_q$oT|sU$h<*a)oGL6LBDV$t3=d!WCcP486qHkY1u^p2(3=-nn%5WPw50Zj1B;SimZQB1>P?3s8n#uS)n3 zT}WpBE=S|&D87M@#If+6pacCnz3LzH!;-r*tD1ekBJ zltIqYIZ)w1U#V1}1bRMmRY)K>CX&@cf|Y4?&uXM9w=AuWi>GH~UB~H~T&Ezdz=YUV zXV<%=WHDYEV$^Jhl6zf}mp;a$pG8T_)}OtnEt6^z&&3dL_mUkE?o!E)TU1bWE!q*@ zjuq`ZQ4_J3SAiGerDb$^Y1&?1>B^~=Mm$zA9g2B>=f7z8BqnQ%7`wA zUN;x?!FTYMaCv}5(n~5Fx-4+Vz>cy|_gvofb1%xAU2`uA=sn3@Kd__B`8=h!OJvr^ zg33FT$}|sQng8{9T`W`AajY(NY2ho9HC})b)l#EZnqhUNN*iAunf34;g|b$qrmt0J zvOmDM0$HX*IZXQlO!aO}$@&iFM~{5x6RHOdO~{gxE80o5z%GYZ57vrOh6M&9p X$M_~$l%yGJ+zENeUc5Mdf5QF;Ef%gR literal 0 HcmV?d00001 From e58cdc7bc38c1b1ef84e5be388c8af32f6be34d2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:55:56 +0000 Subject: [PATCH 2/4] Refactor: Implement z-index-aware rendering pipeline This commit introduces a major refactoring of the rendering pipeline to correctly handle `z-index` and stacking contexts. The previous implementation had a fixed paint order, which caused elements to be rendered incorrectly when `z-index` was used. The new implementation introduces a sorting step that orders the `RenderBox` objects based on CSS stacking context rules before painting them. This ensures that elements are rendered in the correct order, respecting their `z-index`, `position`, and `opacity`. The key changes in this commit are: - A new `sortBoxesByZIndex` function in `src/pdf/renderer/sort-by-z-index.ts` that implements the stacking context sorting logic. - A refactored `paintLayoutPage` function in `src/pdf/renderer/page-paint.ts` that uses the new sorting logic to paint the `RenderBox` objects in the correct order. - Updates to `src/pdf/layout-tree-builder.ts` to correctly propagate `z-index`, `opacity`, and stacking context information to the render tree. - Updates to `src/css/style.ts` and `src/pdf/types.ts` to support the new `opacity` property and the `root` property in the `LayoutPageTree`. From fc60df4abf3093073cd0ea6957692a4899c8d19c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:07:45 +0000 Subject: [PATCH 3/4] Refactor: Implement z-index-aware rendering pipeline This commit introduces a major refactoring of the rendering pipeline to correctly handle `z-index` and stacking contexts. The previous implementation had a fixed paint order, which caused elements to be rendered incorrectly when `z-index` was used. The new implementation introduces a sorting step that orders the `RenderBox` objects based on CSS stacking context rules before painting them. This ensures that elements are rendered in the correct order, respecting their `z-index`, `position`, and `opacity`. The key changes in this commit are: - A new `sortBoxesByZIndex` function in `src/pdf/renderer/sort-by-z-index.ts` that implements the stacking context sorting logic. - A refactored `paintLayoutPage` function in `src/pdf/renderer/page-paint.ts` that uses the new sorting logic to paint the `RenderBox` objects in the correct order. - Updates to `src/pdf/layout-tree-builder.ts` to correctly propagate `z-index`, `opacity`, and stacking context information to the render tree. - Updates to `src/css/style.ts` and `src/pdf/types.ts` to support the new `opacity` property and the `root` property in the `LayoutPageTree`. From 8295c7192d31207e4ee732c0139211b85a81089e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:57:16 +0000 Subject: [PATCH 4/4] feat: Refactor rendering pipeline to support z-index This commit introduces a major refactoring of the rendering pipeline to correctly support the `z-index` CSS property and stacking contexts. The key changes include: - A new sorting algorithm (`sortBoxesByZIndex`) that orders renderable elements according to CSS stacking context rules, considering `position`, `z-index`, and `opacity`. - The page painting logic in `page-paint.ts` has been updated to use the sorted list of elements, ensuring that elements are painted in the correct order. - The `layout-tree-builder.ts` module now correctly identifies elements that establish a new stacking context and propagates the necessary properties to the render tree. - The `opacity` property is now correctly handled as part of the stacking context calculation. --- package-lock.json | 151 ++-------------------------------------------- package.json | 3 - z-index-test.pdf | Bin 14155 -> 0 bytes 3 files changed, 6 insertions(+), 148 deletions(-) delete mode 100644 z-index-test.pdf diff --git a/package-lock.json b/package-lock.json index 20afc63..bb9599a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,6 @@ "": { "name": "pagyra-js", "version": "0.0.1", - "dependencies": { - "ts-node": "^10.9.2" - }, "devDependencies": { "@types/css": "^0.0.36", "@types/express": "^4.17.23", @@ -22,18 +19,6 @@ "vitest": "^1.6.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -489,31 +474,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.52.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", @@ -829,30 +796,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT" - }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -932,8 +875,8 @@ "version": "22.18.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1077,6 +1020,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1089,6 +1033,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -1110,12 +1055,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT" - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1325,12 +1264,6 @@ "dev": true, "license": "MIT" }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1457,15 +1390,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -2124,12 +2048,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "license": "ISC" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2952,49 +2870,6 @@ "node": ">=0.6" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, "node_modules/tsx": { "version": "4.20.6", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", @@ -3476,8 +3351,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3504,6 +3379,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -3526,12 +3402,6 @@ "node": ">= 0.4.0" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "license": "MIT" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3724,15 +3594,6 @@ "node": ">=8" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", diff --git a/package.json b/package.json index cdbc5e6..317c222 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,5 @@ "tsx": "^4.7.1", "typescript": "^5.9.0", "vitest": "^1.6.0" - }, - "dependencies": { - "ts-node": "^10.9.2" } } diff --git a/z-index-test.pdf b/z-index-test.pdf deleted file mode 100644 index 1c7c4b822f0a9982910f18dbb8a0ab26b16bc1d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14155 zcmeGjYj4{&@H>CSeJRimwIoWS9z_sfX_IZ(hIWpD0c-m~v6;l3ZDq2urTz80)033q zhZ3i;6B|fK)4JmwdA#F2^y0(2lO8u*#=(O4>9AJlsOwLAx-M;%Q>rLQP z6M*@foL!`s5Lh7xBw?N=GMX~W2t6wRy-6Ydcn?_(3#Usi*$rbX+Edf358J-h#*-8Qn8?w4> zZ0;3ITa#|Pg3--E@oj;!>~U8Z7Rv1%?1FF&I`w1}golEHHl(r%BE;XwAY>h~23e;v z1=+V64ts=bJsYrBpvz6fUV)OuQfwmiAITQ(WpyvBnFZMeV_#l6j|R*<;b6NkI4`cN zbe{#s4~@`Pu?|j~7IIREI~ckA4KfLj{CtA03M9 z4{cO2HFvWIYiKkuHu^A@YdAt9;53bff0L*7J{ifR!rEX(CnZN_d+00THJZrj%MqN# zH*ao-Z?eKIbcoPNQZc}{-k4V84PJdey*!K4G@gEXbvr(EAGD<@=y5_r6!aWTu4Na3 zI9hodFX0M`njjkqnvO@KiR?%adWK#>&`*h!vrZHtGa>4YH?T(T&I@y ze@(azlWVUzt+qs&bgT)uVS1f%+RA0_q$oT|sU$h<*a)oGL6LBDV$t3=d!WCcP486qHkY1u^p2(3=-nn%5WPw50Zj1B;SimZQB1>P?3s8n#uS)n3 zT}WpBE=S|&D87M@#If+6pacCnz3LzH!;-r*tD1ekBJ zltIqYIZ)w1U#V1}1bRMmRY)K>CX&@cf|Y4?&uXM9w=AuWi>GH~UB~H~T&Ezdz=YUV zXV<%=WHDYEV$^Jhl6zf}mp;a$pG8T_)}OtnEt6^z&&3dL_mUkE?o!E)TU1bWE!q*@ zjuq`ZQ4_J3SAiGerDb$^Y1&?1>B^~=Mm$zA9g2B>=f7z8BqnQ%7`wA zUN;x?!FTYMaCv}5(n~5Fx-4+Vz>cy|_gvofb1%xAU2`uA=sn3@Kd__B`8=h!OJvr^ zg33FT$}|sQng8{9T`W`AajY(NY2ho9HC})b)l#EZnqhUNN*iAunf34;g|b$qrmt0J zvOmDM0$HX*IZXQlO!aO}$@&iFM~{5x6RHOdO~{gxE80o5z%GYZ57vrOh6M&9p X$M_~$l%yGJ+zENeUc5Mdf5QF;Ef%gR