From 10cf68d7eca87f5749a5d8a3401ec48f04f20d8b Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Mon, 30 Jun 2025 09:35:36 -0700 Subject: [PATCH 01/11] WIP: prep for upstream sync --- .vscode/launch.json | 14 + package.json | 3 +- site/config.js | 5 +- site/index.html | 33 +- site/index.js | 48 +- specifications/layout-system.md | 271 +++ src/paint-canvas.ts | 261 ++- src/paint-html.ts | 159 +- src/paint-svg.ts | 115 +- src/paint.ts | 1110 +++++++++-- src/parse-css.js | 3125 +++++++++++++++++++------------ src/parse-css.pegjs | 87 +- src/style.ts | 903 +++++---- test/paint-spy.js | 68 +- tsconfig.json | 11 +- 15 files changed, 4308 insertions(+), 1905 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 specifications/layout-system.md diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a7ff1c6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "msedge", + "request": "launch", + "name": "Open index.html", + "file": "d:\\code\\dropflow\\site\\index.html" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 15e243c..dac6d6a 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,9 @@ "devDependencies": { "@codemirror/lang-html": "^6.4.8", "@codemirror/state": "^6.4.1", - "@ddietr/codemirror-themes": "^1.4.2", + "@ddietr/codemirror-themes": "^1.5.1", "@rollup/plugin-node-resolve": "^15.2.3", + "@types/codemirror": "^5.60.15", "@types/mocha": "^8.0.3", "@types/node": "^20.17.10", "canvas": "^3.1.0", diff --git a/site/config.js b/site/config.js index 1323853..655d0e2 100644 --- a/site/config.js +++ b/site/config.js @@ -2,12 +2,11 @@ import {environment} from 'dropflow/environment.js'; import wasmUrl from 'dropflow/dropflow.wasm?url'; environment.wasmLocator = function () { - return fetch(wasmUrl).then(res => { + return fetch(wasmUrl).then((res) => { if (res.status === 200) { - return res.arrayBuffer() + return res.arrayBuffer(); } else { throw new Error(res.statusText); } }); }; - diff --git a/site/index.html b/site/index.html index 5cec67b..7b32c5b 100644 --- a/site/index.html +++ b/site/index.html @@ -1,17 +1,34 @@ - + Dropflow Playground - -
-
-
-
- + +
+
+
+
+
-
+
<canvas>
diff --git a/site/index.js b/site/index.js index b79db56..5d70ccd 100644 --- a/site/index.js +++ b/site/index.js @@ -1,22 +1,22 @@ // @ts-check -import './config.js'; -import * as flow from 'dropflow'; -import parse from 'dropflow/parse.js'; -import registerNotoFonts from 'dropflow/register-noto-fonts.js'; -import {EditorView, basicSetup} from 'codemirror'; -import {EditorState} from '@codemirror/state'; -import {html} from '@codemirror/lang-html'; -import {solarizedDark} from '@ddietr/codemirror-themes/solarized-dark.js' - -const [canvas] = document.getElementsByTagName('canvas'); -const wrap = document.getElementById('wrap'); -const canvasLabel = document.getElementById('canvas-label'); - -flow.setOriginStyle({zoom: window.devicePixelRatio}); +import "./config.js"; +import * as flow from "dropflow"; +import parse from "dropflow/parse.js"; +import registerNotoFonts from "dropflow/register-noto-fonts.js"; +import { EditorView, basicSetup } from "codemirror"; +import { EditorState } from "@codemirror/state"; +import { html } from "@codemirror/lang-html"; +import { solarizedDark } from "@ddietr/codemirror-themes/theme/solarized-dark"; + +const [canvas] = document.getElementsByTagName("canvas"); +const wrap = document.getElementById("wrap"); +const canvasLabel = document.getElementById("canvas-label"); + +flow.setOriginStyle({ zoom: window.devicePixelRatio }); registerNotoFonts(); async function loadLayoutPaint() { - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext("2d"); const cssWidth = wrap.getBoundingClientRect().width; const cssHeight = wrap.getBoundingClientRect().height; const dpxWidth = Math.ceil(cssWidth * window.devicePixelRatio); @@ -28,17 +28,17 @@ async function loadLayoutPaint() { canvas.width = dpxWidth; canvas.height = dpxHeight; - const {r, g, b, a} = documentElement.style.backgroundColor; + const { r, g, b, a } = documentElement.style.backgroundColor; canvasLabel.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${a})`; ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height); flow.layout(blockContainer, canvas.width, canvas.height); - flow.paintToCanvas(blockContainer, canvas.getContext('2d')); + flow.paintToCanvas(blockContainer, canvas.getContext("2d")); ctx.restore(); } -const watch = EditorView.updateListener.of(update => { +const watch = EditorView.updateListener.of((update) => { if (update.docChanged) { parseGenerate(); loadLayoutPaint(); @@ -141,12 +141,12 @@ const state = EditorState.create({ this does interrupt shaping boundaries.
`, - extensions: [basicSetup, html(), watch, solarizedDark] + extensions: [basicSetup, html(), watch, solarizedDark], }); const view = new EditorView({ state, - parent: document.querySelector('#editor') + parent: document.querySelector("#editor"), }); let documentElement; @@ -168,10 +168,10 @@ const observer = new ResizeObserver(function () { let lastDevicePixelRatio = window.devicePixelRatio; -window.addEventListener('resize', function () { +window.addEventListener("resize", function () { if (window.devicePixelRatio !== lastDevicePixelRatio) { lastDevicePixelRatio = window.devicePixelRatio; - flow.setOriginStyle({zoom: window.devicePixelRatio}); + flow.setOriginStyle({ zoom: window.devicePixelRatio }); parseGenerate(); loadLayoutPaint(); } @@ -181,5 +181,5 @@ observer.observe(document.body); window.flow = flow; -view.dom.style.height = '100%'; -view.scrollDOM.style.overflow = 'auto'; +view.dom.style.height = "100%"; +view.scrollDOM.style.overflow = "auto"; diff --git a/specifications/layout-system.md b/specifications/layout-system.md new file mode 100644 index 0000000..9725e4e --- /dev/null +++ b/specifications/layout-system.md @@ -0,0 +1,271 @@ +# Dropflow Layout System Specification + +## Overview + +Dropflow is a layout engine that implements CSS-style box model layout and rendering. The system is designed to handle both block and inline layout, with support for text layout, floats, and positioning. + +## Core Components + +### 1. Box Model + +- `Box` class (layout-box.ts) - Base class for all layout boxes +- `BoxArea` class - Represents the geometric area of a box +- Supports margin, border, padding, and content areas +- Handles box sizing and positioning + +### 2. Layout Flow + +- `BlockFormattingContext` - Manages block-level layout +- `FloatContext` - Handles floating elements +- `InlineFormattingContext` - Manages inline-level layout +- Supports text layout, line breaking, and inline box positioning + +### 3. Style System + +- `Style` class - Handles computed styles +- `DeclaredStyle` - Manages declared styles +- Supports CSS properties including: + - Box model properties (margin, border, padding) + - Layout properties (display, position, float) + - Text properties (font, line-height, text-align) + - Visual properties (color, background) + +### 4. Text Layout + +- `Paragraph` - Handles text layout within blocks +- `Run` - Represents text runs with consistent styling +- `Linebox` - Manages line-level layout +- Supports text shaping, line breaking, and text alignment + +## Layout Process + +1. **Style Resolution** + + - Parse and cascade styles + - Compute final values for all properties + - Handle inheritance and initial values + +2. **Box Tree Construction** + + - Create box tree from DOM + - Assign styles to boxes + - Handle anonymous boxes + +3. **Prelayout** + + - Assign containing blocks + - Shape text and gather font metrics + - Handle relative positioning + +4. **Layout** + + - Block formatting context layout + - Float placement + - Inline formatting context layout + - Text layout and line breaking + +5. **Postlayout** + - Absolute positioning + - Pixel snapping + - Final position calculation + +## Box Border Drawing Integration + +### Current State + +The system currently has support for border properties in the style system: + +- Border width, style, and color properties +- Border area calculation in box model +- Logical border properties (block/inline) + +### Required Changes for Border Drawing + +1. **Box Model Updates** + + - Add border rendering to `Box` class + - Implement border style rendering (solid, dashed, etc.) + - Handle border radius (if supported) + +2. **Layout System Updates** + + - Account for border width in layout calculations + - Update box area calculations to include borders + - Handle border collapse (if supported) + +3. **Rendering System Updates** + - Add border drawing to paint system + - Implement border style rendering + - Handle border color and opacity + +### Implementation Strategy + +1. **Box Model Changes** + + - Update `BoxArea` to include border dimensions + - Add border rendering methods to `Box` class + - Implement border style calculations + +2. **Layout Changes** + + - Update `BlockFormattingContext` to account for borders + - Modify float placement to consider border width + - Update inline layout to handle bordered elements + +3. **Rendering Changes** + - **Integration with Paint System:** Modify the existing paint traversal (e.g., a `PaintingVisitor` or a `renderBox` method within each `Box` subclass) to incorporate border drawing. This step occurs after the background is painted but before child content is rendered. + - **Border Path Generation:** For each box with borders, generate the path(s) for the border(s) based on the box's dimensions (content, padding, and border-width). This needs to account for the `border-style`. + - **Style-Specific Rendering Routines:** + - `solid`: Draw a continuous line along the generated path. + - `dashed`: Draw a series of dashes along the path, respecting `border-color`. Dash length and gap might be predefined or configurable. + - `dotted`: Draw a series of dots (or small squares/circles) along the path. + - `double`: Draw two parallel solid lines, with a gap between them, ensuring the total width matches `border-width`. The gap itself should be transparent or background color. + - **Color and Opacity:** Apply the computed `border-color` and any relevant opacity values during the drawing operations. + - **Border Radius Handling (if supported):** If `border-radius` is implemented, the border path generation must create rounded corners. Each border segment (top, right, bottom, left) might need to be drawn as an arc and straight line combination. + +### Border Style Support + +1. **Basic Styles** + + - solid + - dashed + - dotted + - double + +2. **Advanced Styles** (if supported) + - groove + - ridge + - inset + - outset + +### Performance Considerations + +1. **Border Caching** + + - Cache border calculations + - Reuse border paths where possible + - Optimize border style rendering + +2. **Layout Optimization** + - Minimize layout recalculations + - Cache border dimensions + - Optimize border style changes + +### Implementation Status (COMPLETED) + +#### Phase 1: PaintBackend Enhancement ✅ + +- Added `path()` method to `PaintBackend` interface for SVG path rendering +- Added stroke pattern properties: `strokeDasharray`, `strokeLinecap`, `strokeLinejoin` +- Maintained backward compatibility with existing `edge()` calls + +#### Phase 2: Border Constants and Utilities ✅ + +- Added border pattern constants for dashed and dotted styles +- Created `getStrokePropertiesFromBorder()` function to convert border styles to stroke properties +- Added helper functions: `isBorderVisible()`, `bordersMatch()` + +#### Phase 3: Border Segment Detection ✅ + +- Implemented `getBorderSegments()` logic to detect contiguous border segments +- Created `BoxBorderSegment` type for tracking uniform adjacent borders +- Added segment matching logic for optimized rendering (full, three-side, two-side, single-side segments) + +#### Phase 4: Path Generation ✅ + +- Implemented `generateBorderPath()` and `generateSegmentPath()` functions +- Added support for border-radius (values will be zero until parser supports them) +- Handles double border insets using 1/3 and 5/3 factors +- Generates SVG path strings with proper arcs for rounded corners + +#### Phase 5: Paint System Integration ✅ + +- Updated `paintBlockBackground()` to use the new border system +- Replaced simple `edge()` calls with advanced path-based rendering +- Added support for different border styles (solid, dashed, dotted, double) +- Maintained inline border painting (can be enhanced later with path-based system) + +#### Phase 6: Backend Implementation ✅ + +- **SVG Backend:** Added stroke properties, implemented native SVG path rendering with proper stroke attributes +- **Canvas Backend:** Updated CanvasRenderingContext2D interface, implemented path() with Path2D support and fallback SVG parser for M/L/A/Z commands, handled optional methods with null checks +- **HTML Backend:** Implemented path() using inline SVG elements + +#### Bug Fixes ✅ + +- **Double Border Rendering:** Fixed double borders appearing as solid lines by implementing proper positioning logic + - Created `generateBorderPathForDouble()` function with correct inset calculations + - Outer border positioned at border edge with 1/6 border width adjustment + - Inner border positioned at 2/3 inset with proper spacing + - Both lines now render as distinct 1/3 width strokes with proper gap +- **Border Style Rendering:** Fixed dotted and dashed border rendering to match CSS specifications + - Dash arrays now properly multiply by border width for correct proportions + - Dotted borders use "round" line caps, dashed borders use "butt" line caps + - Stroke patterns scale correctly with border thickness +- **Border Radius Scaling:** Added proper radius overlap handling and proportional scaling + - Implements radius ratio calculation to prevent overlapping corners + - Scales all radii proportionally when they would exceed available space + - Handles complex geometry correctly for both single and double borders +- **Path Closing Logic:** Fixed incomplete border rendering for complete rectangular borders + - Added logic to close SVG paths only when all four sides are present + - Eliminates notches and gaps in complete borders (like solid borders on all sides) + - Prevents inappropriate path closing for partial border segments + +#### Border Radius Implementation ✅ + +- **CSS Parser Enhancement:** Updated `parse-css.pegjs` to support border-radius properties according to CSS Backgrounds and Borders Module Level 3 + - Added individual corner properties: `border-top-left-radius`, `border-top-right-radius`, `border-bottom-right-radius`, `border-bottom-left-radius` + - Added shorthand `border-radius` property with 1-4 value syntax support + - Support for both circular (single value) and elliptical (horizontal/vertical values) radii +- **Style System Integration:** Enhanced `style.ts` with complete border-radius support + - Added `BorderRadius` type supporting length, percentage, and elliptical values + - Added border-radius properties to `DeclaredStyleProperties` and `ComputedStyle` interfaces + - Implemented `getBorderTopLeftRadius()`, `getBorderTopRightRadius()`, `getBorderBottomRightRadius()`, `getBorderBottomLeftRadius()` methods + - Proper percentage resolution and logical property mapping +- **Paint System Integration:** Complete border path generation and rendering system restored + - Restored all border path generation functions: `generateSegmentPath()`, `generateBorderPath()`, `generateBorderPathForDouble()` + - Restored border utility functions: `getStrokePropertiesFromBorder()`, `isBorderVisible()`, `bordersMatch()` + - Restored border segment detection with `getBorderSegments()` for optimization + - Updated `paintBlockBackground()` to use advanced path-based border rendering + - Full support for border-radius values from style system in path generation + +#### Current Capabilities + +- **Block Element Borders:** Full support for dashed, dotted, double, and solid borders +- **Double Border Fix:** Double borders now render correctly as two separate lines with proper spacing +- **Segment Optimization:** Automatically detects and optimizes contiguous border segments +- **Border Radius Support:** Complete CSS border-radius implementation with parser, style system, and rendering support +- **Style Patterns:** Proper dash and dot patterns with correct line caps +- **Cross-Platform:** Works across SVG, Canvas, and HTML backends + +#### Next Steps (Lower Priority) + +- **Inline Border Enhancement:** Apply path-based system to inline borders for better style support +- **Advanced Border Styles:** Implement groove, ridge, inset, outset styles +- **Performance Optimization:** Add path caching for repeated border patterns + +## Fragmentation Implementation Strategy + +Implementing CSS fragmentation (e.g., for paged media or multi-column layouts) requires significant additions and modifications to the layout system. The goal is to break content across logical boundaries (fragmentainers) while respecting properties like `break-before`, `break-after`, `break-inside`, `widows`, and `orphans`. + +### 1. Fragmentation Context and Fragmentainers + +- **`FragmentationContext` Class:** Introduce a new context to manage the overall fragmentation process. This would be responsible for: + - Defining the dimensions and properties of fragmentainers (e.g., page size, column width/count). + - Iterating through content and distributing it into available fragmentainers. +- **`FragmentainerBox` (or `PageBox`, `ColumnBox`):** A new type of box representing an individual fragmentainer. It defines a viewport for a portion of the content. + +### 2. Layout Process Modifications + +- **Initial Layout Pass:** Perform an initial layout pass similar to the current process, but with an understanding of available space within the _first_ fragmentainer. +- **Break Point Identification:** + - During or after the initial layout of content within a fragmentainer, identify potential break points. + - Consider CSS properties (`break-before`, `break-after`, `break-inside`). + - Implement logic for `widows` and `orphans` for block-level content and text. + - Forced breaks (`page-break-before/after`, `column-break-before/after`) must be respected. +- **Content Splitting and Continuation:** + - **`Box` Splitting:** Modify `Box` classes (both block and inline) to support splitting. A box might be partially rendered in one fragmentainer and continued in the next. + - This involves cloning the box properties and adjusting its geometry and content for the new fragmentainer. + - Margins, borders, and padding at the split point need careful handling (e.g., `box-decoration-break`). + - **`InlineFormattingContext` and `LineBox`:** Update to handle line breaking across fragmentainers. Text runs and inline boxes might be split. + - \*\*` diff --git a/src/paint-canvas.ts b/src/paint-canvas.ts index 0a8d8c9..e694116 100644 --- a/src/paint-canvas.ts +++ b/src/paint-canvas.ts @@ -2,15 +2,24 @@ import { prevCluster, nextCluster, nextGrapheme, - prevGrapheme -} from './layout-text.ts'; -import {G_ID, G_CL, G_AX, G_AY, G_DX, G_DY, G_FL, G_SZ} from './text-harfbuzz.ts'; + prevGrapheme, +} from "./layout-text.ts"; +import { + G_ID, + G_CL, + G_AX, + G_AY, + G_DX, + G_DY, + G_FL, + G_SZ, +} from "./text-harfbuzz.ts"; -import type {Color} from './style.ts'; -import type {PaintBackend} from './paint.ts'; -import type {ShapedItem} from './layout-text.ts'; -import type {LoadedFontFace} from './text-font.ts'; -import type {Image} from './layout-image.ts'; +import type { Color } from "./style.ts"; +import type { PaintBackend } from "./paint.ts"; +import type { ShapedItem } from "./layout-text.ts"; +import type { LoadedFontFace } from "./text-font.ts"; +import type { Image } from "./layout-image.ts"; // This is used in the public API to ensure the external context has the right // API (there are four known to dropflow: node-canvas, @napi-rs/canvas, @@ -19,17 +28,46 @@ export interface CanvasRenderingContext2D { moveTo(x: number, y: number): void; lineTo(x: number, y: number): void; quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void; - bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void; + bezierCurveTo( + cp1x: number, + cp1y: number, + cp2x: number, + cp2y: number, + x: number, + y: number + ): void; fillRect(x: number, y: number, w: number, h: number): void; fillText(text: string, x: number, y: number, maxWidth?: number): void; translate(x: number, y: number): void; scale(x: number, y: number): void; stroke(): void; + stroke(path?: any): void; // For Path2D fill(): void; beginPath(): void; closePath(): void; save(): void; restore(): void; + arc( + x: number, + y: number, + radius: number, + startAngle: number, + endAngle: number, + counterclockwise?: boolean + ): void; + ellipse?( + x: number, + y: number, + radiusX: number, + radiusY: number, + rotation: number, + startAngle: number, + endAngle: number, + counterclockwise?: boolean + ): void; + setLineDash?(segments: number[]): void; + lineCap?: string; + lineJoin?: string; // strokeStyle and fillStyle could be objects (eg CanvasGradient) whose // interfaces could be different depending on if the backend is skia-canvas, // canvas, @napi-rs/canvas, or browser canvas, so their type is unknown by @@ -42,9 +80,9 @@ export interface CanvasRenderingContext2D { get fillStyle(): unknown; lineWidth: number; font: string; - set direction(value: 'ltr' | 'rtl'); // node-canvas has no concept of inherit + set direction(value: "ltr" | "rtl"); // node-canvas has no concept of inherit get direction(): unknown; // no use so far - set textAlign(value: 'left'); // only use 'left' so far + set textAlign(value: "left"); // only use 'left' so far get textAlign(): unknown; // no use so far rect(x: number, y: number, w: number, h: number): void; clip(): void; @@ -52,14 +90,18 @@ export interface CanvasRenderingContext2D { } export interface Canvas { - getContext(ctx: '2d'): CanvasRenderingContext2D; + getContext(ctx: "2d"): CanvasRenderingContext2D; width: number; height: number; } function findGlyph(item: ShapedItem, offset: number) { let index = item.attrs.level & 1 ? item.glyphs.length - G_SZ : 0; - while (index >= 0 && index < item.glyphs.length && item.glyphs[index + G_CL] < offset) { + while ( + index >= 0 && + index < item.glyphs.length && + item.glyphs[index + G_CL] < offset + ) { index += item.attrs.level & 1 ? -G_SZ : G_SZ; } return index; @@ -68,7 +110,7 @@ function findGlyph(item: ShapedItem, offset: number) { function glyphsWidth(item: ShapedItem, glyphStart: number, glyphEnd: number) { let ax = 0; for (let i = glyphStart; i < glyphEnd; i += G_SZ) ax += item.glyphs[i + G_AX]; - return ax / item.face.hbface.upem * item.attrs.style.fontSize; + return (ax / item.face.hbface.upem) * item.attrs.style.fontSize; } // Solve for: @@ -76,13 +118,20 @@ function glyphsWidth(item: ShapedItem, glyphStart: number, glyphEnd: number) { // startGlyphStart...startGlyphEnd: chain of glyphs inside totalTextStart..textStart // endGlyphStart...endGlyphEnd: chain of glyphs inside textEnd...totalTextEnd // TODO not well tested. this took me days to figure out -function fastGlyphBoundaries(item: ShapedItem, totalTextStart: number, totalTextEnd: number) { +function fastGlyphBoundaries( + item: ShapedItem, + totalTextStart: number, + totalTextEnd: number +) { const glyphs = item.glyphs; let startGlyphStart = findGlyph(item, totalTextStart); let endGlyphEnd = findGlyph(item, totalTextEnd); // TODO findGlyphFromEnd? let textStart = nextGrapheme(item.paragraph.string, totalTextStart); let startGlyphEnd = findGlyph(item, textStart); - let textEnd = Math.max(textStart, prevGrapheme(item.paragraph.string, totalTextEnd)); + let textEnd = Math.max( + textStart, + prevGrapheme(item.paragraph.string, totalTextEnd) + ); let endGlyphStart = findGlyph(item, textEnd); if (item.attrs.level & 1) { @@ -108,27 +157,40 @@ function fastGlyphBoundaries(item: ShapedItem, totalTextStart: number, totalText } if (item.attrs.level & 1) { - [startGlyphStart, startGlyphEnd] = [startGlyphEnd + G_SZ, startGlyphStart + G_SZ]; + [startGlyphStart, startGlyphEnd] = [ + startGlyphEnd + G_SZ, + startGlyphStart + G_SZ, + ]; [endGlyphStart, endGlyphEnd] = [endGlyphEnd + G_SZ, endGlyphStart + G_SZ]; } - return {startGlyphStart, startGlyphEnd, textStart, textEnd, endGlyphStart, endGlyphEnd}; + return { + startGlyphStart, + startGlyphEnd, + textStart, + textEnd, + endGlyphStart, + endGlyphEnd, + }; } export default class CanvasPaintBackend implements PaintBackend { fillColor: Color; strokeColor: Color; lineWidth: number; - direction: 'ltr' | 'rtl'; + direction: "ltr" | "rtl"; font: LoadedFontFace | undefined; fontSize: number; ctx: CanvasRenderingContext2D; + strokeDasharray?: string; + strokeLinecap?: "butt" | "round" | "square"; + strokeLinejoin?: "miter" | "round" | "bevel"; constructor(ctx: CanvasRenderingContext2D) { - this.fillColor = {r: 0, g: 0, b: 0, a: 0}; - this.strokeColor = {r: 0, g: 0, b: 0, a: 0}; + this.fillColor = { r: 0, g: 0, b: 0, a: 0 }; + this.strokeColor = { r: 0, g: 0, b: 0, a: 0 }; this.lineWidth = 0; - this.direction = 'ltr'; + this.direction = "ltr"; this.font = undefined; this.fontSize = 8; this.ctx = ctx; @@ -136,39 +198,56 @@ export default class CanvasPaintBackend implements PaintBackend { // TODO: pass in amount of each side that's shared with another border so they can divide // TODO: pass in border-radius - edge(x: number, y: number, length: number, side: 'top' | 'right' | 'bottom' | 'left') { - const {r, g, b, a} = this.strokeColor; + edge( + x: number, + y: number, + length: number, + side: "top" | "right" | "bottom" | "left" + ) { + const { r, g, b, a } = this.strokeColor; this.ctx.beginPath(); this.ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${a})`; this.ctx.lineWidth = this.lineWidth; this.ctx.moveTo(x, y); this.ctx.lineTo( - side === 'top' || side === 'bottom' ? x + length : x, - side === 'left' || side === 'right' ? y + length : y + side === "top" || side === "bottom" ? x + length : x, + side === "left" || side === "right" ? y + length : y ); this.ctx.stroke(); } - fastText(x: number, y: number, item: ShapedItem, textStart: number, textEnd: number) { + fastText( + x: number, + y: number, + item: ShapedItem, + textStart: number, + textEnd: number + ) { const text = item.paragraph.slice(textStart, textEnd); - const {r, g, b, a} = this.fillColor; + const { r, g, b, a } = this.fillColor; this.ctx.save(); - this.ctx.direction = item.attrs.level & 1 ? 'rtl' : 'ltr'; - this.ctx.textAlign = 'left'; + this.ctx.direction = item.attrs.level & 1 ? "rtl" : "ltr"; + this.ctx.textAlign = "left"; // TODO: PR to node-canvas to make this the default. I see no issues with // drawing glyphs, and it's way way way faster, and the correct way to do it - if ('textDrawingMode' in this.ctx) { - this.ctx.textDrawingMode = 'glyph'; + if ("textDrawingMode" in this.ctx) { + this.ctx.textDrawingMode = "glyph"; } - this.ctx.font = this.font?.toFontString(this.fontSize) || ''; + this.ctx.font = this.font?.toFontString(this.fontSize) || ""; this.ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`; this.ctx.fillText(text, x, y); this.ctx.restore(); } - correctText(x: number, y: number, item: ShapedItem, glyphStart: number, glyphEnd: number) { - const {r, g, b, a} = this.fillColor; - const scale = 1 / item.face.hbface.upem * this.fontSize; + correctText( + x: number, + y: number, + item: ShapedItem, + glyphStart: number, + glyphEnd: number + ) { + const { r, g, b, a } = this.fillColor; + const scale = (1 / item.face.hbface.upem) * this.fontSize; let sx = 0; let sy = 0; @@ -191,7 +270,14 @@ export default class CanvasPaintBackend implements PaintBackend { this.ctx.restore(); } - text(x: number, y: number, item: ShapedItem, totalTextStart: number, totalTextEnd: number, isColorBoundary: boolean) { + text( + x: number, + y: number, + item: ShapedItem, + totalTextStart: number, + totalTextEnd: number, + isColorBoundary: boolean + ) { if (isColorBoundary) { const { startGlyphStart, @@ -199,7 +285,7 @@ export default class CanvasPaintBackend implements PaintBackend { textStart, textEnd, endGlyphStart, - endGlyphEnd + endGlyphEnd, } = fastGlyphBoundaries(item, totalTextStart, totalTextEnd); if (item.attrs.level & 1) { @@ -237,12 +323,105 @@ export default class CanvasPaintBackend implements PaintBackend { } rect(x: number, y: number, w: number, h: number) { - const {r, g, b, a} = this.fillColor; + const { r, g, b, a } = this.fillColor; this.ctx.beginPath(); this.ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`; this.ctx.fillRect(x, y, w, h); } + path(pathData: string) { + const { r, g, b, a } = this.strokeColor; + this.ctx.beginPath(); + + // Apply stroke properties + this.ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${a})`; + this.ctx.lineWidth = this.lineWidth; + + if (this.strokeLinecap) { + this.ctx.lineCap = this.strokeLinecap; + } + if (this.strokeLinejoin) { + this.ctx.lineJoin = this.strokeLinejoin; + } + if (this.strokeDasharray) { + const dashArray = this.strokeDasharray.split(" ").map(Number); + if (this.ctx.setLineDash) { + this.ctx.setLineDash(dashArray); + } + } else { + if (this.ctx.setLineDash) { + this.ctx.setLineDash([]); + } + } + + // Try to use Path2D if available (modern browsers and some canvas implementations) + if (typeof Path2D !== "undefined") { + try { + const path2D = new Path2D(pathData); + this.ctx.stroke(path2D); + return; + } catch (e) { + // Fall back to manual parsing if Path2D fails + } + } + + // Basic SVG path parsing for simple cases (fallback for node-canvas and older browsers) + this.parseSvgPath(pathData); + this.ctx.stroke(); + } + + private parseSvgPath(pathData: string) { + // Simple SVG path parser for basic Move, Line, Arc commands + // This handles the path data generated by our border system + const commands = pathData.match(/[MLAZ][^MLAZ]*/gi) || []; + + for (const command of commands) { + const cmd = command[0].toUpperCase(); + const params = command + .slice(1) + .trim() + .split(/[\s,]+/) + .map(Number) + .filter((n) => !isNaN(n)); + + switch (cmd) { + case "M": // MoveTo + if (params.length >= 2) { + this.ctx.moveTo(params[0], params[1]); + } + break; + case "L": // LineTo + if (params.length >= 2) { + this.ctx.lineTo(params[0], params[1]); + } + break; + case "A": // Arc + if (params.length >= 7) { + // A rx ry x-axis-rotation large-arc-flag sweep-flag x y + const [rx, ry, rotation, _largeArc, _sweep, x, y] = params; + // For simple cases, approximate with arc (this won't handle all elliptical arcs perfectly) + // This is a simplified implementation for our border radius use case + try { + // Use ellipse if available (modern canvas implementations) + if (this.ctx.ellipse) { + this.ctx.ellipse(x, y, rx, ry, rotation, 0, Math.PI * 2); + } else { + // Fallback to arc for circular approximation + this.ctx.arc(x, y, Math.max(rx, ry), 0, Math.PI * 2); + } + } catch { + // If arc fails, just draw a line to the end point + this.ctx.lineTo(x, y); + } + } + break; + case "Z": // ClosePath + this.ctx.closePath(); + break; + } + } + } + pushClip(x: number, y: number, w: number, h: number) { this.ctx.save(); this.ctx.beginPath(); @@ -256,7 +435,9 @@ export default class CanvasPaintBackend implements PaintBackend { image(x: number, y: number, w: number, h: number, image: Image) { if (!image.decoded) { - throw new Error('Image handle missing. Did you call flow.loadSync instead of flow.load?'); + throw new Error( + "Image handle missing. Did you call flow.loadSync instead of flow.load?" + ); } this.ctx.drawImage(image.decoded, x, y, w, h); } diff --git a/src/paint-html.ts b/src/paint-html.ts index a03d529..5ed2210 100644 --- a/src/paint-html.ts +++ b/src/paint-html.ts @@ -1,18 +1,18 @@ -import {getMetrics, ShapedItem} from './layout-text.ts'; +import { getMetrics, ShapedItem } from "./layout-text.ts"; -import type {Color} from './style.ts'; -import type {PaintBackend} from './paint.ts'; -import type {LoadedFontFace} from './text-font.ts'; -import type {Image} from './layout-image.ts'; +import type { Color } from "./style.ts"; +import type { PaintBackend } from "./paint.ts"; +import type { LoadedFontFace } from "./text-font.ts"; +import type { Image } from "./layout-image.ts"; function encode(s: string) { - return s.replaceAll('&', '&').replaceAll('<', '<'); + return s.replaceAll("&", "&").replaceAll("<", "<"); } type StringMap = Record; function camelToKebab(camel: string) { - return camel.replace(/[A-Z]/g, s => '-' + s.toLowerCase()); + return camel.replace(/[A-Z]/g, (s) => "-" + s.toLowerCase()); } export default class HtmlPaintBackend implements PaintBackend { @@ -20,96 +20,153 @@ export default class HtmlPaintBackend implements PaintBackend { fillColor: Color; strokeColor: Color; lineWidth: number; - direction: 'ltr' | 'rtl'; + direction: "ltr" | "rtl"; font: LoadedFontFace | undefined; fontSize: number; + strokeDasharray?: string; + strokeLinecap?: "butt" | "round" | "square"; + strokeLinejoin?: "miter" | "round" | "bevel"; constructor() { - this.s = ''; - this.fillColor = {r: 0, g: 0, b: 0, a: 0}; - this.strokeColor = {r: 0, g: 0, b: 0, a: 0}; + this.s = ""; + this.fillColor = { r: 0, g: 0, b: 0, a: 0 }; + this.strokeColor = { r: 0, g: 0, b: 0, a: 0 }; this.lineWidth = 0; - this.direction = 'ltr'; + this.direction = "ltr"; this.font = undefined; this.fontSize = 0; } style(style: StringMap) { - return Object.entries(style).map(([prop, value]) => { - return `${camelToKebab(prop)}: ${value}`; - }).join('; '); + return Object.entries(style) + .map(([prop, value]) => { + return `${camelToKebab(prop)}: ${value}`; + }) + .join("; "); } attrs(attrs: StringMap) { - return Object.entries(attrs).map(([name, value]) => { - return `${name}="${value}"`; - }).join(' '); + return Object.entries(attrs) + .map(([name, value]) => { + return `${name}="${value}"`; + }) + .join(" "); } // TODO: pass in amount of each side that's shared with another border so they can divide // TODO: pass in border-radius - edge(x: number, y: number, length: number, side: 'top' | 'right' | 'bottom' | 'left') { - const {r, g, b, a} = this.strokeColor; + edge( + x: number, + y: number, + length: number, + side: "top" | "right" | "bottom" | "left" + ) { + const { r, g, b, a } = this.strokeColor; const sw = this.lineWidth; - const left = (side === 'left' ? x - sw/2 : side === 'right' ? x - sw/2 : x) + 'px'; - const top = (side === 'top' ? y - sw/2 : side === 'bottom' ? y - sw/2 : y) + 'px'; - const width = side === 'top' || side === 'bottom' ? length + 'px' : sw + 'px'; - const height = side === 'left' || side === 'right' ? length + 'px' : sw + 'px'; - const position = 'absolute'; + const left = + (side === "left" ? x - sw / 2 : side === "right" ? x - sw / 2 : x) + "px"; + const top = + (side === "top" ? y - sw / 2 : side === "bottom" ? y - sw / 2 : y) + "px"; + const width = + side === "top" || side === "bottom" ? length + "px" : sw + "px"; + const height = + side === "left" || side === "right" ? length + "px" : sw + "px"; + const position = "absolute"; const backgroundColor = `rgba(${r}, ${g}, ${b}, ${a})`; - const style = this.style({position, left, top, width, height, backgroundColor}); + const style = this.style({ + position, + left, + top, + width, + height, + backgroundColor, + }); this.s += `
`; } - text(x: number, y: number, item: ShapedItem, textStart: number, textEnd: number) { - const {ascenderBox, descenderBox} = getMetrics(item.attrs.style, item.face); + text( + x: number, + y: number, + item: ShapedItem, + textStart: number, + textEnd: number + ) { + const { ascenderBox, descenderBox } = getMetrics( + item.attrs.style, + item.face + ); const text = item.paragraph.string.slice(textStart, textEnd); - const {r, g, b, a} = this.fillColor; + const { r, g, b, a } = this.fillColor; const style = this.style({ - position: 'absolute', - left: '0', - top: '0', - transform: `translate(${x}px, ${y - (ascenderBox - (ascenderBox + descenderBox)/2)}px)`, + position: "absolute", + left: "0", + top: "0", + transform: `translate(${x}px, ${ + y - (ascenderBox - (ascenderBox + descenderBox) / 2) + }px)`, font: this.font?.toFontString(this.fontSize) || "", - lineHeight: '0', - whiteSpace: 'pre', + lineHeight: "0", + whiteSpace: "pre", direction: this.direction, - unicodeBidi: 'bidi-override', - color: `rgba(${r}, ${g}, ${b}, ${a})` + unicodeBidi: "bidi-override", + color: `rgba(${r}, ${g}, ${b}, ${a})`, }); this.s += `
${encode(text)}
`; } rect(x: number, y: number, w: number, h: number) { - const {r, g, b, a} = this.fillColor; + const { r, g, b, a } = this.fillColor; const style = this.style({ - position: 'absolute', - left: x + 'px', - top: y + 'px', - width: w + 'px', - height: h + 'px', - backgroundColor: `rgba(${r}, ${g}, ${b}, ${a})` + position: "absolute", + left: x + "px", + top: y + "px", + width: w + "px", + height: h + "px", + backgroundColor: `rgba(${r}, ${g}, ${b}, ${a})`, }); this.s += `
`; } image(x: number, y: number, w: number, h: number, image: Image) { const style = this.style({ - position: 'absolute', - left: x + 'px', - top: y + 'px', - width: w + 'px', - height: h + 'px' + position: "absolute", + left: x + "px", + top: y + "px", + width: w + "px", + height: h + "px", }); this.s += ``; } + path(pathData: string) { + // For HTML backend, render SVG path as an inline SVG element + const { r, g, b, a } = this.strokeColor; + const stroke = `rgba(${r}, ${g}, ${b}, ${a})`; + + const strokeDasharray = this.strokeDasharray + ? `stroke-dasharray="${this.strokeDasharray}"` + : ""; + const strokeLinecap = this.strokeLinecap + ? `stroke-linecap="${this.strokeLinecap}"` + : ""; + const strokeLinejoin = this.strokeLinejoin + ? `stroke-linejoin="${this.strokeLinejoin}"` + : ""; + + // Create a minimal SVG container for the path + this.s += ``; + this.s += ``; + this.s += ``; + } + pushClip(x: number, y: number, width: number, height: number) { - this.s += `
`; + this.s += `
`; } popClip() { - this.s += '
'; + this.s += "
"; } } diff --git a/src/paint-svg.ts b/src/paint-svg.ts index fffdea7..f447e11 100644 --- a/src/paint-svg.ts +++ b/src/paint-svg.ts @@ -1,16 +1,16 @@ -import {ShapedItem} from './layout-text.ts'; +import { ShapedItem } from "./layout-text.ts"; -import type {Color} from './style.ts'; -import type {PaintBackend} from './paint.ts'; -import type {LoadedFontFace} from './text-font.ts'; -import type {Image} from './layout-image.ts'; +import type { Color } from "./style.ts"; +import type { PaintBackend } from "./paint.ts"; +import type { LoadedFontFace } from "./text-font.ts"; +import type { Image } from "./layout-image.ts"; function encode(s: string) { - return s.replaceAll('&', '&').replaceAll('<', '<'); + return s.replaceAll("&", "&").replaceAll("<", "<"); } function camelToKebab(camel: string) { - return camel.replace(/[A-Z]/g, s => '-' + s.toLowerCase()); + return camel.replace(/[A-Z]/g, (s) => "-" + s.toLowerCase()); } interface Rect { @@ -18,11 +18,11 @@ interface Rect { x: number; y: number; width: number; - height: number -}; + height: number; +} function intersectRects(rects: Rect[]) { - const rect = {...rects[0]}; + const rect = { ...rects[0] }; for (let i = 1; i < rects.length; i++) { const right = rect.x + rect.width; @@ -37,7 +37,7 @@ function intersectRects(rects: Rect[]) { } function createId() { - let ret = ''; + let ret = ""; for (let i = 0; i < 10; i++) { ret += String.fromCharCode(0x61 /* 'a' */ + Math.floor(Math.random() * 26)); } @@ -51,85 +51,122 @@ export default class SvgPaintBackend implements PaintBackend { fillColor: Color; strokeColor: Color; lineWidth: number; - direction: 'ltr' | 'rtl'; + direction: "ltr" | "rtl"; font: LoadedFontFace | undefined; fontSize: number; usedFonts: Map; + strokeDasharray?: string; + strokeLinecap?: "butt" | "round" | "square"; + strokeLinejoin?: "miter" | "round" | "bevel"; constructor() { - this.main = ''; - this.defs = ''; + this.main = ""; + this.defs = ""; this.clips = []; - this.fillColor = {r: 0, g: 0, b: 0, a: 0}; - this.strokeColor = {r: 0, g: 0, b: 0, a: 0}; + this.fillColor = { r: 0, g: 0, b: 0, a: 0 }; + this.strokeColor = { r: 0, g: 0, b: 0, a: 0 }; this.lineWidth = 0; - this.direction = 'ltr'; + this.direction = "ltr"; this.font = undefined; this.fontSize = 0; this.usedFonts = new Map(); } style(style: Record) { - return Object.entries(style).map(([prop, value]) => { - return `${camelToKebab(prop)}: ${value}`; - }).join('; '); + return Object.entries(style) + .map(([prop, value]) => { + return `${camelToKebab(prop)}: ${value}`; + }) + .join("; "); } - edge(x: number, y: number, length: number, side: 'top' | 'right' | 'bottom' | 'left') { - const {r, g, b, a} = this.strokeColor; + edge( + x: number, + y: number, + length: number, + side: "top" | "right" | "bottom" | "left" + ) { + const { r, g, b, a } = this.strokeColor; const lw = this.lineWidth; const lw2 = lw / 2; - const width = side === 'top' || side === 'bottom' ? length : lw; - const height = side === 'left' || side === 'right' ? length : lw; + const width = side === "top" || side === "bottom" ? length : lw; + const height = side === "left" || side === "right" ? length : lw; const backgroundColor = `rgba(${r}, ${g}, ${b}, ${a})`; const rect = this.clips.at(-1); - const clipPath = rect ? `clip-path="url(#${rect.id}) "` : ' '; + const clipPath = rect ? `clip-path="url(#${rect.id}) "` : " "; - x = side === 'left' || side === 'right' ? x - lw2 : x; - y = side === 'top' || side === 'bottom' ? y - lw2 : y; + x = side === "left" || side === "right" ? x - lw2 : x; + y = side === "top" || side === "bottom" ? y - lw2 : y; this.main += ``; } - text(x: number, y: number, item: ShapedItem, textStart: number, textEnd: number) { + text( + x: number, + y: number, + item: ShapedItem, + textStart: number, + textEnd: number + ) { const text = item.paragraph.string.slice(textStart, textEnd).trim(); - const {r, g, b, a} = this.fillColor; + const { r, g, b, a } = this.fillColor; const color = `rgba(${r}, ${g}, ${b}, ${a})`; const style = this.style({ - font: this.font?.toFontString(this.fontSize) ?? '', - whiteSpace: 'pre', + font: this.font?.toFontString(this.fontSize) ?? "", + whiteSpace: "pre", direction: this.direction, - unicodeBidi: 'bidi-override' + unicodeBidi: "bidi-override", }); const rect = this.clips.at(-1); - const clipPath = rect ? `clip-path="url(#${rect.id}) "` : ' '; + const clipPath = rect ? `clip-path="url(#${rect.id}) "` : " "; // We set the direction property above so that unicode-bidi: bidi-override // works and the direction set in CSS is the direction used in the outputted // SVG. But that also causes the text to be right-aligned, and this seems to // be the only way to get around that. - if (this.direction === 'rtl') x += item.measure().advance; + if (this.direction === "rtl") x += item.measure().advance; - this.main += `${encode(text)}`; + this.main += `${encode(text)}`; this.usedFonts.set(item.face.url.href, item.face); } rect(x: number, y: number, w: number, h: number) { - const {r, g, b, a} = this.fillColor; + const { r, g, b, a } = this.fillColor; const fill = `rgba(${r}, ${g}, ${b}, ${a})`; const rect = this.clips.at(-1); - const clipPath = rect ? `clip-path="url(#${rect.id}) "` : ' '; + const clipPath = rect ? `clip-path="url(#${rect.id}) "` : " "; this.main += ``; } + path(pathData: string) { + const { r, g, b, a } = this.strokeColor; + const stroke = `rgba(${r}, ${g}, ${b}, ${a})`; + const rect = this.clips.at(-1); + const clipPath = rect ? `clip-path="url(#${rect.id}) "` : " "; + + const strokeDasharray = this.strokeDasharray + ? `stroke-dasharray="${this.strokeDasharray}" ` + : ""; + const strokeLinecap = this.strokeLinecap + ? `stroke-linecap="${this.strokeLinecap}" ` + : ""; + const strokeLinejoin = this.strokeLinejoin + ? `stroke-linejoin="${this.strokeLinejoin}" ` + : ""; + + this.main += ``; + } + pushClip(x: number, y: number, width: number, height: number) { const id = createId(); - this.clips.push({id, x, y, width, height}); + this.clips.push({ id, x, y, width, height }); { - const {x, y, width, height} = intersectRects(this.clips); + const { x, y, width, height } = intersectRects(this.clips); const shape = ``; this.defs += `${shape}`; } diff --git a/src/paint.ts b/src/paint.ts index ecd1711..6a0bcbb 100644 --- a/src/paint.ts +++ b/src/paint.ts @@ -1,28 +1,73 @@ -import {BlockContainer, ReplacedBox, Inline, IfcInline} from './layout-flow.ts'; -import {Image} from './layout-image.ts'; -import {G_CL, G_AX, G_SZ} from './text-harfbuzz.ts'; -import {ShapedItem, Paragraph} from './layout-text.ts'; -import {Box, FormattingBox} from './layout-box.ts'; -import {binarySearchOf} from './util.ts'; - -import type {InlineLevel, BlockLevel} from './layout-flow.ts'; -import type {BackgroundBox} from './layout-text.ts'; -import type {Color} from './style.ts'; -import type {LoadedFontFace} from './text-font.ts'; +import { + BlockContainer, + ReplacedBox, + Inline, + IfcInline, +} from "./layout-flow.ts"; +import { Image } from "./layout-image.ts"; +import { G_CL, G_AX, G_SZ } from "./text-harfbuzz.ts"; +import { ShapedItem, Paragraph } from "./layout-text.ts"; +import { Box, FormattingBox } from "./layout-box.ts"; +import { binarySearchOf } from "./util.ts"; + +import type { InlineLevel, BlockLevel } from "./layout-flow.ts"; +import type { BackgroundBox } from "./layout-text.ts"; +import type { Color } from "./style.ts"; +import type { LoadedFontFace } from "./text-font.ts"; + +// Border rendering constants +const BORDER_DASHED_ARRAY = [4, 4]; +const BORDER_DOTTED_ARRAY = [0, 2]; + +interface BorderInfo { + width: number; + style: string; + color: Color; +} + +interface BoxBorderSegment { + firstSide: "left" | "top" | "right" | "bottom"; + left: boolean; + top: boolean; + right: boolean; + bottom: boolean; +} export interface PaintBackend { fillColor: Color; strokeColor: Color; lineWidth: number; - direction: 'ltr' | 'rtl'; + direction: "ltr" | "rtl"; font: LoadedFontFace | undefined; fontSize: number; - edge(x: number, y: number, length: number, side: 'top' | 'right' | 'bottom' | 'left'): void; - text(x: number, y: number, item: ShapedItem, textStart: number, textEnd: number, isColorBoundary?: boolean): void; + strokeDasharray?: string; + strokeLinecap?: "butt" | "round" | "square"; + strokeLinejoin?: "miter" | "round" | "bevel"; + edge( + x: number, + y: number, + length: number, + side: "top" | "right" | "bottom" | "left" + ): void; + text( + x: number, + y: number, + item: ShapedItem, + textStart: number, + textEnd: number, + isColorBoundary?: boolean + ): void; rect(x: number, y: number, w: number, h: number): void; + path(pathData: string): void; pushClip(x: number, y: number, w: number, h: number): void; popClip(): void; - image(x: number, y: number, width: number, height: number, image: Image): void; + image( + x: number, + y: number, + width: number, + height: number, + image: Image + ): void; } function getTextOffsetsForUncollapsedGlyphs(item: ShapedItem) { @@ -30,7 +75,8 @@ function getTextOffsetsForUncollapsedGlyphs(item: ShapedItem) { let glyphStart = 0; let glyphEnd = glyphs.length - G_SZ; - while (glyphStart < glyphs.length && glyphs[glyphStart + G_AX] === 0) glyphStart += G_SZ; + while (glyphStart < glyphs.length && glyphs[glyphStart + G_AX] === 0) + glyphStart += G_SZ; while (glyphEnd >= 0 && glyphs[glyphEnd + G_AX] === 0) glyphEnd -= G_SZ; if (glyphStart in glyphs && glyphEnd in glyphs) { @@ -52,9 +98,9 @@ function getTextOffsetsForUncollapsedGlyphs(item: ShapedItem) { } } - return {textStart, textEnd}; + return { textStart, textEnd }; } else { - return {textStart: 0, textEnd: 0}; + return { textStart: 0, textEnd: 0 }; } } @@ -64,13 +110,19 @@ function drawText( b: PaintBackend ) { const style = item.attrs.style; - const {textStart, textEnd} = getTextOffsetsForUncollapsedGlyphs(item); + const { textStart, textEnd } = getTextOffsetsForUncollapsedGlyphs(item); // Split the colors into spans so that colored diacritics can work. // Sadly this seems to only work in Firefox and only when the font doesn't do // any normalizination, so I could probably stop trying to support it // https://github.com/w3c/csswg-drafts/issues/699 - const end = item.attrs.level & 1 ? item.colorsStart(colors) - 1 : item.colorsEnd(colors); - let i = item.attrs.level & 1 ? item.colorsEnd(colors) - 1 : item.colorsStart(colors); + const end = + item.attrs.level & 1 + ? item.colorsStart(colors) - 1 + : item.colorsEnd(colors); + let i = + item.attrs.level & 1 + ? item.colorsEnd(colors) - 1 + : item.colorsStart(colors); let glyphIndex = 0; let tx = item.x; @@ -83,17 +135,24 @@ function drawText( if (start < end) { // TODO: should really have isStartColorBoundary, isEndColorBoundary - const isColorBoundary = start !== textStart && start === colorStart - || end !== textEnd && end === colorEnd; + const isColorBoundary = + (start !== textStart && start === colorStart) || + (end !== textEnd && end === colorEnd); let ax = 0; if (item.attrs.level & 1) { - while (glyphIndex < item.glyphs.length && item.glyphs[glyphIndex + G_CL] >= start) { + while ( + glyphIndex < item.glyphs.length && + item.glyphs[glyphIndex + G_CL] >= start + ) { ax += item.glyphs[glyphIndex + G_AX]; glyphIndex += G_SZ; } } else { - while (glyphIndex < item.glyphs.length && item.glyphs[glyphIndex + G_CL] < end) { + while ( + glyphIndex < item.glyphs.length && + item.glyphs[glyphIndex + G_CL] < end + ) { ax += item.glyphs[glyphIndex + G_AX]; glyphIndex += G_SZ; } @@ -102,10 +161,10 @@ function drawText( b.fillColor = color; b.fontSize = style.fontSize; b.font = item.face; - b.direction = item.attrs.level & 1 ? 'rtl' : 'ltr'; + b.direction = item.attrs.level & 1 ? "rtl" : "ltr"; b.text(tx, item.y, item, start, end, isColorBoundary); - tx += ax / item.face.hbface.upem * style.fontSize; + tx += (ax / item.face.hbface.upem) * style.fontSize; } if (item.attrs.level & 1) { @@ -119,17 +178,24 @@ function drawText( /** * Paints the background and borders */ -function paintFormattingBoxBackground(box: FormattingBox, b: PaintBackend, isRoot = false) { +function paintFormattingBoxBackground( + box: FormattingBox, + b: PaintBackend, + isRoot = false +) { const style = box.style; const borderArea = box.getBorderArea(); if (!isRoot) { const paddingArea = box.getPaddingArea(); const contentArea = box.getContentArea(); - const {backgroundColor, backgroundClip} = style; - const area = backgroundClip === 'border-box' ? borderArea : - backgroundClip === 'padding-box' ? paddingArea : - contentArea; + const { backgroundColor, backgroundClip } = style; + const area = + backgroundClip === "border-box" + ? borderArea + : backgroundClip === "padding-box" + ? paddingArea + : contentArea; if (backgroundColor.a > 0) { b.fillColor = backgroundColor; @@ -137,37 +203,125 @@ function paintFormattingBoxBackground(box: FormattingBox, b: PaintBackend, isRoo } } - const work = [ - ['top', style.borderTopWidth, style.borderTopColor], - ['right', style.borderRightWidth, style.borderRightColor], - ['bottom', style.borderBottomWidth, style.borderBottomColor], - ['left', style.borderLeftWidth, style.borderLeftColor], - ] as const; + // Advanced border rendering with path-based system + const borders = { + left: { + width: style.borderLeftWidth, + style: style.borderLeftStyle, + color: style.borderLeftColor, + }, + top: { + width: style.borderTopWidth, + style: style.borderTopStyle, + color: style.borderTopColor, + }, + right: { + width: style.borderRightWidth, + style: style.borderRightStyle, + color: style.borderRightColor, + }, + bottom: { + width: style.borderBottomWidth, + style: style.borderBottomStyle, + color: style.borderBottomColor, + }, + }; + + // Get border radius values from the style + const borderRadii = { + topLeft: box.style.getBorderTopLeftRadius(box), + topRight: box.style.getBorderTopRightRadius(box), + bottomRight: box.style.getBorderBottomRightRadius(box), + bottomLeft: box.style.getBorderBottomLeftRadius(box), + }; + + const segments = getBorderSegments(borders); + + for (const segment of segments) { + const border = borders[segment.firstSide]; + if (!isBorderVisible(border)) continue; + + const strokeProps = getStrokePropertiesFromBorder(border); + + // Apply stroke properties to backend + b.strokeColor = strokeProps.strokeColor; + b.lineWidth = strokeProps.strokeWidth; + b.strokeDasharray = strokeProps.strokeDasharray; + b.strokeLinecap = strokeProps.strokeLinecap; + + if (border.style === "double") { + // For double borders, we need to draw two separate lines + // Each line should be 1/3 of the total border width + // The outer line is at the border edge, the inner line is 2/3 inset + + // Set stroke width to 1/3 of border width for double borders + const doubleStrokeWidth = border.width > 0 ? border.width / 3 : 0; + b.lineWidth = doubleStrokeWidth; + + // Draw the outer border (at border edge) + const outerPath = generateBorderPathForDouble( + borderArea.x, + borderArea.y, + borderArea.width, + borderArea.height, + borders, + borderRadii, + segment, + "outer" + ); + b.path(outerPath); + + // Draw the inner border (inset by 2/3 of border width) + const innerPath = generateBorderPathForDouble( + borderArea.x, + borderArea.y, + borderArea.width, + borderArea.height, + borders, + borderRadii, + segment, + "inner" + ); + b.path(innerPath); + } else { + // For all other border styles, draw a single path + const path = generateBorderPath( + borderArea.x, + borderArea.y, + borderArea.width, + borderArea.height, + borders, + borderRadii, + segment, + 1 + ); + b.path(path); + } - for (const [side, lineWidth, color] of work) { - if (lineWidth === 0 || color.a === 0) continue; - const length = side === 'top' || side === 'bottom' ? borderArea.width : borderArea.height; - let x = side === 'right' ? borderArea.x + borderArea.width - lineWidth: borderArea.x; - let y = side === 'bottom' ? borderArea.y + borderArea.height - lineWidth : borderArea.y; - b.strokeColor = color; - b.lineWidth = lineWidth; - x += side === 'left' || side === 'right' ? lineWidth/2 : 0; - y += side === 'top' || side === 'bottom' ? lineWidth/2 : 0; - b.edge(x, y, length, side); + // Reset stroke properties to defaults + b.strokeDasharray = undefined; + b.strokeLinecap = "butt"; } } -function paintBackgroundDescendents(root: FormattingBox | Inline, b: PaintBackend) { - const stack: (FormattingBox | Inline | {sentinel: true})[] = [root]; +function paintBackgroundDescendents( + root: FormattingBox | Inline, + b: PaintBackend +) { + const stack: (FormattingBox | Inline | { sentinel: true })[] = [root]; const parents: Box[] = []; while (stack.length) { const box = stack.pop()!; - if ('sentinel' in box) { + if ("sentinel" in box) { const box = parents.pop()!; - if (box.isFormattingBox() && box.style.overflow === 'hidden' && box !== root) { + if ( + box.isFormattingBox() && + box.style.overflow === "hidden" && + box !== root + ) { b.popClip(); } } else { @@ -176,11 +330,15 @@ function paintBackgroundDescendents(root: FormattingBox | Inline, b: PaintBacken } if (box.isBlockContainer() && box.hasBackgroundInLayerRoot()) { - stack.push({sentinel: true}); + stack.push({ sentinel: true }); parents.push(box); - if (box.isFormattingBox() && box.style.overflow === 'hidden' && box !== root) { - const {x, y, width, height} = box.getPaddingArea(); + if ( + box.isFormattingBox() && + box.style.overflow === "hidden" && + box !== root + ) { + const { x, y, width, height } = box.getPaddingArea(); b.pushClip(x, y, width, height); } @@ -203,7 +361,7 @@ function snap(ox: number, oy: number, ow: number, oh: number) { const y = Math.round(oy); const width = Math.round(ox + ow) - x; const height = Math.round(oy + oh) - y; - return {x, y, width, height}; + return { x, y, width, height }; } function paintInlineBackground( @@ -216,18 +374,35 @@ function paintInlineBackground( const direction = ifc.style.direction; const bgc = inline.style.backgroundColor; const clip = inline.style.backgroundClip; - const {borderTopColor, borderRightColor, borderBottomColor, borderLeftColor} = inline.style; - const {a: ta} = borderTopColor; - const {a: ra} = borderRightColor; - const {a: ba} = borderBottomColor; - const {a: la} = borderLeftColor; - const {start, end, blockOffset, ascender, descender, naturalStart, naturalEnd} = background; + const { + borderTopColor, + borderRightColor, + borderBottomColor, + borderLeftColor, + } = inline.style; + const { a: ta } = borderTopColor; + const { a: ra } = borderRightColor; + const { a: ba } = borderBottomColor; + const { a: la } = borderLeftColor; + const { + start, + end, + blockOffset, + ascender, + descender, + naturalStart, + naturalEnd, + } = background; const paddingTop = inline.style.getPaddingBlockStart(ifc); const paddingRight = inline.style.getPaddingLineRight(ifc); const paddingBottom = inline.style.getPaddingBlockEnd(ifc); const paddingLeft = inline.style.getPaddingLineLeft(ifc); - const paintLeft = naturalStart && direction === 'ltr' || naturalEnd && direction === 'rtl'; - const paintRight = naturalEnd && direction === 'ltr' || naturalStart && direction === 'rtl'; + const paintLeft = + (naturalStart && direction === "ltr") || + (naturalEnd && direction === "rtl"); + const paintRight = + (naturalEnd && direction === "ltr") || + (naturalStart && direction === "rtl"); const borderTopWidth = inline.style.getBorderBlockStartWidth(ifc); let borderRightWidth = inline.style.getBorderLineRightWidth(ifc); const borderBottomWidth = inline.style.getBorderBlockEndWidth(ifc); @@ -240,18 +415,18 @@ function paintInlineBackground( let extraTop = 0; let extraBottom = 0; - if (clip !== 'content-box') { + if (clip !== "content-box") { extraTop += inline.style.getPaddingBlockStart(ifc); extraBottom += inline.style.getPaddingBlockEnd(ifc); } - if (clip === 'border-box') { + if (clip === "border-box") { extraTop += borderTopWidth; extraBottom += borderBottomWidth; } b.fillColor = bgc; - const {x, y, width, height} = snap( + const { x, y, width, height } = snap( Math.min(start, end), blockOffset - ascender - extraTop, Math.abs(start - end), @@ -264,16 +439,16 @@ function paintInlineBackground( let extraLeft = 0; let extraRight = 0; - if (paintLeft && clip === 'content-box') extraLeft += paddingLeft - if (paintLeft && clip !== 'border-box') extraLeft += borderLeftWidth; - if (paintRight && clip === 'content-box') extraRight += paddingRight; - if (paintRight && clip !== 'border-box') extraRight += borderRightWidth; + if (paintLeft && clip === "content-box") extraLeft += paddingLeft; + if (paintLeft && clip !== "border-box") extraLeft += borderLeftWidth; + if (paintRight && clip === "content-box") extraRight += paddingRight; + if (paintRight && clip !== "border-box") extraRight += borderRightWidth; const work = [ - ['top', borderTopWidth, borderTopColor], - ['right', borderRightWidth, borderRightColor], - ['bottom', borderBottomWidth, borderBottomColor], - ['left', borderLeftWidth, borderLeftColor] + ["top", borderTopWidth, borderTopColor], + ["right", borderRightWidth, borderRightColor], + ["bottom", borderBottomWidth, borderBottomColor], + ["left", borderLeftWidth, borderLeftColor], ] as const; // TODO there's a bug here: try @@ -285,14 +460,22 @@ function paintInlineBackground( Math.min(start, end) - extraLeft, blockOffset - ascender - paddingTop - borderTopWidth, Math.abs(start - end) + extraLeft + extraRight, - borderTopWidth + paddingTop + ascender + descender + paddingBottom + borderBottomWidth + borderTopWidth + + paddingTop + + ascender + + descender + + paddingBottom + + borderBottomWidth ); - const length = side === 'left' || side === 'right' ? rect.height : rect.width; - let x = side === 'right' ? rect.x + rect.width : rect.x; - let y = side === 'bottom' ? rect.y + rect.height : rect.y; - x += side === 'left' ? lineWidth/2 : side === 'right' ? -lineWidth/2 : 0; - y += side === 'top' ? lineWidth/2 : side === 'bottom' ? -lineWidth/2 : 0; + const length = + side === "left" || side === "right" ? rect.height : rect.width; + let x = side === "right" ? rect.x + rect.width : rect.x; + let y = side === "bottom" ? rect.y + rect.height : rect.y; + x += + side === "left" ? lineWidth / 2 : side === "right" ? -lineWidth / 2 : 0; + y += + side === "top" ? lineWidth / 2 : side === "bottom" ? -lineWidth / 2 : 0; b.lineWidth = lineWidth; b.strokeColor = color; b.edge(x, y, length, side); @@ -302,8 +485,8 @@ function paintInlineBackground( function paintReplacedBox(box: ReplacedBox, b: PaintBackend) { const image = box.getImage(); - if (image?.status === 'loaded') { - const {x, y, width, height} = box.getContentArea(); + if (image?.status === "loaded") { + const { x, y, width, height } = box.getContentArea(); b.image(x, y, width, height, image); } } @@ -319,7 +502,8 @@ function paintInlines(root: BlockLayerRoot, ifc: IfcInline, b: PaintBackend) { let hasPositionedParent = false; if (lineboxItem) lineboxItem = lineboxItem.next; - if (!lineboxItem) { // starting a new linebox + if (!lineboxItem) { + // starting a new linebox lineboxItem = lineboxes[++lineboxIndex].head; painted.clear(); } @@ -360,12 +544,12 @@ function paintInlines(root: BlockLayerRoot, ifc: IfcInline, b: PaintBackend) { } function paintBlockForeground(root: BlockLayerRoot, b: PaintBackend) { - const stack: (IfcInline | BlockLevel | {sentinel: true})[] = [root.box]; + const stack: (IfcInline | BlockLevel | { sentinel: true })[] = [root.box]; while (stack.length) { const box = stack.pop()!; - if ('sentinel' in box) { + if ("sentinel" in box) { b.popClip(); } else if (box.isReplacedBox()) { // Belongs to this LayerRoot @@ -379,10 +563,10 @@ function paintBlockForeground(root: BlockLayerRoot, b: PaintBackend) { // Has something we should paint underneath it (box.hasForegroundInLayerRoot() || root.isInInlineBlockPath(box)) ) { - if (box !== root.box && box.style.overflow === 'hidden') { - const {x, y, width, height} = box.getPaddingArea(); + if (box !== root.box && box.style.overflow === "hidden") { + const { x, y, width, height } = box.getPaddingArea(); b.pushClip(x, y, width, height); - stack.push({sentinel: true}); + stack.push({ sentinel: true }); } for (let i = box.children.length - 1; i >= 0; i--) { @@ -402,7 +586,11 @@ function paintInline( const treeItems = paragraph.treeItems; const stack = root.box.children.slice().reverse(); const ranges: [number, number][] = []; - let itemIndex = binarySearchOf(paragraph.treeItems, root.box.start, item => item.offset); + let itemIndex = binarySearchOf( + paragraph.treeItems, + root.box.start, + (item) => item.offset + ); function paintRanges() { while (ranges.length) { @@ -481,26 +669,32 @@ class LayerRoot { get zIndex() { const zIndex = this.box.style.zIndex; - return zIndex === 'auto' ? 0 : zIndex; + return zIndex === "auto" ? 0 : zIndex; } finalize(preorderScores: Map) { this.negativeRoots.sort((a, b) => a.zIndex - b.zIndex); - this.floats.sort((a, b) => preorderScores.get(a.box)! - preorderScores.get(b.box)!); - this.positionedRoots.sort((a, b) => preorderScores.get(a.box)! - preorderScores.get(b.box)!); + this.floats.sort( + (a, b) => preorderScores.get(a.box)! - preorderScores.get(b.box)! + ); + this.positionedRoots.sort( + (a, b) => preorderScores.get(a.box)! - preorderScores.get(b.box)! + ); this.positiveRoots.sort((a, b) => a.zIndex - b.zIndex); } isEmpty() { - return !this.box.hasBackground() - && !this.box.hasForeground() - && !this.box.hasBackgroundInLayerRoot() - && !this.box.hasForegroundInLayerRoot() - && this.negativeRoots.length === 0 - && this.floats.length === 0 - && this.positionedRoots.length === 0 - && this.positiveRoots.length === 0 - && this.inlineBlocks.size === 0; + return ( + !this.box.hasBackground() && + !this.box.hasForeground() && + !this.box.hasBackgroundInLayerRoot() && + !this.box.hasForegroundInLayerRoot() && + this.negativeRoots.length === 0 && + this.floats.length === 0 && + this.positionedRoots.length === 0 && + this.positiveRoots.length === 0 && + this.inlineBlocks.size === 0 + ); } /** @@ -563,7 +757,9 @@ function createLayerRoot(box: BlockContainer) { const layerRoot = new BlockLayerRoot(box, []); const preorderIndices = new Map(); const parentRoots: LayerRoot[] = [layerRoot]; - const stack: (InlineLevel | {sentinel: true})[] = box.children.slice().reverse(); + const stack: (InlineLevel | { sentinel: true })[] = box.children + .slice() + .reverse(); const parents: Box[] = []; let preorderIndex = 0; @@ -571,7 +767,7 @@ function createLayerRoot(box: BlockContainer) { const box = stack.pop()!; let layerRoot; - if ('sentinel' in box) { + if ("sentinel" in box) { const layerRoot = parentRoots.at(-1)!; const box = parents.pop()!; @@ -620,7 +816,9 @@ function createLayerRoot(box: BlockContainer) { parentRoot = parentRoots[--parentRootIndex]; } - const parentIndex = parents.findLastIndex(box => parentRoot.box === box); + const parentIndex = parents.findLastIndex( + (box) => parentRoot.box === box + ); const paintRootParents = parents.slice(parentIndex + 1); let nearestParagraph; @@ -635,13 +833,19 @@ function createLayerRoot(box: BlockContainer) { } if (box.isInline()) { - layerRoot = new InlineLayerRoot(box, paintRootParents, nearestParagraph!); + layerRoot = new InlineLayerRoot( + box, + paintRootParents, + nearestParagraph! + ); } else { layerRoot = new BlockLayerRoot(box, paintRootParents); } } else if (!box.isInline()) { - if (box.isFloat() || box.isBlockContainer() && box.isInlineLevel()) { - const parentIndex = parents.findLastIndex(box => parentRoot.box === box); + if (box.isFloat() || (box.isBlockContainer() && box.isInlineLevel())) { + const parentIndex = parents.findLastIndex( + (box) => parentRoot.box === box + ); const paintRootParents = parents.slice(parentIndex + 1); layerRoot = new BlockLayerRoot(box, paintRootParents); if (box.isBlockContainer() && box.isInlineLevel()) { @@ -656,7 +860,7 @@ function createLayerRoot(box: BlockContainer) { box.hasBackground() || box.hasForeground() ) { - stack.push({sentinel: true}); + stack.push({ sentinel: true }); parents.push(box); if (layerRoot) parentRoots.push(layerRoot); if (box.isBlockContainer() || box.isInline()) { @@ -703,10 +907,11 @@ function paintBlockLayerRoot( b: PaintBackend, isRoot = false ) { - if (root.box.hasBackground() && !isRoot) paintFormattingBoxBackground(root.box, b); + if (root.box.hasBackground() && !isRoot) + paintFormattingBoxBackground(root.box, b); - if (!isRoot && root.box.style.overflow === 'hidden') { - const {x, y, width, height} = root.box.getPaddingArea(); + if (!isRoot && root.box.style.overflow === "hidden") { + const { x, y, width, height } = root.box.getPaddingArea(); b.pushClip(x, y, width, height); } @@ -718,7 +923,11 @@ function paintBlockLayerRoot( for (const r of root.floats) paintLayerRoot(r, b); - if (root.box.hasForeground() || root.box.hasForegroundInLayerRoot() || root.inlineBlocks.size) { + if ( + root.box.hasForeground() || + root.box.hasForegroundInLayerRoot() || + root.inlineBlocks.size + ) { paintBlockForeground(root, b); } @@ -726,13 +935,13 @@ function paintBlockLayerRoot( for (const r of root.positiveRoots) paintLayerRoot(r, b); - if (!isRoot && root.box.style.overflow === 'hidden') b.popClip(); + if (!isRoot && root.box.style.overflow === "hidden") b.popClip(); } function paintLayerRoot(paintRoot: LayerRoot, b: PaintBackend) { for (const parent of paintRoot.parents) { - if (parent.isBlockContainer() && parent.style.overflow === 'hidden') { - const {x, y, width, height} = parent.getPaddingArea(); + if (parent.isBlockContainer() && parent.style.overflow === "hidden") { + const { x, y, width, height } = parent.getPaddingArea(); b.pushClip(x, y, width, height); } } @@ -744,7 +953,7 @@ function paintLayerRoot(paintRoot: LayerRoot, b: PaintBackend) { } for (const parent of paintRoot.parents) { - if (parent.isBlockContainer() && parent.style.overflow === 'hidden') { + if (parent.isBlockContainer() && parent.style.overflow === "hidden") { b.popClip(); } } @@ -765,13 +974,674 @@ export default function paint(block: BlockContainer, b: PaintBackend) { b.rect(area.x, area.y, area.width, area.height); } - if (block.style.overflow === 'hidden') { - const {x, y, width, height} = block.containingBlock; + if (block.style.overflow === "hidden") { + const { x, y, width, height } = block.containingBlock; b.pushClip(x, y, width, height); } paintBlockLayerRoot(layerRoot, b, true); - if (block.style.overflow === 'hidden') b.popClip(); + if (block.style.overflow === "hidden") b.popClip(); + } +} + +// Get stroke properties for a border style +function getStrokePropertiesFromBorder(border: BorderInfo): { + strokeWidth: number; + strokeColor: Color; + strokeDasharray?: string; + strokeLinecap?: "butt" | "round" | "square"; +} { + const base = { + strokeWidth: border.width, + strokeColor: border.color, + }; + + switch (border.style) { + case "dashed": + return { + ...base, + strokeDasharray: BORDER_DASHED_ARRAY.map((x) => x * border.width).join( + " " + ), + strokeLinecap: "butt", + }; + case "dotted": + return { + ...base, + strokeDasharray: BORDER_DOTTED_ARRAY.map((x) => x * border.width).join( + " " + ), + strokeLinecap: "round", + }; + case "solid": + case "double": + default: + return { + ...base, + strokeLinecap: "butt", + }; + } +} + +// Check if a border is visible (has width, style, and color alpha > 0) +function isBorderVisible(border: BorderInfo): boolean { + return ( + border.width > 0 && + border.style !== "none" && + border.style !== "hidden" && + border.color.a > 0 + ); +} + +// Check if two borders match (same width, style, and color) +function bordersMatch(border1: BorderInfo, border2: BorderInfo): boolean { + return ( + border1.width === border2.width && + border1.style === border2.style && + border1.color.r === border2.color.r && + border1.color.g === border2.color.g && + border1.color.b === border2.color.b && + border1.color.a === border2.color.a + ); +} + +// Detect contiguous border segments for optimization +function getBorderSegments(borders: { + left: BorderInfo; + top: BorderInfo; + right: BorderInfo; + bottom: BorderInfo; +}): BoxBorderSegment[] { + const { left, top, right, bottom } = borders; + + // Check for full uniform border + if ( + isBorderVisible(left) && + isBorderVisible(top) && + isBorderVisible(right) && + isBorderVisible(bottom) && + bordersMatch(left, top) && + bordersMatch(top, right) && + bordersMatch(right, bottom) + ) { + return [ + { + firstSide: "left", + left: true, + top: true, + right: true, + bottom: true, + }, + ]; + } + + // Check for three-side matches + const segments: BoxBorderSegment[] = []; + + // Left-Top-Right + if ( + isBorderVisible(left) && + isBorderVisible(top) && + isBorderVisible(right) && + bordersMatch(left, top) && + bordersMatch(top, right) + ) { + segments.push({ + firstSide: "left", + left: true, + top: true, + right: true, + bottom: false, + }); + if (isBorderVisible(bottom)) { + segments.push({ + firstSide: "bottom", + left: false, + top: false, + right: false, + bottom: true, + }); + } + return segments; + } + + // Top-Right-Bottom + if ( + isBorderVisible(top) && + isBorderVisible(right) && + isBorderVisible(bottom) && + bordersMatch(top, right) && + bordersMatch(right, bottom) + ) { + segments.push({ + firstSide: "top", + left: false, + top: true, + right: true, + bottom: true, + }); + if (isBorderVisible(left)) { + segments.push({ + firstSide: "left", + left: true, + top: false, + right: false, + bottom: false, + }); + } + return segments; + } + + // Right-Bottom-Left + if ( + isBorderVisible(right) && + isBorderVisible(bottom) && + isBorderVisible(left) && + bordersMatch(right, bottom) && + bordersMatch(bottom, left) + ) { + segments.push({ + firstSide: "right", + left: true, + top: false, + right: true, + bottom: true, + }); + if (isBorderVisible(top)) { + segments.push({ + firstSide: "top", + left: false, + top: true, + right: false, + bottom: false, + }); + } + return segments; + } + + // Bottom-Left-Top + if ( + isBorderVisible(bottom) && + isBorderVisible(left) && + isBorderVisible(top) && + bordersMatch(bottom, left) && + bordersMatch(left, top) + ) { + segments.push({ + firstSide: "bottom", + left: true, + top: true, + right: false, + bottom: true, + }); + if (isBorderVisible(right)) { + segments.push({ + firstSide: "right", + left: false, + top: false, + right: true, + bottom: false, + }); + } + return segments; + } + + // Check for two-side matches + // Left-Top + if ( + isBorderVisible(left) && + isBorderVisible(top) && + bordersMatch(left, top) + ) { + segments.push({ + firstSide: "left", + left: true, + top: true, + right: false, + bottom: false, + }); + } else { + if (isBorderVisible(left)) { + segments.push({ + firstSide: "left", + left: true, + top: false, + right: false, + bottom: false, + }); + } + if (isBorderVisible(top)) { + segments.push({ + firstSide: "top", + left: false, + top: true, + right: false, + bottom: false, + }); + } + } + + // Right-Bottom + if ( + isBorderVisible(right) && + isBorderVisible(bottom) && + bordersMatch(right, bottom) + ) { + segments.push({ + firstSide: "right", + left: false, + top: false, + right: true, + bottom: true, + }); + } else { + if (isBorderVisible(right)) { + segments.push({ + firstSide: "right", + left: false, + top: false, + right: true, + bottom: false, + }); + } + if (isBorderVisible(bottom)) { + segments.push({ + firstSide: "bottom", + left: false, + top: false, + right: false, + bottom: true, + }); + } } + + return segments; +} + +// Generate SVG path for a border segment with radius support +function generateSegmentPath( + x: number, + y: number, + width: number, + height: number, + radii: { + tlHor: number; + tlVer: number; + trHor: number; + trVer: number; + brHor: number; + brVer: number; + blHor: number; + blVer: number; + }, + segment: BoxBorderSegment +): string { + let path = ""; + let started = false; + + const addLineOrMoveTo = (targetX: number, targetY: number) => { + if (!started) { + path += `M ${targetX} ${targetY}`; + started = true; + } else { + path += ` L ${targetX} ${targetY}`; + } + }; + + const addArcTo = ( + rx: number, + ry: number, + targetX: number, + targetY: number + ) => { + if (rx > 0 && ry > 0) { + path += ` A ${rx} ${ry} 0 0 1 ${targetX} ${targetY}`; + } else { + addLineOrMoveTo(targetX, targetY); + } + }; + + // Handle each side of the segment + if (segment.left) { + // Left side: bottom-left corner to top-left corner + if (segment.bottom) { + // Start from bottom-left corner (after radius) + addLineOrMoveTo(x, y + height - radii.blVer); + } else { + // Start from bottom of left side + addLineOrMoveTo(x, y + height); + } + + // Draw to top-left corner (before radius) + if (radii.tlHor > 0 || radii.tlVer > 0) { + addLineOrMoveTo(x, y + radii.tlVer); + if (segment.top) { + // Draw top-left arc + addArcTo(radii.tlHor, radii.tlVer, x + radii.tlHor, y); + } + } else { + addLineOrMoveTo(x, y); + if (segment.top) { + addLineOrMoveTo(x + radii.tlHor, y); + } + } + } + + if (segment.top) { + // Top side: top-left corner to top-right corner + if (!segment.left) { + // Start from left end of top side + addLineOrMoveTo(x, y); + } + + // Draw to top-right corner (before radius) + if (radii.trHor > 0 || radii.trVer > 0) { + addLineOrMoveTo(x + width - radii.trHor, y); + if (segment.right) { + // Draw top-right arc + addArcTo(radii.trHor, radii.trVer, x + width, y + radii.trVer); + } + } else { + addLineOrMoveTo(x + width, y); + if (segment.right) { + addLineOrMoveTo(x + width, y + radii.trVer); + } + } + } + + if (segment.right) { + // Right side: top-right corner to bottom-right corner + if (!segment.top) { + // Start from top of right side + addLineOrMoveTo(x + width, y); + } + + // Draw to bottom-right corner (before radius) + if (radii.brHor > 0 || radii.brVer > 0) { + addLineOrMoveTo(x + width, y + height - radii.brVer); + if (segment.bottom) { + // Draw bottom-right arc + addArcTo(radii.brHor, radii.brVer, x + width - radii.brHor, y + height); + } + } else { + addLineOrMoveTo(x + width, y + height); + if (segment.bottom) { + addLineOrMoveTo(x + width - radii.brHor, y + height); + } + } + } + + if (segment.bottom) { + // Bottom side: bottom-right corner to bottom-left corner + if (!segment.right) { + // Start from right end of bottom side + addLineOrMoveTo(x + width, y + height); + } + + // Draw to bottom-left corner (before radius) + if (radii.blHor > 0 || radii.blVer > 0) { + addLineOrMoveTo(x + radii.blHor, y + height); + if (segment.left) { + // Draw bottom-left arc + addArcTo(radii.blHor, radii.blVer, x, y + height - radii.blVer); + } + } else { + addLineOrMoveTo(x, y + height); + if (segment.left) { + addLineOrMoveTo(x, y + height - radii.blVer); + } + } + } + + // Close path only if the segment includes all four sides + if (segment.left && segment.top && segment.right && segment.bottom) { + path += " Z"; + } + + return path; +} + +// Generate border path with radius support +function generateBorderPath( + x: number, + y: number, + width: number, + height: number, + borders: { + left: BorderInfo; + top: BorderInfo; + right: BorderInfo; + bottom: BorderInfo; + }, + borderRadii: { + topLeft: { horizontal: number; vertical: number }; + topRight: { horizontal: number; vertical: number }; + bottomRight: { horizontal: number; vertical: number }; + bottomLeft: { horizontal: number; vertical: number }; + }, + segment: BoxBorderSegment, + insetFactor: number = 1 +): string { + // Calculate geometry based on inset factor + const calculateGeometry = (factor: number) => { + const leftInset = (borders.left.width * factor) / 2; + const topInset = (borders.top.width * factor) / 2; + const rightInset = (borders.right.width * factor) / 2; + const bottomInset = (borders.bottom.width * factor) / 2; + + const adjustedX = x + leftInset; + const adjustedY = y + topInset; + const adjustedWidth = width - leftInset - rightInset; + const adjustedHeight = height - topInset - bottomInset; + + // Scale border radii proportionally with proper overlap handling + // Only apply corner radii when both adjacent borders are present in the segment + let tlHor = + segment.left && segment.top + ? Math.max(0, borderRadii.topLeft.horizontal - leftInset) + : 0; + let tlVer = + segment.left && segment.top + ? Math.max(0, borderRadii.topLeft.vertical - topInset) + : 0; + let trHor = + segment.top && segment.right + ? Math.max(0, borderRadii.topRight.horizontal - rightInset) + : 0; + let trVer = + segment.top && segment.right + ? Math.max(0, borderRadii.topRight.vertical - topInset) + : 0; + let brHor = + segment.right && segment.bottom + ? Math.max(0, borderRadii.bottomRight.horizontal - rightInset) + : 0; + let brVer = + segment.right && segment.bottom + ? Math.max(0, borderRadii.bottomRight.vertical - bottomInset) + : 0; + let blHor = + segment.bottom && segment.left + ? Math.max(0, borderRadii.bottomLeft.horizontal - leftInset) + : 0; + let blVer = + segment.bottom && segment.left + ? Math.max(0, borderRadii.bottomLeft.vertical - bottomInset) + : 0; + + // Adjust radii ratios if they overlap (like in reference implementation) + const topRatio = adjustedWidth / (tlHor + trHor || 1); + const rightRatio = adjustedHeight / (trVer + brVer || 1); + const bottomRatio = adjustedWidth / (blHor + brHor || 1); + const leftRatio = adjustedHeight / (blVer + tlVer || 1); + const smallestRatio = Math.min( + topRatio, + rightRatio, + bottomRatio, + leftRatio + ); + + if (smallestRatio < 1) { + tlHor *= smallestRatio; + tlVer *= smallestRatio; + trHor *= smallestRatio; + trVer *= smallestRatio; + brHor *= smallestRatio; + brVer *= smallestRatio; + blHor *= smallestRatio; + blVer *= smallestRatio; + } + + return { + x: adjustedX, + y: adjustedY, + width: adjustedWidth, + height: adjustedHeight, + radii: { + tlHor, + tlVer, + trHor, + trVer, + brHor, + brVer, + blHor, + blVer, + }, + }; + }; + + const geometry = calculateGeometry(insetFactor); + return generateSegmentPath( + geometry.x, + geometry.y, + geometry.width, + geometry.height, + geometry.radii, + segment + ); +} + +// Generate path for double borders with proper positioning +function generateBorderPathForDouble( + x: number, + y: number, + width: number, + height: number, + borders: { + left: BorderInfo; + top: BorderInfo; + right: BorderInfo; + bottom: BorderInfo; + }, + borderRadii: { + topLeft: { horizontal: number; vertical: number }; + topRight: { horizontal: number; vertical: number }; + bottomRight: { horizontal: number; vertical: number }; + bottomLeft: { horizontal: number; vertical: number }; + }, + segment: BoxBorderSegment, + position: "outer" | "inner" +): string { + // For double borders, each line is 1/3 of the total border width + // Gap between lines is also 1/3 of the total border width + // Outer offset: strokeWidth/2 (centers stroke on border edge) + // Inner offset: strokeWidth/2 + gapWidth + strokeWidth/2 = 1.5 * strokeWidth + + const totalWidth = borders[segment.firstSide].width; + const strokeWidth = totalWidth > 0 ? totalWidth / 3 : 0; + const gapWidth = strokeWidth; + + let insetFactor: number; + if (position === "outer") { + // Outer border: inset by strokeWidth/2 to center on border edge + insetFactor = strokeWidth / 2 / totalWidth; + } else { + // Inner border: inset by strokeWidth/2 + gapWidth + strokeWidth/2 + insetFactor = (strokeWidth / 2 + gapWidth + strokeWidth / 2) / totalWidth; + } + + // Calculate per-side insets using the inset factor + const leftInset = borders.left.width * insetFactor; + const topInset = borders.top.width * insetFactor; + const rightInset = borders.right.width * insetFactor; + const bottomInset = borders.bottom.width * insetFactor; + + const adjustedX = x + leftInset; + const adjustedY = y + topInset; + const adjustedWidth = width - leftInset - rightInset; + const adjustedHeight = height - topInset - bottomInset; + + // Scale border radii proportionally with proper overlap handling + // Only apply corner radii when both adjacent borders are present in the segment + let tlHor = + segment.left && segment.top + ? Math.max(0, borderRadii.topLeft.horizontal - leftInset) + : 0; + let tlVer = + segment.left && segment.top + ? Math.max(0, borderRadii.topLeft.vertical - topInset) + : 0; + let trHor = + segment.top && segment.right + ? Math.max(0, borderRadii.topRight.horizontal - rightInset) + : 0; + let trVer = + segment.top && segment.right + ? Math.max(0, borderRadii.topRight.vertical - topInset) + : 0; + let brHor = + segment.right && segment.bottom + ? Math.max(0, borderRadii.bottomRight.horizontal - rightInset) + : 0; + let brVer = + segment.right && segment.bottom + ? Math.max(0, borderRadii.bottomRight.vertical - bottomInset) + : 0; + let blHor = + segment.bottom && segment.left + ? Math.max(0, borderRadii.bottomLeft.horizontal - leftInset) + : 0; + let blVer = + segment.bottom && segment.left + ? Math.max(0, borderRadii.bottomLeft.vertical - bottomInset) + : 0; + + // Adjust radii ratios if they overlap (like in reference implementation) + const topRatio = adjustedWidth / (tlHor + trHor || 1); + const rightRatio = adjustedHeight / (trVer + brVer || 1); + const bottomRatio = adjustedWidth / (blHor + brHor || 1); + const leftRatio = adjustedHeight / (blVer + tlVer || 1); + const smallestRatio = Math.min(topRatio, rightRatio, bottomRatio, leftRatio); + + if (smallestRatio < 1) { + tlHor *= smallestRatio; + tlVer *= smallestRatio; + trHor *= smallestRatio; + trVer *= smallestRatio; + brHor *= smallestRatio; + brVer *= smallestRatio; + blHor *= smallestRatio; + blVer *= smallestRatio; + } + + const adjustedRadii = { + tlHor, + tlVer, + trHor, + trVer, + brHor, + brVer, + blHor, + blVer, + }; + + return generateSegmentPath( + adjustedX, + adjustedY, + adjustedWidth, + adjustedHeight, + adjustedRadii, + segment + ); } diff --git a/src/parse-css.js b/src/parse-css.js index d701c5c..f79e0c3 100644 --- a/src/parse-css.js +++ b/src/parse-css.js @@ -387,83 +387,88 @@ function peg$parse(input, options) { var peg$c131 = "border-bottom-color"; var peg$c132 = "border-left-color"; var peg$c133 = "border-color"; - var peg$c134 = "-top"; - var peg$c135 = "-right"; - var peg$c136 = "-bottom"; - var peg$c137 = "-left"; - var peg$c138 = "border"; - var peg$c139 = "background-color"; - var peg$c140 = "background-clip"; - var peg$c141 = "border-box"; - var peg$c142 = "content-box"; - var peg$c143 = "padding-box"; - var peg$c144 = "text-align"; - var peg$c145 = "start"; - var peg$c146 = "end"; - var peg$c147 = "left"; - var peg$c148 = "right"; - var peg$c149 = "center"; - var peg$c150 = "float"; - var peg$c151 = "clear"; - var peg$c152 = "both"; - var peg$c153 = "z-index"; - var peg$c154 = "word-break"; - var peg$c155 = "break-word"; - var peg$c156 = "overflow-wrap"; - var peg$c157 = "word-wrap"; - var peg$c158 = "anywhere"; - var peg$c159 = "overflow"; - var peg$c160 = "visible"; - var peg$c161 = "zoom"; - var peg$c162 = "width"; - var peg$c163 = "height"; - var peg$c164 = "box-sizing"; - var peg$c165 = "\\"; - var peg$c166 = "\r\n"; - var peg$c167 = "\""; - var peg$c168 = "'"; - var peg$c169 = "/*"; - var peg$c170 = "*"; - var peg$c171 = "-"; - var peg$c172 = "e"; - var peg$c173 = "\n"; - var peg$c174 = "\r"; - var peg$c175 = "\f"; - var peg$c176 = "a"; - var peg$c177 = "0"; - var peg$c178 = "c"; - var peg$c179 = "d"; - var peg$c180 = "g"; - var peg$c181 = "\\g"; - var peg$c182 = "h"; - var peg$c183 = "\\h"; - var peg$c184 = "i"; - var peg$c185 = "\\i"; - var peg$c186 = "k"; - var peg$c187 = "\\k"; - var peg$c188 = "l"; - var peg$c189 = "\\l"; - var peg$c190 = "m"; - var peg$c191 = "\\m"; - var peg$c192 = "n"; - var peg$c193 = "\\n"; - var peg$c194 = "o"; - var peg$c195 = "\\o"; - var peg$c196 = "p"; - var peg$c197 = "\\p"; - var peg$c198 = "r"; - var peg$c199 = "\\r"; - var peg$c200 = "s"; - var peg$c201 = "\\s"; - var peg$c202 = "t"; - var peg$c203 = "\\t"; - var peg$c204 = "u"; - var peg$c205 = "\\u"; - var peg$c206 = "x"; - var peg$c207 = "\\x"; - var peg$c208 = "z"; - var peg$c209 = "\\z"; - var peg$c210 = "("; + var peg$c134 = "border-top-left-radius"; + var peg$c135 = "border-top-right-radius"; + var peg$c136 = "border-bottom-right-radius"; + var peg$c137 = "border-bottom-left-radius"; + var peg$c138 = "border-radius"; + var peg$c139 = "-top"; + var peg$c140 = "-right"; + var peg$c141 = "-bottom"; + var peg$c142 = "-left"; + var peg$c143 = "border"; + var peg$c144 = "background-color"; + var peg$c145 = "background-clip"; + var peg$c146 = "border-box"; + var peg$c147 = "content-box"; + var peg$c148 = "padding-box"; + var peg$c149 = "text-align"; + var peg$c150 = "start"; + var peg$c151 = "end"; + var peg$c152 = "left"; + var peg$c153 = "right"; + var peg$c154 = "center"; + var peg$c155 = "float"; + var peg$c156 = "clear"; + var peg$c157 = "both"; + var peg$c158 = "z-index"; + var peg$c159 = "word-break"; + var peg$c160 = "break-word"; + var peg$c161 = "overflow-wrap"; + var peg$c162 = "word-wrap"; + var peg$c163 = "anywhere"; + var peg$c164 = "overflow"; + var peg$c165 = "visible"; + var peg$c166 = "zoom"; + var peg$c167 = "width"; + var peg$c168 = "height"; + var peg$c169 = "box-sizing"; + var peg$c170 = "\\"; + var peg$c171 = "\r\n"; + var peg$c172 = "\""; + var peg$c173 = "'"; + var peg$c174 = "/*"; + var peg$c175 = "*"; + var peg$c176 = "-"; + var peg$c177 = "e"; + var peg$c178 = "\n"; + var peg$c179 = "\r"; + var peg$c180 = "\f"; + var peg$c181 = "a"; + var peg$c182 = "0"; + var peg$c183 = "c"; + var peg$c184 = "d"; + var peg$c185 = "g"; + var peg$c186 = "\\g"; + var peg$c187 = "h"; + var peg$c188 = "\\h"; + var peg$c189 = "i"; + var peg$c190 = "\\i"; + var peg$c191 = "k"; + var peg$c192 = "\\k"; + var peg$c193 = "l"; + var peg$c194 = "\\l"; + var peg$c195 = "m"; + var peg$c196 = "\\m"; + var peg$c197 = "n"; + var peg$c198 = "\\n"; + var peg$c199 = "o"; + var peg$c200 = "\\o"; + var peg$c201 = "p"; + var peg$c202 = "\\p"; + var peg$c203 = "r"; + var peg$c204 = "\\r"; + var peg$c205 = "s"; + var peg$c206 = "\\s"; + var peg$c207 = "t"; + var peg$c208 = "\\t"; + var peg$c209 = "u"; + var peg$c210 = "\\u"; + var peg$c211 = "x"; + var peg$c212 = "\\x"; + var peg$c213 = "z"; + var peg$c214 = "\\z"; + var peg$c215 = "("; var peg$r0 = /^[0-9]/; var peg$r1 = /^[0-4]/; @@ -641,132 +646,137 @@ function peg$parse(input, options) { var peg$e136 = peg$literalExpectation("border-bottom-color", true); var peg$e137 = peg$literalExpectation("border-left-color", true); var peg$e138 = peg$literalExpectation("border-color", true); - var peg$e139 = peg$literalExpectation("-top", false); - var peg$e140 = peg$literalExpectation("-right", false); - var peg$e141 = peg$literalExpectation("-bottom", false); - var peg$e142 = peg$literalExpectation("-left", false); - var peg$e143 = peg$literalExpectation("border", true); - var peg$e144 = peg$literalExpectation("background-color", true); - var peg$e145 = peg$literalExpectation("background-clip", true); - var peg$e146 = peg$literalExpectation("border-box", false); - var peg$e147 = peg$literalExpectation("content-box", false); - var peg$e148 = peg$literalExpectation("padding-box", false); - var peg$e149 = peg$literalExpectation("text-align", true); - var peg$e150 = peg$literalExpectation("start", false); - var peg$e151 = peg$literalExpectation("end", false); - var peg$e152 = peg$literalExpectation("left", false); - var peg$e153 = peg$literalExpectation("right", false); - var peg$e154 = peg$literalExpectation("center", false); - var peg$e155 = peg$literalExpectation("float", true); - var peg$e156 = peg$literalExpectation("clear", true); - var peg$e157 = peg$literalExpectation("both", false); - var peg$e158 = peg$literalExpectation("z-index", true); - var peg$e159 = peg$literalExpectation("word-break", true); - var peg$e160 = peg$literalExpectation("break-word", false); - var peg$e161 = peg$literalExpectation("overflow-wrap", true); - var peg$e162 = peg$literalExpectation("word-wrap", true); - var peg$e163 = peg$literalExpectation("anywhere", false); - var peg$e164 = peg$literalExpectation("overflow", true); - var peg$e165 = peg$literalExpectation("visible", false); - var peg$e166 = peg$literalExpectation("zoom", true); - var peg$e167 = peg$literalExpectation("width", true); - var peg$e168 = peg$literalExpectation("height", true); - var peg$e169 = peg$literalExpectation("top", true); - var peg$e170 = peg$literalExpectation("right", true); - var peg$e171 = peg$literalExpectation("bottom", true); - var peg$e172 = peg$literalExpectation("left", true); - var peg$e173 = peg$literalExpectation("box-sizing", true); - var peg$e174 = peg$classExpectation([["0", "9"], ["a", "f"]], false, true); - var peg$e175 = peg$classExpectation([["\x80", "\uFFFF"]], false, false); - var peg$e176 = peg$literalExpectation("\\", false); - var peg$e177 = peg$literalExpectation("\r\n", false); - var peg$e178 = peg$classExpectation([" ", "\t", "\r", "\n", "\f"], false, false); - var peg$e179 = peg$classExpectation(["\r", "\n", "\f", ["0", "9"], ["a", "f"]], true, true); - var peg$e180 = peg$classExpectation(["_", ["a", "z"]], false, true); - var peg$e181 = peg$classExpectation(["_", ["a", "z"], ["0", "9"], "-"], false, true); - var peg$e182 = peg$literalExpectation("\"", false); - var peg$e183 = peg$classExpectation(["\n", "\r", "\f", "\\", "\""], true, false); - var peg$e184 = peg$literalExpectation("'", false); - var peg$e185 = peg$classExpectation(["\n", "\r", "\f", "\\", "'"], true, false); - var peg$e186 = peg$literalExpectation("/*", false); - var peg$e187 = peg$classExpectation(["*"], true, false); - var peg$e188 = peg$literalExpectation("*", false); - var peg$e189 = peg$classExpectation(["/", "*"], true, false); - var peg$e190 = peg$literalExpectation("-", false); - var peg$e191 = peg$classExpectation(["+", "-"], false, false); - var peg$e192 = peg$literalExpectation("e", false); - var peg$e193 = peg$classExpectation(["!", "#", "$", "%", "&", ["*", "["], ["]", "~"]], false, false); - var peg$e194 = peg$literalExpectation("\n", false); - var peg$e195 = peg$literalExpectation("\r", false); - var peg$e196 = peg$literalExpectation("\f", false); - var peg$e197 = peg$literalExpectation("a", true); - var peg$e198 = peg$literalExpectation("0", false); - var peg$e199 = peg$classExpectation(["A", "a"], false, false); - var peg$e200 = peg$literalExpectation("c", true); - var peg$e201 = peg$classExpectation(["C", "c"], false, false); - var peg$e202 = peg$literalExpectation("d", true); - var peg$e203 = peg$classExpectation(["D", "d"], false, false); - var peg$e204 = peg$literalExpectation("e", true); - var peg$e205 = peg$classExpectation(["E", "e"], false, false); - var peg$e206 = peg$literalExpectation("g", true); - var peg$e207 = peg$classExpectation(["G", "g"], false, false); - var peg$e208 = peg$literalExpectation("\\g", true); - var peg$e209 = peg$literalExpectation("h", true); - var peg$e210 = peg$classExpectation(["H", "h"], false, false); - var peg$e211 = peg$literalExpectation("\\h", true); - var peg$e212 = peg$literalExpectation("i", true); - var peg$e213 = peg$classExpectation(["I", "i"], false, false); - var peg$e214 = peg$literalExpectation("\\i", true); - var peg$e215 = peg$literalExpectation("k", true); - var peg$e216 = peg$classExpectation(["K", "k"], false, false); - var peg$e217 = peg$literalExpectation("\\k", true); - var peg$e218 = peg$literalExpectation("l", true); - var peg$e219 = peg$classExpectation(["L", "l"], false, false); - var peg$e220 = peg$literalExpectation("\\l", true); - var peg$e221 = peg$literalExpectation("m", true); - var peg$e222 = peg$classExpectation(["M", "m"], false, false); - var peg$e223 = peg$literalExpectation("\\m", true); - var peg$e224 = peg$literalExpectation("n", true); - var peg$e225 = peg$classExpectation(["N", "n"], false, false); - var peg$e226 = peg$literalExpectation("\\n", true); - var peg$e227 = peg$literalExpectation("o", true); - var peg$e228 = peg$classExpectation(["O", "o"], false, false); - var peg$e229 = peg$literalExpectation("\\o", true); - var peg$e230 = peg$literalExpectation("p", true); - var peg$e231 = peg$classExpectation(["P", "p"], false, false); - var peg$e232 = peg$literalExpectation("\\p", true); - var peg$e233 = peg$literalExpectation("r", true); - var peg$e234 = peg$classExpectation(["R", "r"], false, false); - var peg$e235 = peg$literalExpectation("\\r", true); - var peg$e236 = peg$literalExpectation("s", true); - var peg$e237 = peg$classExpectation(["S", "s"], false, false); - var peg$e238 = peg$literalExpectation("\\s", true); - var peg$e239 = peg$literalExpectation("t", true); - var peg$e240 = peg$classExpectation(["T", "t"], false, false); - var peg$e241 = peg$literalExpectation("\\t", true); - var peg$e242 = peg$literalExpectation("u", true); - var peg$e243 = peg$classExpectation(["U", "u"], false, false); - var peg$e244 = peg$literalExpectation("\\u", true); - var peg$e245 = peg$literalExpectation("x", true); - var peg$e246 = peg$classExpectation(["X", "x"], false, false); - var peg$e247 = peg$literalExpectation("\\x", true); - var peg$e248 = peg$literalExpectation("z", true); - var peg$e249 = peg$classExpectation(["Z", "z"], false, false); - var peg$e250 = peg$literalExpectation("\\z", true); - var peg$e251 = peg$otherExpectation("whitespace"); - var peg$e252 = peg$otherExpectation("string"); - var peg$e253 = peg$otherExpectation("identifier"); - var peg$e254 = peg$otherExpectation("hash"); - var peg$e255 = peg$otherExpectation("length"); - var peg$e256 = peg$otherExpectation("angle"); - var peg$e257 = peg$otherExpectation("time"); - var peg$e258 = peg$otherExpectation("frequency"); - var peg$e259 = peg$otherExpectation("percentage"); - var peg$e260 = peg$otherExpectation("number"); - var peg$e261 = peg$otherExpectation("uri"); - var peg$e262 = peg$literalExpectation("(", true); - var peg$e263 = peg$otherExpectation("function"); - var peg$e264 = peg$literalExpectation("(", false); + var peg$e139 = peg$literalExpectation("border-top-left-radius", true); + var peg$e140 = peg$literalExpectation("border-top-right-radius", true); + var peg$e141 = peg$literalExpectation("border-bottom-right-radius", true); + var peg$e142 = peg$literalExpectation("border-bottom-left-radius", true); + var peg$e143 = peg$literalExpectation("border-radius", true); + var peg$e144 = peg$literalExpectation("-top", false); + var peg$e145 = peg$literalExpectation("-right", false); + var peg$e146 = peg$literalExpectation("-bottom", false); + var peg$e147 = peg$literalExpectation("-left", false); + var peg$e148 = peg$literalExpectation("border", true); + var peg$e149 = peg$literalExpectation("background-color", true); + var peg$e150 = peg$literalExpectation("background-clip", true); + var peg$e151 = peg$literalExpectation("border-box", false); + var peg$e152 = peg$literalExpectation("content-box", false); + var peg$e153 = peg$literalExpectation("padding-box", false); + var peg$e154 = peg$literalExpectation("text-align", true); + var peg$e155 = peg$literalExpectation("start", false); + var peg$e156 = peg$literalExpectation("end", false); + var peg$e157 = peg$literalExpectation("left", false); + var peg$e158 = peg$literalExpectation("right", false); + var peg$e159 = peg$literalExpectation("center", false); + var peg$e160 = peg$literalExpectation("float", true); + var peg$e161 = peg$literalExpectation("clear", true); + var peg$e162 = peg$literalExpectation("both", false); + var peg$e163 = peg$literalExpectation("z-index", true); + var peg$e164 = peg$literalExpectation("word-break", true); + var peg$e165 = peg$literalExpectation("break-word", false); + var peg$e166 = peg$literalExpectation("overflow-wrap", true); + var peg$e167 = peg$literalExpectation("word-wrap", true); + var peg$e168 = peg$literalExpectation("anywhere", false); + var peg$e169 = peg$literalExpectation("overflow", true); + var peg$e170 = peg$literalExpectation("visible", false); + var peg$e171 = peg$literalExpectation("zoom", true); + var peg$e172 = peg$literalExpectation("width", true); + var peg$e173 = peg$literalExpectation("height", true); + var peg$e174 = peg$literalExpectation("top", true); + var peg$e175 = peg$literalExpectation("right", true); + var peg$e176 = peg$literalExpectation("bottom", true); + var peg$e177 = peg$literalExpectation("left", true); + var peg$e178 = peg$literalExpectation("box-sizing", true); + var peg$e179 = peg$classExpectation([["0", "9"], ["a", "f"]], false, true); + var peg$e180 = peg$classExpectation([["\x80", "\uFFFF"]], false, false); + var peg$e181 = peg$literalExpectation("\\", false); + var peg$e182 = peg$literalExpectation("\r\n", false); + var peg$e183 = peg$classExpectation([" ", "\t", "\r", "\n", "\f"], false, false); + var peg$e184 = peg$classExpectation(["\r", "\n", "\f", ["0", "9"], ["a", "f"]], true, true); + var peg$e185 = peg$classExpectation(["_", ["a", "z"]], false, true); + var peg$e186 = peg$classExpectation(["_", ["a", "z"], ["0", "9"], "-"], false, true); + var peg$e187 = peg$literalExpectation("\"", false); + var peg$e188 = peg$classExpectation(["\n", "\r", "\f", "\\", "\""], true, false); + var peg$e189 = peg$literalExpectation("'", false); + var peg$e190 = peg$classExpectation(["\n", "\r", "\f", "\\", "'"], true, false); + var peg$e191 = peg$literalExpectation("/*", false); + var peg$e192 = peg$classExpectation(["*"], true, false); + var peg$e193 = peg$literalExpectation("*", false); + var peg$e194 = peg$classExpectation(["/", "*"], true, false); + var peg$e195 = peg$literalExpectation("-", false); + var peg$e196 = peg$classExpectation(["+", "-"], false, false); + var peg$e197 = peg$literalExpectation("e", false); + var peg$e198 = peg$classExpectation(["!", "#", "$", "%", "&", ["*", "["], ["]", "~"]], false, false); + var peg$e199 = peg$literalExpectation("\n", false); + var peg$e200 = peg$literalExpectation("\r", false); + var peg$e201 = peg$literalExpectation("\f", false); + var peg$e202 = peg$literalExpectation("a", true); + var peg$e203 = peg$literalExpectation("0", false); + var peg$e204 = peg$classExpectation(["A", "a"], false, false); + var peg$e205 = peg$literalExpectation("c", true); + var peg$e206 = peg$classExpectation(["C", "c"], false, false); + var peg$e207 = peg$literalExpectation("d", true); + var peg$e208 = peg$classExpectation(["D", "d"], false, false); + var peg$e209 = peg$literalExpectation("e", true); + var peg$e210 = peg$classExpectation(["E", "e"], false, false); + var peg$e211 = peg$literalExpectation("g", true); + var peg$e212 = peg$classExpectation(["G", "g"], false, false); + var peg$e213 = peg$literalExpectation("\\g", true); + var peg$e214 = peg$literalExpectation("h", true); + var peg$e215 = peg$classExpectation(["H", "h"], false, false); + var peg$e216 = peg$literalExpectation("\\h", true); + var peg$e217 = peg$literalExpectation("i", true); + var peg$e218 = peg$classExpectation(["I", "i"], false, false); + var peg$e219 = peg$literalExpectation("\\i", true); + var peg$e220 = peg$literalExpectation("k", true); + var peg$e221 = peg$classExpectation(["K", "k"], false, false); + var peg$e222 = peg$literalExpectation("\\k", true); + var peg$e223 = peg$literalExpectation("l", true); + var peg$e224 = peg$classExpectation(["L", "l"], false, false); + var peg$e225 = peg$literalExpectation("\\l", true); + var peg$e226 = peg$literalExpectation("m", true); + var peg$e227 = peg$classExpectation(["M", "m"], false, false); + var peg$e228 = peg$literalExpectation("\\m", true); + var peg$e229 = peg$literalExpectation("n", true); + var peg$e230 = peg$classExpectation(["N", "n"], false, false); + var peg$e231 = peg$literalExpectation("\\n", true); + var peg$e232 = peg$literalExpectation("o", true); + var peg$e233 = peg$classExpectation(["O", "o"], false, false); + var peg$e234 = peg$literalExpectation("\\o", true); + var peg$e235 = peg$literalExpectation("p", true); + var peg$e236 = peg$classExpectation(["P", "p"], false, false); + var peg$e237 = peg$literalExpectation("\\p", true); + var peg$e238 = peg$literalExpectation("r", true); + var peg$e239 = peg$classExpectation(["R", "r"], false, false); + var peg$e240 = peg$literalExpectation("\\r", true); + var peg$e241 = peg$literalExpectation("s", true); + var peg$e242 = peg$classExpectation(["S", "s"], false, false); + var peg$e243 = peg$literalExpectation("\\s", true); + var peg$e244 = peg$literalExpectation("t", true); + var peg$e245 = peg$classExpectation(["T", "t"], false, false); + var peg$e246 = peg$literalExpectation("\\t", true); + var peg$e247 = peg$literalExpectation("u", true); + var peg$e248 = peg$classExpectation(["U", "u"], false, false); + var peg$e249 = peg$literalExpectation("\\u", true); + var peg$e250 = peg$literalExpectation("x", true); + var peg$e251 = peg$classExpectation(["X", "x"], false, false); + var peg$e252 = peg$literalExpectation("\\x", true); + var peg$e253 = peg$literalExpectation("z", true); + var peg$e254 = peg$classExpectation(["Z", "z"], false, false); + var peg$e255 = peg$literalExpectation("\\z", true); + var peg$e256 = peg$otherExpectation("whitespace"); + var peg$e257 = peg$otherExpectation("string"); + var peg$e258 = peg$otherExpectation("identifier"); + var peg$e259 = peg$otherExpectation("hash"); + var peg$e260 = peg$otherExpectation("length"); + var peg$e261 = peg$otherExpectation("angle"); + var peg$e262 = peg$otherExpectation("time"); + var peg$e263 = peg$otherExpectation("frequency"); + var peg$e264 = peg$otherExpectation("percentage"); + var peg$e265 = peg$otherExpectation("number"); + var peg$e266 = peg$otherExpectation("uri"); + var peg$e267 = peg$literalExpectation("(", true); + var peg$e268 = peg$otherExpectation("function"); + var peg$e269 = peg$literalExpectation("(", false); var peg$f0 = function(declarationsHead, declarationsTail) { return combine(buildList(declarationsHead, declarationsTail, 2)); }; var peg$f1 = function(name, value) { @@ -1093,178 +1103,220 @@ function peg$parse(input, options) { var peg$f107 = function(s) { return setTopRightBottomLeft({}, 'border', 'Color', s, s, s, s); }; - var peg$f108 = function(t, w, s, c) { + var peg$f108 = function(h, v) { + return {borderTopLeftRadius: {horizontal: h, vertical: v}}; + }; + var peg$f109 = function(r) { + return {borderTopLeftRadius: r}; + }; + var peg$f110 = function(h, v) { + return {borderTopRightRadius: {horizontal: h, vertical: v}}; + }; + var peg$f111 = function(r) { + return {borderTopRightRadius: r}; + }; + var peg$f112 = function(h, v) { + return {borderBottomRightRadius: {horizontal: h, vertical: v}}; + }; + var peg$f113 = function(r) { + return {borderBottomRightRadius: r}; + }; + var peg$f114 = function(h, v) { + return {borderBottomLeftRadius: {horizontal: h, vertical: v}}; + }; + var peg$f115 = function(r) { + return {borderBottomLeftRadius: r}; + }; + var peg$f116 = function(tl, tr, br, bl) { + return { + borderTopLeftRadius: tl, + borderTopRightRadius: tr, + borderBottomRightRadius: br, + borderBottomLeftRadius: bl + }; + }; + var peg$f117 = function(t, h, b) { + return { + borderTopLeftRadius: t, + borderTopRightRadius: h, + borderBottomRightRadius: b, + borderBottomLeftRadius: h + }; + }; + var peg$f118 = function(v, h) { + return { + borderTopLeftRadius: v, + borderTopRightRadius: h, + borderBottomRightRadius: v, + borderBottomLeftRadius: h + }; + }; + var peg$f119 = function(r) { + return { + borderTopLeftRadius: r, + borderTopRightRadius: r, + borderBottomRightRadius: r, + borderBottomLeftRadius: r + }; + }; + var peg$f120 = function(t, w, s, c) { const ret = {}; setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); if (c) setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); return ret; }; - var peg$f109 = function(t, s, w, c) { + var peg$f121 = function(t, s, w, c) { const ret = {}; setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); if (c) setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); return ret; }; - var peg$f110 = function(t, w, c, s) { + var peg$f122 = function(t, w, c, s) { const ret = {}; setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); if (s) setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); return ret; }; - var peg$f111 = function(t, c, w, s) { + var peg$f123 = function(t, c, w, s) { const ret = {}; setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); if (s) setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); return ret; }; - var peg$f112 = function(t, c, s, w) { - const ret = {}; - setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); - setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); - if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); - return ret; - }; - var peg$f113 = function(t, s, c, w) { - const ret = {}; - setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); - setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); - if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); - return ret; - }; - var peg$f114 = function(t, w) { + var peg$f124 = function(t, c, w) { return setTopRightBottomLeftOr(t, {}, 'border', 'Width', w, w, w, w); }; - var peg$f115 = function(t, c) { + var peg$f125 = function(t, c) { return setTopRightBottomLeftOr(t, {}, 'border', 'Color', c, c, c, c); }; - var peg$f116 = function(t, s) { + var peg$f126 = function(t, s) { return setTopRightBottomLeftOr(t, {}, 'border', 'Style', s, s, s, s); }; - var peg$f117 = function(t, i) { + var peg$f127 = function(t, i) { const ret = setTopRightBottomLeftOr(t, {}, 'border', 'Style', i, i, i, i); setTopRightBottomLeftOr(t, ret, 'border', 'Width', i, i, i, i); setTopRightBottomLeftOr(t, ret, 'border', 'Color', i, i, i, i); return ret; }; - var peg$f118 = function(backgroundColor) { + var peg$f128 = function(backgroundColor) { return {backgroundColor}; }; - var peg$f119 = function(backgroundClip) { + var peg$f129 = function(backgroundClip) { return {backgroundClip}; }; - var peg$f120 = function(textAlign) { + var peg$f130 = function(textAlign) { return {textAlign}; }; - var peg$f121 = function(float) { + var peg$f131 = function(float) { return {float}; }; - var peg$f122 = function(clear) { + var peg$f132 = function(clear) { return {clear}; }; - var peg$f123 = function(zIndex) { + var peg$f133 = function(zIndex) { return {zIndex}; }; - var peg$f124 = function(wordBreak) { + var peg$f134 = function(wordBreak) { return {wordBreak}; }; - var peg$f125 = function(overflowWrap) { + var peg$f135 = function(overflowWrap) { return {overflowWrap}; }; - var peg$f126 = function(overflow) { + var peg$f136 = function(overflow) { return {overflow}; }; - var peg$f127 = function(zoom) { + var peg$f137 = function(zoom) { return {zoom}; }; - var peg$f128 = function(width) { + var peg$f138 = function(width) { return {width}; }; - var peg$f129 = function(height) { + var peg$f139 = function(height) { return {height}; }; - var peg$f130 = function(top) { + var peg$f140 = function(top) { return {top}; }; - var peg$f131 = function(right) { + var peg$f141 = function(right) { return {right}; }; - var peg$f132 = function(bottom) { + var peg$f142 = function(bottom) { return {bottom}; }; - var peg$f133 = function(left) { + var peg$f143 = function(left) { return {left}; }; - var peg$f134 = function(boxSizing) { + var peg$f144 = function(boxSizing) { return {boxSizing}; }; - var peg$f135 = function(digits) { + var peg$f145 = function(digits) { return String.fromCharCode(parseInt(digits, 16)); }; - var peg$f136 = function(ch) { return ch; }; - var peg$f137 = function(nl) { return ''; }; - var peg$f138 = function(chars) { + var peg$f146 = function(ch) { return ch; }; + var peg$f147 = function(nl) { return ''; }; + var peg$f148 = function(chars) { return chars.join(''); }; - var peg$f139 = function(nl) { return ''; }; - var peg$f140 = function(chars) { + var peg$f149 = function(nl) { return ''; }; + var peg$f150 = function(chars) { return chars.join(''); }; - var peg$f141 = function(prefix, start, chars) { + var peg$f151 = function(prefix, start, chars) { return prefix + start + chars.join(''); }; - var peg$f142 = function(chars) { return chars.join(''); }; - var peg$f143 = function() { + var peg$f152 = function(chars) { return chars.join(''); }; + var peg$f153 = function() { return parseFloat(text()); }; - var peg$f144 = function(chars) { return chars.join(''); }; - var peg$f145 = function() { return 'a'; }; - var peg$f146 = function() { return 'c'; }; - var peg$f147 = function() { return 'd'; }; - var peg$f148 = function() { return 'e'; }; - var peg$f149 = function() { return 'g'; }; - var peg$f150 = function() { return 'h'; }; - var peg$f151 = function() { return 'i'; }; - var peg$f152 = function() { return 'k'; }; - var peg$f153 = function() { return 'l'; }; - var peg$f154 = function() { return 'm'; }; - var peg$f155 = function() { return 'n'; }; - var peg$f156 = function() { return 'o'; }; - var peg$f157 = function() { return 'p'; }; - var peg$f158 = function() { return 'r'; }; - var peg$f159 = function() { return 's'; }; - var peg$f160 = function() { return 't'; }; - var peg$f161 = function() { return 'u'; }; - var peg$f162 = function() { return 'x'; }; - var peg$f163 = function() { return 'z'; }; - var peg$f164 = function(string) { return string; }; - var peg$f165 = function(ident) { return ident; }; - var peg$f166 = function(name) { return '#' + name; }; - var peg$f167 = function(value) { return { value: value, unit: 'ex' }; }; - var peg$f168 = function(value) { return value; }; - var peg$f169 = function(value) { return { value: value, unit: 'cm' }; }; - var peg$f170 = function(value) { return { value: value, unit: 'mm' }; }; - var peg$f171 = function(value) { return { value: value, unit: 'in' }; }; - var peg$f172 = function(value) { return { value: value, unit: 'pt' }; }; - var peg$f173 = function(value) { return { value: value, unit: 'pc' }; }; - var peg$f174 = function(value) { return { value: value, unit: 'em' }; }; - var peg$f175 = function() { return 0; }; - var peg$f176 = function(value) { return { value: value, unit: 'deg' }; }; - var peg$f177 = function(value) { return { value: value, unit: 'rad' }; }; - var peg$f178 = function(value) { return { value: value, unit: 'grad' }; }; - var peg$f179 = function(value) { return { value: value, unit: 'ms' }; }; - var peg$f180 = function(value) { return { value: value, unit: 's' }; }; - var peg$f181 = function(value) { return { value: value, unit: 'hz' }; }; - var peg$f182 = function(value) { return { value: value, unit: 'kh' }; }; - var peg$f183 = function(value) { return { value: value, unit: '%' }; }; - var peg$f184 = function(value) { return value; }; - var peg$f185 = function(value) { return { value: value, unit: null }; }; - var peg$f186 = function(url) { return url; }; - var peg$f187 = function(url) { return url; }; - var peg$f188 = function(name) { return name; }; + var peg$f154 = function(chars) { return chars.join(''); }; + var peg$f155 = function() { return 'a'; }; + var peg$f156 = function() { return 'c'; }; + var peg$f157 = function() { return 'd'; }; + var peg$f158 = function() { return 'e'; }; + var peg$f159 = function() { return 'g'; }; + var peg$f160 = function() { return 'h'; }; + var peg$f161 = function() { return 'i'; }; + var peg$f162 = function() { return 'k'; }; + var peg$f163 = function() { return 'l'; }; + var peg$f164 = function() { return 'm'; }; + var peg$f165 = function() { return 'n'; }; + var peg$f166 = function() { return 'o'; }; + var peg$f167 = function() { return 'p'; }; + var peg$f168 = function() { return 'r'; }; + var peg$f169 = function() { return 's'; }; + var peg$f170 = function() { return 't'; }; + var peg$f171 = function() { return 'u'; }; + var peg$f172 = function() { return 'x'; }; + var peg$f173 = function() { return 'z'; }; + var peg$f174 = function(string) { return string; }; + var peg$f175 = function(ident) { return ident; }; + var peg$f176 = function(name) { return '#' + name; }; + var peg$f177 = function(value) { return { value: value, unit: 'ex' }; }; + var peg$f178 = function(value) { return value; }; + var peg$f179 = function(value) { return { value: value, unit: 'cm' }; }; + var peg$f180 = function(value) { return { value: value, unit: 'mm' }; }; + var peg$f181 = function(value) { return { value: value, unit: 'in' }; }; + var peg$f182 = function(value) { return { value: value, unit: 'pt' }; }; + var peg$f183 = function(value) { return { value: value, unit: 'pc' }; }; + var peg$f184 = function(value) { return { value: value, unit: 'em' }; }; + var peg$f185 = function() { return 0; }; + var peg$f186 = function(value) { return { value: value, unit: 'deg' }; }; + var peg$f187 = function(value) { return { value: value, unit: 'rad' }; }; + var peg$f188 = function(value) { return { value: value, unit: 'grad' }; }; + var peg$f189 = function(value) { return { value: value, unit: 'ms' }; }; + var peg$f190 = function(value) { return { value: value, unit: 's' }; }; + var peg$f191 = function(value) { return { value: value, unit: 'hz' }; }; + var peg$f192 = function(value) { return { value: value, unit: 'kh' }; }; + var peg$f193 = function(value) { return { value: value, unit: '%' }; }; + var peg$f194 = function(value) { return value; }; + var peg$f195 = function(value) { return { value: value, unit: null }; }; + var peg$f196 = function(url) { return url; }; + var peg$f197 = function(url) { return url; }; + var peg$f198 = function(name) { return name; }; var peg$currPos = 0; var peg$savedPos = 0; var peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -1577,74 +1629,89 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$parseborder_color_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseborder_dec(); + s0 = peg$parseborder_top_left_radius_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsewidth_dec(); + s0 = peg$parseborder_top_right_radius_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseheight_dec(); + s0 = peg$parseborder_bottom_right_radius_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsetop_dec(); + s0 = peg$parseborder_bottom_left_radius_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseright_dec(); + s0 = peg$parseborder_radius_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsebottom_dec(); + s0 = peg$parseborder_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseleft_dec(); + s0 = peg$parsewidth_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsebox_sizing_dec(); + s0 = peg$parseheight_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsebackground_color_dec(); + s0 = peg$parsetop_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsebackground_clip_dec(); + s0 = peg$parseright_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsetext_align_dec(); + s0 = peg$parsebottom_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsefloat_dec(); + s0 = peg$parseleft_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseclear_dec(); + s0 = peg$parsebox_sizing_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsez_index_dec(); + s0 = peg$parsebackground_color_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseword_break_dec(); + s0 = peg$parsebackground_clip_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseoverflow_wrap_dec(); + s0 = peg$parsetext_align_dec(); if (s0 === peg$FAILED) { - s0 = peg$parseoverflow_dec(); + s0 = peg$parsefloat_dec(); if (s0 === peg$FAILED) { - s0 = peg$parsezoom_dec(); + s0 = peg$parseclear_dec(); if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$parseproperty(); - if (s1 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 58) { - s2 = peg$c1; - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } - } - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$parseS(); - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseS(); - } - s4 = peg$parseexpr(); - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f1(s1, s4); - } else { - peg$currPos = s0; - s0 = peg$FAILED; + s0 = peg$parsez_index_dec(); + if (s0 === peg$FAILED) { + s0 = peg$parseword_break_dec(); + if (s0 === peg$FAILED) { + s0 = peg$parseoverflow_wrap_dec(); + if (s0 === peg$FAILED) { + s0 = peg$parseoverflow_dec(); + if (s0 === peg$FAILED) { + s0 = peg$parsezoom_dec(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseproperty(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s2 = peg$c1; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parseS(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseS(); + } + s4 = peg$parseexpr(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f1(s1, s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } } - } else { - peg$currPos = s0; - s0 = peg$FAILED; } - } else { - peg$currPos = s0; - s0 = peg$FAILED; } } } @@ -7648,105 +7715,164 @@ function peg$parse(input, options) { return s0; } - function peg$parseborder_s() { - var s0; + function peg$parseborder_top_left_radius_dec() { + var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 4) === peg$c134) { - s0 = peg$c134; - peg$currPos += 4; + s0 = peg$currPos; + if (input.substr(peg$currPos, 22).toLowerCase() === peg$c134) { + s1 = input.substr(peg$currPos, 22); + peg$currPos += 22; } else { - s0 = peg$FAILED; + s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e139); } } - if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c135) { - s0 = peg$c135; - peg$currPos += 6; + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e140); } + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } } - if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 7) === peg$c136) { - s0 = peg$c136; - peg$currPos += 7; + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseS(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseS(); + } + s7 = peg$parseLENGTH(); + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f108(s5, s7); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } } else { + peg$currPos = s0; s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e141); } } - if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 5) === peg$c137) { - s0 = peg$c137; - peg$currPos += 5; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 22).toLowerCase() === peg$c134) { + s1 = input.substr(peg$currPos, 22); + peg$currPos += 22; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e139); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 === peg$FAILED) { + s5 = peg$parsedefault(); + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f109(s5); } else { + peg$currPos = s0; s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e142); } } + } else { + peg$currPos = s0; + s0 = peg$FAILED; } + } else { + peg$currPos = s0; + s0 = peg$FAILED; } } return s0; } - function peg$parseborder_dec() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10; + function peg$parseborder_top_right_radius_dec() { + var s0, s1, s2, s3, s4, s5, s6, s7; s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { - s1 = input.substr(peg$currPos, 6); - peg$currPos += 6; + if (input.substr(peg$currPos, 23).toLowerCase() === peg$c135) { + s1 = input.substr(peg$currPos, 23); + peg$currPos += 23; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e140); } } if (s1 !== peg$FAILED) { - s2 = peg$parseborder_s(); - if (s2 === peg$FAILED) { - s2 = null; - } - s3 = []; - s4 = peg$parseS(); - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseS(); + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); } if (input.charCodeAt(peg$currPos) === 58) { - s4 = peg$c1; + s3 = peg$c1; peg$currPos++; } else { - s4 = peg$FAILED; + s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e1); } } - if (s4 !== peg$FAILED) { - s5 = []; - s6 = peg$parseS(); - while (s6 !== peg$FAILED) { - s5.push(s6); - s6 = peg$parseS(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); } - s6 = peg$parseLENGTH(); - if (s6 !== peg$FAILED) { - s7 = []; - s8 = peg$parseS(); - while (s8 !== peg$FAILED) { - s7.push(s8); - s8 = peg$parseS(); + s5 = peg$parseLENGTH(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseS(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseS(); } - s8 = peg$parseborder_style(); - if (s8 !== peg$FAILED) { - s9 = []; - s10 = peg$parseS(); - while (s10 !== peg$FAILED) { - s9.push(s10); - s10 = peg$parseS(); - } - s10 = peg$parsecolor(); - if (s10 === peg$FAILED) { - s10 = null; - } + s7 = peg$parseLENGTH(); + if (s7 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f108(s2, s6, s8, s10); + s0 = peg$f110(s5, s7); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -7765,67 +7891,732 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { - s1 = input.substr(peg$currPos, 6); - peg$currPos += 6; + if (input.substr(peg$currPos, 23).toLowerCase() === peg$c135) { + s1 = input.substr(peg$currPos, 23); + peg$currPos += 23; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e140); } } if (s1 !== peg$FAILED) { - s2 = peg$parseborder_s(); - if (s2 === peg$FAILED) { - s2 = null; - } - s3 = []; - s4 = peg$parseS(); - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseS(); + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); } if (input.charCodeAt(peg$currPos) === 58) { - s4 = peg$c1; + s3 = peg$c1; peg$currPos++; } else { - s4 = peg$FAILED; + s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e1); } } - if (s4 !== peg$FAILED) { - s5 = []; - s6 = peg$parseS(); - while (s6 !== peg$FAILED) { - s5.push(s6); - s6 = peg$parseS(); - } - s6 = peg$parseborder_style(); - if (s6 !== peg$FAILED) { - s7 = []; - s8 = peg$parseS(); - while (s8 !== peg$FAILED) { - s7.push(s8); - s8 = peg$parseS(); - } - s8 = peg$parseLENGTH(); - if (s8 !== peg$FAILED) { - s9 = []; - s10 = peg$parseS(); - while (s10 !== peg$FAILED) { - s9.push(s10); - s10 = peg$parseS(); - } - s10 = peg$parsecolor(); - if (s10 === peg$FAILED) { - s10 = null; - } - peg$savedPos = s0; - s0 = peg$f109(s2, s6, s8, s10); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 === peg$FAILED) { + s5 = peg$parsedefault(); + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f111(s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseborder_bottom_right_radius_dec() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 26).toLowerCase() === peg$c136) { + s1 = input.substr(peg$currPos, 26); + peg$currPos += 26; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e141); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseS(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseS(); + } + s7 = peg$parseLENGTH(); + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f112(s5, s7); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 26).toLowerCase() === peg$c136) { + s1 = input.substr(peg$currPos, 26); + peg$currPos += 26; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e141); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 === peg$FAILED) { + s5 = peg$parsedefault(); + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f113(s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseborder_bottom_left_radius_dec() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 25).toLowerCase() === peg$c137) { + s1 = input.substr(peg$currPos, 25); + peg$currPos += 25; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e142); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseS(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseS(); + } + s7 = peg$parseLENGTH(); + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f114(s5, s7); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 25).toLowerCase() === peg$c137) { + s1 = input.substr(peg$currPos, 25); + peg$currPos += 25; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e142); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 === peg$FAILED) { + s5 = peg$parsedefault(); + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f115(s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseborder_radius_dec() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 13).toLowerCase() === peg$c138) { + s1 = input.substr(peg$currPos, 13); + peg$currPos += 13; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e143); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseS(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseS(); + } + s7 = peg$parseLENGTH(); + if (s7 !== peg$FAILED) { + s8 = []; + s9 = peg$parseS(); + while (s9 !== peg$FAILED) { + s8.push(s9); + s9 = peg$parseS(); + } + s9 = peg$parseLENGTH(); + if (s9 !== peg$FAILED) { + s10 = []; + s11 = peg$parseS(); + while (s11 !== peg$FAILED) { + s10.push(s11); + s11 = peg$parseS(); + } + s11 = peg$parseLENGTH(); + if (s11 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f116(s5, s7, s9, s11); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 13).toLowerCase() === peg$c138) { + s1 = input.substr(peg$currPos, 13); + peg$currPos += 13; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e143); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseS(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseS(); + } + s7 = peg$parseLENGTH(); + if (s7 !== peg$FAILED) { + s8 = []; + s9 = peg$parseS(); + while (s9 !== peg$FAILED) { + s8.push(s9); + s9 = peg$parseS(); + } + s9 = peg$parseLENGTH(); + if (s9 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f117(s5, s7, s9); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 13).toLowerCase() === peg$c138) { + s1 = input.substr(peg$currPos, 13); + peg$currPos += 13; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e143); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseS(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseS(); + } + s7 = peg$parseLENGTH(); + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f118(s5, s7); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 13).toLowerCase() === peg$c138) { + s1 = input.substr(peg$currPos, 13); + peg$currPos += 13; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e143); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseS(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + s5 = peg$parseLENGTH(); + if (s5 === peg$FAILED) { + s5 = peg$parsedefault(); + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f119(s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + + return s0; + } + + function peg$parseborder_s() { + var s0; + + if (input.substr(peg$currPos, 4) === peg$c139) { + s0 = peg$c139; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e144); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c140) { + s0 = peg$c140; + peg$currPos += 6; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e145); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 7) === peg$c141) { + s0 = peg$c141; + peg$currPos += 7; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e146); } + } + if (s0 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c142) { + s0 = peg$c142; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e147); } + } + } + } + } + + return s0; + } + + function peg$parseborder_dec() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { + s1 = input.substr(peg$currPos, 6); + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e148); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseborder_s(); + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = []; + s4 = peg$parseS(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s4 = peg$c1; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s4 !== peg$FAILED) { + s5 = []; + s6 = peg$parseS(); + while (s6 !== peg$FAILED) { + s5.push(s6); + s6 = peg$parseS(); + } + s6 = peg$parseLENGTH(); + if (s6 !== peg$FAILED) { + s7 = []; + s8 = peg$parseS(); + while (s8 !== peg$FAILED) { + s7.push(s8); + s8 = peg$parseS(); + } + s8 = peg$parseborder_style(); + if (s8 !== peg$FAILED) { + s9 = []; + s10 = peg$parseS(); + while (s10 !== peg$FAILED) { + s9.push(s10); + s10 = peg$parseS(); + } + s10 = peg$parsecolor(); + if (s10 === peg$FAILED) { + s10 = null; + } + peg$savedPos = s0; + s0 = peg$f120(s2, s6, s8, s10); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { + s1 = input.substr(peg$currPos, 6); + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e148); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseborder_s(); + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = []; + s4 = peg$parseS(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseS(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s4 = peg$c1; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s4 !== peg$FAILED) { + s5 = []; + s6 = peg$parseS(); + while (s6 !== peg$FAILED) { + s5.push(s6); + s6 = peg$parseS(); + } + s6 = peg$parseborder_style(); + if (s6 !== peg$FAILED) { + s7 = []; + s8 = peg$parseS(); + while (s8 !== peg$FAILED) { + s7.push(s8); + s8 = peg$parseS(); + } + s8 = peg$parseLENGTH(); + if (s8 !== peg$FAILED) { + s9 = []; + s10 = peg$parseS(); + while (s10 !== peg$FAILED) { + s9.push(s10); + s10 = peg$parseS(); + } + s10 = peg$parsecolor(); + if (s10 === peg$FAILED) { + s10 = null; + } + peg$savedPos = s0; + s0 = peg$f121(s2, s6, s8, s10); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; } } else { peg$currPos = s0; @@ -7837,12 +8628,12 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { s1 = input.substr(peg$currPos, 6); peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e148); } } if (s1 !== peg$FAILED) { s2 = peg$parseborder_s(); @@ -7890,7 +8681,7 @@ function peg$parse(input, options) { s10 = null; } peg$savedPos = s0; - s0 = peg$f110(s2, s6, s8, s10); + s0 = peg$f122(s2, s6, s8, s10); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -7909,12 +8700,12 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { s1 = input.substr(peg$currPos, 6); peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e148); } } if (s1 !== peg$FAILED) { s2 = peg$parseborder_s(); @@ -7962,7 +8753,7 @@ function peg$parse(input, options) { s10 = null; } peg$savedPos = s0; - s0 = peg$f111(s2, s6, s8, s10); + s0 = peg$f123(s2, s6, s8, s10); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -7981,12 +8772,12 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { s1 = input.substr(peg$currPos, 6); peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e148); } } if (s1 !== peg$FAILED) { s2 = peg$parseborder_s(); @@ -8021,7 +8812,7 @@ function peg$parse(input, options) { s7.push(s8); s8 = peg$parseS(); } - s8 = peg$parseborder_style(); + s8 = peg$parseLENGTH(); if (s8 !== peg$FAILED) { s9 = []; s10 = peg$parseS(); @@ -8029,12 +8820,8 @@ function peg$parse(input, options) { s9.push(s10); s10 = peg$parseS(); } - s10 = peg$parseLENGTH(); - if (s10 === peg$FAILED) { - s10 = null; - } peg$savedPos = s0; - s0 = peg$f112(s2, s6, s8, s10); + s0 = peg$f124(s2, s6, s8); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8053,12 +8840,12 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { s1 = input.substr(peg$currPos, 6); peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e148); } } if (s1 !== peg$FAILED) { s2 = peg$parseborder_s(); @@ -8085,32 +8872,16 @@ function peg$parse(input, options) { s5.push(s6); s6 = peg$parseS(); } - s6 = peg$parseborder_style(); + s6 = peg$parsecolor(); if (s6 !== peg$FAILED) { s7 = []; s8 = peg$parseS(); - while (s8 !== peg$FAILED) { - s7.push(s8); - s8 = peg$parseS(); - } - s8 = peg$parsecolor(); - if (s8 !== peg$FAILED) { - s9 = []; - s10 = peg$parseS(); - while (s10 !== peg$FAILED) { - s9.push(s10); - s10 = peg$parseS(); - } - s10 = peg$parseLENGTH(); - if (s10 === peg$FAILED) { - s10 = null; - } - peg$savedPos = s0; - s0 = peg$f113(s2, s6, s8, s10); - } else { - peg$currPos = s0; - s0 = peg$FAILED; + while (s8 !== peg$FAILED) { + s7.push(s8); + s8 = peg$parseS(); } + peg$savedPos = s0; + s0 = peg$f125(s2, s6); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8125,12 +8896,12 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { s1 = input.substr(peg$currPos, 6); peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e148); } } if (s1 !== peg$FAILED) { s2 = peg$parseborder_s(); @@ -8157,7 +8928,7 @@ function peg$parse(input, options) { s5.push(s6); s6 = peg$parseS(); } - s6 = peg$parseLENGTH(); + s6 = peg$parseborder_style(); if (s6 !== peg$FAILED) { s7 = []; s8 = peg$parseS(); @@ -8166,7 +8937,7 @@ function peg$parse(input, options) { s8 = peg$parseS(); } peg$savedPos = s0; - s0 = peg$f114(s2, s6); + s0 = peg$f126(s2, s6); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8181,12 +8952,12 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c143) { s1 = input.substr(peg$currPos, 6); peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + if (peg$silentFails === 0) { peg$fail(peg$e148); } } if (s1 !== peg$FAILED) { s2 = peg$parseborder_s(); @@ -8213,7 +8984,7 @@ function peg$parse(input, options) { s5.push(s6); s6 = peg$parseS(); } - s6 = peg$parsecolor(); + s6 = peg$parsedefault(); if (s6 !== peg$FAILED) { s7 = []; s8 = peg$parseS(); @@ -8222,7 +8993,7 @@ function peg$parse(input, options) { s8 = peg$parseS(); } peg$savedPos = s0; - s0 = peg$f115(s2, s6); + s0 = peg$f127(s2, s6); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8235,120 +9006,6 @@ function peg$parse(input, options) { peg$currPos = s0; s0 = peg$FAILED; } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { - s1 = input.substr(peg$currPos, 6); - peg$currPos += 6; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parseborder_s(); - if (s2 === peg$FAILED) { - s2 = null; - } - s3 = []; - s4 = peg$parseS(); - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseS(); - } - if (input.charCodeAt(peg$currPos) === 58) { - s4 = peg$c1; - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } - } - if (s4 !== peg$FAILED) { - s5 = []; - s6 = peg$parseS(); - while (s6 !== peg$FAILED) { - s5.push(s6); - s6 = peg$parseS(); - } - s6 = peg$parseborder_style(); - if (s6 !== peg$FAILED) { - s7 = []; - s8 = peg$parseS(); - while (s8 !== peg$FAILED) { - s7.push(s8); - s8 = peg$parseS(); - } - peg$savedPos = s0; - s0 = peg$f116(s2, s6); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c138) { - s1 = input.substr(peg$currPos, 6); - peg$currPos += 6; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parseborder_s(); - if (s2 === peg$FAILED) { - s2 = null; - } - s3 = []; - s4 = peg$parseS(); - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseS(); - } - if (input.charCodeAt(peg$currPos) === 58) { - s4 = peg$c1; - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } - } - if (s4 !== peg$FAILED) { - s5 = []; - s6 = peg$parseS(); - while (s6 !== peg$FAILED) { - s5.push(s6); - s6 = peg$parseS(); - } - s6 = peg$parsedefault(); - if (s6 !== peg$FAILED) { - s7 = []; - s8 = peg$parseS(); - while (s8 !== peg$FAILED) { - s7.push(s8); - s8 = peg$parseS(); - } - peg$savedPos = s0; - s0 = peg$f117(s2, s6); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } - } } } } @@ -8364,12 +9021,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 16).toLowerCase() === peg$c139) { + if (input.substr(peg$currPos, 16).toLowerCase() === peg$c144) { s1 = input.substr(peg$currPos, 16); peg$currPos += 16; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e144); } + if (peg$silentFails === 0) { peg$fail(peg$e149); } } if (s1 !== peg$FAILED) { s2 = []; @@ -8398,7 +9055,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f118(s5); + s0 = peg$f128(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8419,12 +9076,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 15).toLowerCase() === peg$c140) { + if (input.substr(peg$currPos, 15).toLowerCase() === peg$c145) { s1 = input.substr(peg$currPos, 15); peg$currPos += 15; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e145); } + if (peg$silentFails === 0) { peg$fail(peg$e150); } } if (s1 !== peg$FAILED) { s2 = []; @@ -8447,28 +9104,28 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - if (input.substr(peg$currPos, 10) === peg$c141) { - s5 = peg$c141; + if (input.substr(peg$currPos, 10) === peg$c146) { + s5 = peg$c146; peg$currPos += 10; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e146); } + if (peg$silentFails === 0) { peg$fail(peg$e151); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 11) === peg$c142) { - s5 = peg$c142; + if (input.substr(peg$currPos, 11) === peg$c147) { + s5 = peg$c147; peg$currPos += 11; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e147); } + if (peg$silentFails === 0) { peg$fail(peg$e152); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 11) === peg$c143) { - s5 = peg$c143; + if (input.substr(peg$currPos, 11) === peg$c148) { + s5 = peg$c148; peg$currPos += 11; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e148); } + if (peg$silentFails === 0) { peg$fail(peg$e153); } } if (s5 === peg$FAILED) { s5 = peg$parsedefault(); @@ -8477,7 +9134,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f119(s5); + s0 = peg$f129(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8498,12 +9155,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 10).toLowerCase() === peg$c144) { + if (input.substr(peg$currPos, 10).toLowerCase() === peg$c149) { s1 = input.substr(peg$currPos, 10); peg$currPos += 10; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e149); } + if (peg$silentFails === 0) { peg$fail(peg$e154); } } if (s1 !== peg$FAILED) { s2 = []; @@ -8526,44 +9183,44 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - if (input.substr(peg$currPos, 5) === peg$c145) { - s5 = peg$c145; + if (input.substr(peg$currPos, 5) === peg$c150) { + s5 = peg$c150; peg$currPos += 5; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e150); } + if (peg$silentFails === 0) { peg$fail(peg$e155); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c146) { - s5 = peg$c146; + if (input.substr(peg$currPos, 3) === peg$c151) { + s5 = peg$c151; peg$currPos += 3; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e151); } + if (peg$silentFails === 0) { peg$fail(peg$e156); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 4) === peg$c147) { - s5 = peg$c147; + if (input.substr(peg$currPos, 4) === peg$c152) { + s5 = peg$c152; peg$currPos += 4; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e152); } + if (peg$silentFails === 0) { peg$fail(peg$e157); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 5) === peg$c148) { - s5 = peg$c148; + if (input.substr(peg$currPos, 5) === peg$c153) { + s5 = peg$c153; peg$currPos += 5; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e153); } + if (peg$silentFails === 0) { peg$fail(peg$e158); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c149) { - s5 = peg$c149; + if (input.substr(peg$currPos, 6) === peg$c154) { + s5 = peg$c154; peg$currPos += 6; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e154); } + if (peg$silentFails === 0) { peg$fail(peg$e159); } } if (s5 === peg$FAILED) { s5 = peg$parsedefault(); @@ -8574,7 +9231,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f120(s5); + s0 = peg$f130(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8595,12 +9252,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 5).toLowerCase() === peg$c150) { + if (input.substr(peg$currPos, 5).toLowerCase() === peg$c155) { s1 = input.substr(peg$currPos, 5); peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e155); } + if (peg$silentFails === 0) { peg$fail(peg$e160); } } if (s1 !== peg$FAILED) { s2 = []; @@ -8623,20 +9280,20 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - if (input.substr(peg$currPos, 4) === peg$c147) { - s5 = peg$c147; + if (input.substr(peg$currPos, 4) === peg$c152) { + s5 = peg$c152; peg$currPos += 4; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e152); } + if (peg$silentFails === 0) { peg$fail(peg$e157); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 5) === peg$c148) { - s5 = peg$c148; + if (input.substr(peg$currPos, 5) === peg$c153) { + s5 = peg$c153; peg$currPos += 5; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e153); } + if (peg$silentFails === 0) { peg$fail(peg$e158); } } if (s5 === peg$FAILED) { if (input.substr(peg$currPos, 4) === peg$c72) { @@ -8653,7 +9310,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f121(s5); + s0 = peg$f131(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8674,12 +9331,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 5).toLowerCase() === peg$c151) { + if (input.substr(peg$currPos, 5).toLowerCase() === peg$c156) { s1 = input.substr(peg$currPos, 5); peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e156); } + if (peg$silentFails === 0) { peg$fail(peg$e161); } } if (s1 !== peg$FAILED) { s2 = []; @@ -8702,20 +9359,20 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - if (input.substr(peg$currPos, 4) === peg$c147) { - s5 = peg$c147; + if (input.substr(peg$currPos, 4) === peg$c152) { + s5 = peg$c152; peg$currPos += 4; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e152); } + if (peg$silentFails === 0) { peg$fail(peg$e157); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 5) === peg$c148) { - s5 = peg$c148; + if (input.substr(peg$currPos, 5) === peg$c153) { + s5 = peg$c153; peg$currPos += 5; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e153); } + if (peg$silentFails === 0) { peg$fail(peg$e158); } } if (s5 === peg$FAILED) { if (input.substr(peg$currPos, 4) === peg$c72) { @@ -8726,12 +9383,12 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e77); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 4) === peg$c152) { - s5 = peg$c152; + if (input.substr(peg$currPos, 4) === peg$c157) { + s5 = peg$c157; peg$currPos += 4; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e157); } + if (peg$silentFails === 0) { peg$fail(peg$e162); } } if (s5 === peg$FAILED) { s5 = peg$parsedefault(); @@ -8741,7 +9398,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f122(s5); + s0 = peg$f132(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8762,12 +9419,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 7).toLowerCase() === peg$c153) { + if (input.substr(peg$currPos, 7).toLowerCase() === peg$c158) { s1 = input.substr(peg$currPos, 7); peg$currPos += 7; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e158); } + if (peg$silentFails === 0) { peg$fail(peg$e163); } } if (s1 !== peg$FAILED) { s2 = []; @@ -8805,7 +9462,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f123(s5); + s0 = peg$f133(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8826,12 +9483,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 10).toLowerCase() === peg$c154) { + if (input.substr(peg$currPos, 10).toLowerCase() === peg$c159) { s1 = input.substr(peg$currPos, 10); peg$currPos += 10; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e159); } + if (peg$silentFails === 0) { peg$fail(peg$e164); } } if (s1 !== peg$FAILED) { s2 = []; @@ -8862,12 +9519,12 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e48); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 10) === peg$c155) { - s5 = peg$c155; + if (input.substr(peg$currPos, 10) === peg$c160) { + s5 = peg$c160; peg$currPos += 10; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e160); } + if (peg$silentFails === 0) { peg$fail(peg$e165); } } if (s5 === peg$FAILED) { s5 = peg$parsedefault(); @@ -8875,7 +9532,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f124(s5); + s0 = peg$f134(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8896,20 +9553,20 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 13).toLowerCase() === peg$c156) { + if (input.substr(peg$currPos, 13).toLowerCase() === peg$c161) { s1 = input.substr(peg$currPos, 13); peg$currPos += 13; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e161); } + if (peg$silentFails === 0) { peg$fail(peg$e166); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 9).toLowerCase() === peg$c157) { + if (input.substr(peg$currPos, 9).toLowerCase() === peg$c162) { s1 = input.substr(peg$currPos, 9); peg$currPos += 9; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e162); } + if (peg$silentFails === 0) { peg$fail(peg$e167); } } } if (s1 !== peg$FAILED) { @@ -8941,20 +9598,20 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e48); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c158) { - s5 = peg$c158; + if (input.substr(peg$currPos, 8) === peg$c163) { + s5 = peg$c163; peg$currPos += 8; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e163); } + if (peg$silentFails === 0) { peg$fail(peg$e168); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 10) === peg$c155) { - s5 = peg$c155; + if (input.substr(peg$currPos, 10) === peg$c160) { + s5 = peg$c160; peg$currPos += 10; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e160); } + if (peg$silentFails === 0) { peg$fail(peg$e165); } } if (s5 === peg$FAILED) { s5 = peg$parsedefault(); @@ -8963,7 +9620,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f125(s5); + s0 = peg$f135(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8984,12 +9641,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 8).toLowerCase() === peg$c159) { + if (input.substr(peg$currPos, 8).toLowerCase() === peg$c164) { s1 = input.substr(peg$currPos, 8); peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e164); } + if (peg$silentFails === 0) { peg$fail(peg$e169); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9012,12 +9669,12 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - if (input.substr(peg$currPos, 7) === peg$c160) { - s5 = peg$c160; + if (input.substr(peg$currPos, 7) === peg$c165) { + s5 = peg$c165; peg$currPos += 7; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e165); } + if (peg$silentFails === 0) { peg$fail(peg$e170); } } if (s5 === peg$FAILED) { if (input.substr(peg$currPos, 6) === peg$c84) { @@ -9033,7 +9690,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f126(s5); + s0 = peg$f136(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9054,12 +9711,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 4).toLowerCase() === peg$c161) { + if (input.substr(peg$currPos, 4).toLowerCase() === peg$c166) { s1 = input.substr(peg$currPos, 4); peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e166); } + if (peg$silentFails === 0) { peg$fail(peg$e171); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9091,7 +9748,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f127(s5); + s0 = peg$f137(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9112,12 +9769,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 5).toLowerCase() === peg$c162) { + if (input.substr(peg$currPos, 5).toLowerCase() === peg$c167) { s1 = input.substr(peg$currPos, 5); peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e167); } + if (peg$silentFails === 0) { peg$fail(peg$e172); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9155,7 +9812,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f128(s5); + s0 = peg$f138(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9176,12 +9833,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 6).toLowerCase() === peg$c163) { + if (input.substr(peg$currPos, 6).toLowerCase() === peg$c168) { s1 = input.substr(peg$currPos, 6); peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e168); } + if (peg$silentFails === 0) { peg$fail(peg$e173); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9219,7 +9876,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f129(s5); + s0 = peg$f139(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9245,7 +9902,7 @@ function peg$parse(input, options) { peg$currPos += 3; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e169); } + if (peg$silentFails === 0) { peg$fail(peg$e174); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9283,7 +9940,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f130(s5); + s0 = peg$f140(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9304,12 +9961,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 5).toLowerCase() === peg$c148) { + if (input.substr(peg$currPos, 5).toLowerCase() === peg$c153) { s1 = input.substr(peg$currPos, 5); peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e170); } + if (peg$silentFails === 0) { peg$fail(peg$e175); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9347,7 +10004,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f131(s5); + s0 = peg$f141(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9373,7 +10030,7 @@ function peg$parse(input, options) { peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e171); } + if (peg$silentFails === 0) { peg$fail(peg$e176); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9411,7 +10068,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f132(s5); + s0 = peg$f142(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9432,12 +10089,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 4).toLowerCase() === peg$c147) { + if (input.substr(peg$currPos, 4).toLowerCase() === peg$c152) { s1 = input.substr(peg$currPos, 4); peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e172); } + if (peg$silentFails === 0) { peg$fail(peg$e177); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9475,7 +10132,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f133(s5); + s0 = peg$f143(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9496,12 +10153,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 10).toLowerCase() === peg$c164) { + if (input.substr(peg$currPos, 10).toLowerCase() === peg$c169) { s1 = input.substr(peg$currPos, 10); peg$currPos += 10; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e173); } + if (peg$silentFails === 0) { peg$fail(peg$e178); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9524,20 +10181,20 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - if (input.substr(peg$currPos, 10) === peg$c141) { - s5 = peg$c141; + if (input.substr(peg$currPos, 10) === peg$c146) { + s5 = peg$c146; peg$currPos += 10; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e146); } + if (peg$silentFails === 0) { peg$fail(peg$e151); } } if (s5 === peg$FAILED) { - if (input.substr(peg$currPos, 11) === peg$c142) { - s5 = peg$c142; + if (input.substr(peg$currPos, 11) === peg$c147) { + s5 = peg$c147; peg$currPos += 11; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e147); } + if (peg$silentFails === 0) { peg$fail(peg$e152); } } if (s5 === peg$FAILED) { s5 = peg$parsedefault(); @@ -9545,7 +10202,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f134(s5); + s0 = peg$f144(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9570,7 +10227,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e174); } + if (peg$silentFails === 0) { peg$fail(peg$e179); } } return s0; @@ -9584,7 +10241,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e175); } + if (peg$silentFails === 0) { peg$fail(peg$e180); } } return s0; @@ -9595,11 +10252,11 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { s2 = peg$currPos; @@ -9638,12 +10295,12 @@ function peg$parse(input, options) { s2 = s3; } if (s2 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s3 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s3 = peg$c171; peg$currPos += 2; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s3 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -9651,14 +10308,14 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s3 === peg$FAILED) { s3 = null; } peg$savedPos = s0; - s0 = peg$f135(s2); + s0 = peg$f145(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9678,11 +10335,11 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (peg$r8.test(input.charAt(peg$currPos))) { @@ -9690,11 +10347,11 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e179); } + if (peg$silentFails === 0) { peg$fail(peg$e184); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f136(s2); + s0 = peg$f146(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9716,7 +10373,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e180); } + if (peg$silentFails === 0) { peg$fail(peg$e185); } } if (s0 === peg$FAILED) { s0 = peg$parsenonascii(); @@ -9736,7 +10393,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e181); } + if (peg$silentFails === 0) { peg$fail(peg$e186); } } if (s0 === peg$FAILED) { s0 = peg$parsenonascii(); @@ -9753,11 +10410,11 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c167; + s1 = peg$c172; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e182); } + if (peg$silentFails === 0) { peg$fail(peg$e187); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9766,22 +10423,22 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e183); } + if (peg$silentFails === 0) { peg$fail(peg$e188); } } if (s3 === peg$FAILED) { s3 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s4 = peg$c165; + s4 = peg$c170; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s4 !== peg$FAILED) { s5 = peg$parsenl(); if (s5 !== peg$FAILED) { peg$savedPos = s3; - s3 = peg$f137(s5); + s3 = peg$f147(s5); } else { peg$currPos = s3; s3 = peg$FAILED; @@ -9801,22 +10458,22 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e183); } + if (peg$silentFails === 0) { peg$fail(peg$e188); } } if (s3 === peg$FAILED) { s3 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s4 = peg$c165; + s4 = peg$c170; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s4 !== peg$FAILED) { s5 = peg$parsenl(); if (s5 !== peg$FAILED) { peg$savedPos = s3; - s3 = peg$f137(s5); + s3 = peg$f147(s5); } else { peg$currPos = s3; s3 = peg$FAILED; @@ -9831,15 +10488,15 @@ function peg$parse(input, options) { } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c167; + s3 = peg$c172; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e182); } + if (peg$silentFails === 0) { peg$fail(peg$e187); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f138(s2); + s0 = peg$f148(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9857,11 +10514,11 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 39) { - s1 = peg$c168; + s1 = peg$c173; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e184); } + if (peg$silentFails === 0) { peg$fail(peg$e189); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9870,22 +10527,22 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e185); } + if (peg$silentFails === 0) { peg$fail(peg$e190); } } if (s3 === peg$FAILED) { s3 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s4 = peg$c165; + s4 = peg$c170; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s4 !== peg$FAILED) { s5 = peg$parsenl(); if (s5 !== peg$FAILED) { peg$savedPos = s3; - s3 = peg$f139(s5); + s3 = peg$f149(s5); } else { peg$currPos = s3; s3 = peg$FAILED; @@ -9905,22 +10562,22 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e185); } + if (peg$silentFails === 0) { peg$fail(peg$e190); } } if (s3 === peg$FAILED) { s3 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s4 = peg$c165; + s4 = peg$c170; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s4 !== peg$FAILED) { s5 = peg$parsenl(); if (s5 !== peg$FAILED) { peg$savedPos = s3; - s3 = peg$f139(s5); + s3 = peg$f149(s5); } else { peg$currPos = s3; s3 = peg$FAILED; @@ -9935,15 +10592,15 @@ function peg$parse(input, options) { } } if (input.charCodeAt(peg$currPos) === 39) { - s3 = peg$c168; + s3 = peg$c173; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e184); } + if (peg$silentFails === 0) { peg$fail(peg$e189); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f140(s2); + s0 = peg$f150(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -9960,12 +10617,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c169) { - s1 = peg$c169; + if (input.substr(peg$currPos, 2) === peg$c174) { + s1 = peg$c174; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e186); } + if (peg$silentFails === 0) { peg$fail(peg$e191); } } if (s1 !== peg$FAILED) { s2 = []; @@ -9974,7 +10631,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e187); } + if (peg$silentFails === 0) { peg$fail(peg$e192); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -9983,26 +10640,26 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e187); } + if (peg$silentFails === 0) { peg$fail(peg$e192); } } } s3 = []; if (input.charCodeAt(peg$currPos) === 42) { - s4 = peg$c170; + s4 = peg$c175; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e188); } + if (peg$silentFails === 0) { peg$fail(peg$e193); } } if (s4 !== peg$FAILED) { while (s4 !== peg$FAILED) { s3.push(s4); if (input.charCodeAt(peg$currPos) === 42) { - s4 = peg$c170; + s4 = peg$c175; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e188); } + if (peg$silentFails === 0) { peg$fail(peg$e193); } } } } else { @@ -10016,7 +10673,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e189); } + if (peg$silentFails === 0) { peg$fail(peg$e194); } } if (s6 !== peg$FAILED) { s7 = []; @@ -10025,7 +10682,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e187); } + if (peg$silentFails === 0) { peg$fail(peg$e192); } } while (s8 !== peg$FAILED) { s7.push(s8); @@ -10034,26 +10691,26 @@ function peg$parse(input, options) { peg$currPos++; } else { s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e187); } + if (peg$silentFails === 0) { peg$fail(peg$e192); } } } s8 = []; if (input.charCodeAt(peg$currPos) === 42) { - s9 = peg$c170; + s9 = peg$c175; peg$currPos++; } else { s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e188); } + if (peg$silentFails === 0) { peg$fail(peg$e193); } } if (s9 !== peg$FAILED) { while (s9 !== peg$FAILED) { s8.push(s9); if (input.charCodeAt(peg$currPos) === 42) { - s9 = peg$c170; + s9 = peg$c175; peg$currPos++; } else { s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e188); } + if (peg$silentFails === 0) { peg$fail(peg$e193); } } } } else { @@ -10078,7 +10735,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e189); } + if (peg$silentFails === 0) { peg$fail(peg$e194); } } if (s6 !== peg$FAILED) { s7 = []; @@ -10087,7 +10744,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e187); } + if (peg$silentFails === 0) { peg$fail(peg$e192); } } while (s8 !== peg$FAILED) { s7.push(s8); @@ -10096,26 +10753,26 @@ function peg$parse(input, options) { peg$currPos++; } else { s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e187); } + if (peg$silentFails === 0) { peg$fail(peg$e192); } } } s8 = []; if (input.charCodeAt(peg$currPos) === 42) { - s9 = peg$c170; + s9 = peg$c175; peg$currPos++; } else { s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e188); } + if (peg$silentFails === 0) { peg$fail(peg$e193); } } if (s9 !== peg$FAILED) { while (s9 !== peg$FAILED) { s8.push(s9); if (input.charCodeAt(peg$currPos) === 42) { - s9 = peg$c170; + s9 = peg$c175; peg$currPos++; } else { s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e188); } + if (peg$silentFails === 0) { peg$fail(peg$e193); } } } } else { @@ -10165,11 +10822,11 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = peg$currPos; if (input.charCodeAt(peg$currPos) === 45) { - s2 = peg$c171; + s2 = peg$c176; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e190); } + if (peg$silentFails === 0) { peg$fail(peg$e195); } } if (s2 === peg$FAILED) { s2 = null; @@ -10184,7 +10841,7 @@ function peg$parse(input, options) { s4 = peg$parsenmchar(); } peg$savedPos = s0; - s0 = peg$f141(s1, s2, s3); + s0 = peg$f151(s1, s2, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -10209,7 +10866,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f142(s1); + s1 = peg$f152(s1); } s0 = s1; @@ -10225,7 +10882,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e191); } + if (peg$silentFails === 0) { peg$fail(peg$e196); } } if (s1 === peg$FAILED) { s1 = null; @@ -10317,11 +10974,11 @@ function peg$parse(input, options) { if (s2 !== peg$FAILED) { s3 = peg$currPos; if (input.charCodeAt(peg$currPos) === 101) { - s4 = peg$c172; + s4 = peg$c177; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e192); } + if (peg$silentFails === 0) { peg$fail(peg$e197); } } if (s4 !== peg$FAILED) { if (peg$r15.test(input.charAt(peg$currPos))) { @@ -10329,7 +10986,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e191); } + if (peg$silentFails === 0) { peg$fail(peg$e196); } } if (s5 === peg$FAILED) { s5 = null; @@ -10371,7 +11028,7 @@ function peg$parse(input, options) { s3 = null; } peg$savedPos = s0; - s0 = peg$f143(); + s0 = peg$f153(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -10401,7 +11058,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e193); } + if (peg$silentFails === 0) { peg$fail(peg$e198); } } if (s2 === peg$FAILED) { s2 = peg$parsenonascii(); @@ -10416,7 +11073,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e193); } + if (peg$silentFails === 0) { peg$fail(peg$e198); } } if (s2 === peg$FAILED) { s2 = peg$parsenonascii(); @@ -10426,7 +11083,7 @@ function peg$parse(input, options) { } } peg$savedPos = s0; - s1 = peg$f144(s1); + s1 = peg$f154(s1); s0 = s1; return s0; @@ -10441,7 +11098,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } if (s1 !== peg$FAILED) { while (s1 !== peg$FAILED) { @@ -10451,7 +11108,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } } else { @@ -10476,35 +11133,35 @@ function peg$parse(input, options) { var s0; if (input.charCodeAt(peg$currPos) === 10) { - s0 = peg$c173; + s0 = peg$c178; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e194); } + if (peg$silentFails === 0) { peg$fail(peg$e199); } } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s0 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s0 = peg$c171; peg$currPos += 2; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 13) { - s0 = peg$c174; + s0 = peg$c179; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e195); } + if (peg$silentFails === 0) { peg$fail(peg$e200); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 12) { - s0 = peg$c175; + s0 = peg$c180; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e196); } + if (peg$silentFails === 0) { peg$fail(peg$e201); } } } } @@ -10516,59 +11173,59 @@ function peg$parse(input, options) { function peg$parseA() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c176) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c181) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e197); } + if (peg$silentFails === 0) { peg$fail(peg$e202); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -10578,15 +11235,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e199); } + if (peg$silentFails === 0) { peg$fail(peg$e204); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -10594,14 +11251,14 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { s7 = null; } peg$savedPos = s0; - s0 = peg$f145(); + s0 = peg$f155(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -10618,59 +11275,59 @@ function peg$parse(input, options) { function peg$parseC() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c178) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c183) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e200); } + if (peg$silentFails === 0) { peg$fail(peg$e205); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -10680,15 +11337,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e201); } + if (peg$silentFails === 0) { peg$fail(peg$e206); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -10696,14 +11353,14 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { s7 = null; } peg$savedPos = s0; - s0 = peg$f146(); + s0 = peg$f156(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -10720,59 +11377,59 @@ function peg$parse(input, options) { function peg$parseD() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c179) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c184) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e202); } + if (peg$silentFails === 0) { peg$fail(peg$e207); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -10782,15 +11439,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e203); } + if (peg$silentFails === 0) { peg$fail(peg$e208); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -10798,14 +11455,14 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { s7 = null; } peg$savedPos = s0; - s0 = peg$f147(); + s0 = peg$f157(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -10822,59 +11479,59 @@ function peg$parse(input, options) { function peg$parseE() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c172) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c177) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e204); } + if (peg$silentFails === 0) { peg$fail(peg$e209); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -10884,15 +11541,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e205); } + if (peg$silentFails === 0) { peg$fail(peg$e210); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -10900,14 +11557,14 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { s7 = null; } peg$savedPos = s0; - s0 = peg$f148(); + s0 = peg$f158(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -10924,59 +11581,59 @@ function peg$parse(input, options) { function peg$parseG() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c180) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c185) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e206); } + if (peg$silentFails === 0) { peg$fail(peg$e211); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -10986,15 +11643,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e207); } + if (peg$silentFails === 0) { peg$fail(peg$e212); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11002,7 +11659,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11020,16 +11677,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c181) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c186) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e208); } + if (peg$silentFails === 0) { peg$fail(peg$e213); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f149(); + s1 = peg$f159(); } s0 = s1; } @@ -11041,59 +11698,59 @@ function peg$parse(input, options) { function peg$parseH() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c182) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c187) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e209); } + if (peg$silentFails === 0) { peg$fail(peg$e214); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11103,15 +11760,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e210); } + if (peg$silentFails === 0) { peg$fail(peg$e215); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11119,7 +11776,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11137,16 +11794,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c183) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c188) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e211); } + if (peg$silentFails === 0) { peg$fail(peg$e216); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f150(); + s1 = peg$f160(); } s0 = s1; } @@ -11158,59 +11815,59 @@ function peg$parse(input, options) { function peg$parseI() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c184) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c189) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e212); } + if (peg$silentFails === 0) { peg$fail(peg$e217); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11220,15 +11877,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e213); } + if (peg$silentFails === 0) { peg$fail(peg$e218); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11236,7 +11893,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11254,16 +11911,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c185) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c190) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e214); } + if (peg$silentFails === 0) { peg$fail(peg$e219); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f151(); + s1 = peg$f161(); } s0 = s1; } @@ -11275,59 +11932,59 @@ function peg$parse(input, options) { function peg$parseK() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c186) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c191) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e215); } + if (peg$silentFails === 0) { peg$fail(peg$e220); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11337,15 +11994,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e216); } + if (peg$silentFails === 0) { peg$fail(peg$e221); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11353,7 +12010,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11371,16 +12028,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c187) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c192) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e217); } + if (peg$silentFails === 0) { peg$fail(peg$e222); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f152(); + s1 = peg$f162(); } s0 = s1; } @@ -11392,59 +12049,59 @@ function peg$parse(input, options) { function peg$parseL() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c188) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c193) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e218); } + if (peg$silentFails === 0) { peg$fail(peg$e223); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11454,15 +12111,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e219); } + if (peg$silentFails === 0) { peg$fail(peg$e224); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11470,7 +12127,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11488,16 +12145,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c189) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c194) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e220); } + if (peg$silentFails === 0) { peg$fail(peg$e225); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f153(); + s1 = peg$f163(); } s0 = s1; } @@ -11509,59 +12166,59 @@ function peg$parse(input, options) { function peg$parseM() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c190) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c195) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e221); } + if (peg$silentFails === 0) { peg$fail(peg$e226); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11571,15 +12228,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e222); } + if (peg$silentFails === 0) { peg$fail(peg$e227); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11587,7 +12244,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11605,16 +12262,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c191) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c196) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e223); } + if (peg$silentFails === 0) { peg$fail(peg$e228); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f154(); + s1 = peg$f164(); } s0 = s1; } @@ -11626,59 +12283,59 @@ function peg$parse(input, options) { function peg$parseN() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c192) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c197) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e224); } + if (peg$silentFails === 0) { peg$fail(peg$e229); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11688,15 +12345,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e225); } + if (peg$silentFails === 0) { peg$fail(peg$e230); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11704,7 +12361,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11722,16 +12379,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c193) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c198) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e226); } + if (peg$silentFails === 0) { peg$fail(peg$e231); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f155(); + s1 = peg$f165(); } s0 = s1; } @@ -11743,59 +12400,59 @@ function peg$parse(input, options) { function peg$parseO() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c194) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c199) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e227); } + if (peg$silentFails === 0) { peg$fail(peg$e232); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11805,15 +12462,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e228); } + if (peg$silentFails === 0) { peg$fail(peg$e233); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11821,7 +12478,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11839,16 +12496,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c195) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c200) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e229); } + if (peg$silentFails === 0) { peg$fail(peg$e234); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f156(); + s1 = peg$f166(); } s0 = s1; } @@ -11860,59 +12517,59 @@ function peg$parse(input, options) { function peg$parseP() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c196) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c201) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e230); } + if (peg$silentFails === 0) { peg$fail(peg$e235); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -11922,15 +12579,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e231); } + if (peg$silentFails === 0) { peg$fail(peg$e236); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -11938,7 +12595,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -11956,16 +12613,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c197) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c202) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e232); } + if (peg$silentFails === 0) { peg$fail(peg$e237); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f157(); + s1 = peg$f167(); } s0 = s1; } @@ -11977,59 +12634,59 @@ function peg$parse(input, options) { function peg$parseR() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c198) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c203) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e233); } + if (peg$silentFails === 0) { peg$fail(peg$e238); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -12039,15 +12696,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e234); } + if (peg$silentFails === 0) { peg$fail(peg$e239); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -12055,7 +12712,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -12073,16 +12730,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c199) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c204) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e235); } + if (peg$silentFails === 0) { peg$fail(peg$e240); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f158(); + s1 = peg$f168(); } s0 = s1; } @@ -12094,59 +12751,59 @@ function peg$parse(input, options) { function peg$parseS_() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c200) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c205) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e236); } + if (peg$silentFails === 0) { peg$fail(peg$e241); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -12156,15 +12813,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e237); } + if (peg$silentFails === 0) { peg$fail(peg$e242); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -12172,7 +12829,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -12190,16 +12847,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c201) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c206) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e238); } + if (peg$silentFails === 0) { peg$fail(peg$e243); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f159(); + s1 = peg$f169(); } s0 = s1; } @@ -12211,59 +12868,59 @@ function peg$parse(input, options) { function peg$parseT() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c202) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c207) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e239); } + if (peg$silentFails === 0) { peg$fail(peg$e244); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -12273,15 +12930,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e240); } + if (peg$silentFails === 0) { peg$fail(peg$e245); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -12289,7 +12946,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -12307,16 +12964,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c203) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c208) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e241); } + if (peg$silentFails === 0) { peg$fail(peg$e246); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f160(); + s1 = peg$f170(); } s0 = s1; } @@ -12328,59 +12985,59 @@ function peg$parse(input, options) { function peg$parseU() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c204) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c209) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e242); } + if (peg$silentFails === 0) { peg$fail(peg$e247); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -12390,15 +13047,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e243); } + if (peg$silentFails === 0) { peg$fail(peg$e248); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -12406,7 +13063,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -12424,16 +13081,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c205) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c210) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e244); } + if (peg$silentFails === 0) { peg$fail(peg$e249); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f161(); + s1 = peg$f171(); } s0 = s1; } @@ -12445,59 +13102,59 @@ function peg$parse(input, options) { function peg$parseX() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c206) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c211) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e245); } + if (peg$silentFails === 0) { peg$fail(peg$e250); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -12507,15 +13164,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e246); } + if (peg$silentFails === 0) { peg$fail(peg$e251); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -12523,7 +13180,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -12541,16 +13198,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c207) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c212) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e247); } + if (peg$silentFails === 0) { peg$fail(peg$e252); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f162(); + s1 = peg$f172(); } s0 = s1; } @@ -12562,59 +13219,59 @@ function peg$parse(input, options) { function peg$parseZ() { var s0, s1, s2, s3, s4, s5, s6, s7; - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c208) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c213) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e248); } + if (peg$silentFails === 0) { peg$fail(peg$e253); } } if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c165; + s1 = peg$c170; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e176); } + if (peg$silentFails === 0) { peg$fail(peg$e181); } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 === peg$FAILED) { s2 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s3 = peg$c177; + s3 = peg$c182; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s3 === peg$FAILED) { s3 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s4 = peg$c177; + s4 = peg$c182; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s4 === peg$FAILED) { s4 = null; } if (input.charCodeAt(peg$currPos) === 48) { - s5 = peg$c177; + s5 = peg$c182; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s5 === peg$FAILED) { s5 = null; @@ -12624,15 +13281,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e249); } + if (peg$silentFails === 0) { peg$fail(peg$e254); } } if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c166) { - s7 = peg$c166; + if (input.substr(peg$currPos, 2) === peg$c171) { + s7 = peg$c171; peg$currPos += 2; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e177); } + if (peg$silentFails === 0) { peg$fail(peg$e182); } } if (s7 === peg$FAILED) { if (peg$r7.test(input.charAt(peg$currPos))) { @@ -12640,7 +13297,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e178); } + if (peg$silentFails === 0) { peg$fail(peg$e183); } } } if (s7 === peg$FAILED) { @@ -12658,16 +13315,16 @@ function peg$parse(input, options) { } if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c209) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c214) { s1 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e250); } + if (peg$silentFails === 0) { peg$fail(peg$e255); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f163(); + s1 = peg$f173(); } s0 = s1; } @@ -12698,7 +13355,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e251); } + if (peg$silentFails === 0) { peg$fail(peg$e256); } } return s0; @@ -12718,7 +13375,7 @@ function peg$parse(input, options) { s2 = peg$parsestring(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f164(s2); + s0 = peg$f174(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12726,7 +13383,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e252); } + if (peg$silentFails === 0) { peg$fail(peg$e257); } } return s0; @@ -12746,7 +13403,7 @@ function peg$parse(input, options) { s2 = peg$parseident(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f165(s2); + s0 = peg$f175(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12754,7 +13411,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e253); } + if (peg$silentFails === 0) { peg$fail(peg$e258); } } return s0; @@ -12782,7 +13439,7 @@ function peg$parse(input, options) { s3 = peg$parsename(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f166(s3); + s0 = peg$f176(s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12794,7 +13451,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e254); } + if (peg$silentFails === 0) { peg$fail(peg$e259); } } return s0; @@ -12818,7 +13475,7 @@ function peg$parse(input, options) { s4 = peg$parseX(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f167(s2); + s0 = peg$f177(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12834,7 +13491,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e255); } + if (peg$silentFails === 0) { peg$fail(peg$e260); } } return s0; @@ -12858,7 +13515,7 @@ function peg$parse(input, options) { s4 = peg$parseX(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f168(s2); + s0 = peg$f178(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12886,7 +13543,7 @@ function peg$parse(input, options) { s4 = peg$parseM(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f169(s2); + s0 = peg$f179(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12914,7 +13571,7 @@ function peg$parse(input, options) { s4 = peg$parseM(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f170(s2); + s0 = peg$f180(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12942,7 +13599,7 @@ function peg$parse(input, options) { s4 = peg$parseN(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f171(s2); + s0 = peg$f181(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12970,7 +13627,7 @@ function peg$parse(input, options) { s4 = peg$parseT(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f172(s2); + s0 = peg$f182(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -12998,7 +13655,7 @@ function peg$parse(input, options) { s4 = peg$parseC(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f173(s2); + s0 = peg$f183(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13026,7 +13683,7 @@ function peg$parse(input, options) { s4 = peg$parseM(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f174(s2); + s0 = peg$f184(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13048,15 +13705,15 @@ function peg$parse(input, options) { s2 = peg$parsecomment(); } if (input.charCodeAt(peg$currPos) === 48) { - s2 = peg$c177; + s2 = peg$c182; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e198); } + if (peg$silentFails === 0) { peg$fail(peg$e203); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f175(); + s0 = peg$f185(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13071,7 +13728,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e255); } + if (peg$silentFails === 0) { peg$fail(peg$e260); } } return s0; @@ -13097,7 +13754,7 @@ function peg$parse(input, options) { s5 = peg$parseG(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f176(s2); + s0 = peg$f186(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13131,7 +13788,7 @@ function peg$parse(input, options) { s5 = peg$parseD(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f177(s2); + s0 = peg$f187(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13167,7 +13824,7 @@ function peg$parse(input, options) { s6 = peg$parseD(); if (s6 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f178(s2); + s0 = peg$f188(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13193,7 +13850,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e256); } + if (peg$silentFails === 0) { peg$fail(peg$e261); } } return s0; @@ -13217,7 +13874,7 @@ function peg$parse(input, options) { s4 = peg$parseS_(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f179(s2); + s0 = peg$f189(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13243,7 +13900,7 @@ function peg$parse(input, options) { s3 = peg$parseS_(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f180(s2); + s0 = peg$f190(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13256,7 +13913,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e257); } + if (peg$silentFails === 0) { peg$fail(peg$e262); } } return s0; @@ -13280,7 +13937,7 @@ function peg$parse(input, options) { s4 = peg$parseZ(); if (s4 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f181(s2); + s0 = peg$f191(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13310,7 +13967,7 @@ function peg$parse(input, options) { s5 = peg$parseZ(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f182(s2); + s0 = peg$f192(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13331,7 +13988,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e258); } + if (peg$silentFails === 0) { peg$fail(peg$e263); } } return s0; @@ -13359,7 +14016,7 @@ function peg$parse(input, options) { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f183(s2); + s0 = peg$f193(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13371,7 +14028,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e259); } + if (peg$silentFails === 0) { peg$fail(peg$e264); } } return s0; @@ -13391,7 +14048,7 @@ function peg$parse(input, options) { s2 = peg$parsenum(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f184(s2); + s0 = peg$f194(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13399,7 +14056,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e260); } + if (peg$silentFails === 0) { peg$fail(peg$e265); } } return s0; @@ -13419,7 +14076,7 @@ function peg$parse(input, options) { s2 = peg$parsenum(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f185(s2); + s0 = peg$f195(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13427,7 +14084,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e260); } + if (peg$silentFails === 0) { peg$fail(peg$e265); } } return s0; @@ -13450,12 +14107,12 @@ function peg$parse(input, options) { if (s3 !== peg$FAILED) { s4 = peg$parseL(); if (s4 !== peg$FAILED) { - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c210) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c215) { s5 = input.charAt(peg$currPos); peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e262); } + if (peg$silentFails === 0) { peg$fail(peg$e267); } } if (s5 !== peg$FAILED) { s6 = peg$parsew(); @@ -13471,7 +14128,7 @@ function peg$parse(input, options) { } if (s9 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f186(s7); + s0 = peg$f196(s7); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13510,12 +14167,12 @@ function peg$parse(input, options) { if (s3 !== peg$FAILED) { s4 = peg$parseL(); if (s4 !== peg$FAILED) { - if (input.substr(peg$currPos, 1).toLowerCase() === peg$c210) { + if (input.substr(peg$currPos, 1).toLowerCase() === peg$c215) { s5 = input.charAt(peg$currPos); peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e262); } + if (peg$silentFails === 0) { peg$fail(peg$e267); } } if (s5 !== peg$FAILED) { s6 = peg$parsew(); @@ -13530,7 +14187,7 @@ function peg$parse(input, options) { } if (s9 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f187(s7); + s0 = peg$f197(s7); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13555,7 +14212,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e261); } + if (peg$silentFails === 0) { peg$fail(peg$e266); } } return s0; @@ -13575,15 +14232,15 @@ function peg$parse(input, options) { s2 = peg$parseident(); if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 40) { - s3 = peg$c210; + s3 = peg$c215; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e264); } + if (peg$silentFails === 0) { peg$fail(peg$e269); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f188(s2); + s0 = peg$f198(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -13595,7 +14252,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e263); } + if (peg$silentFails === 0) { peg$fail(peg$e268); } } return s0; diff --git a/src/parse-css.pegjs b/src/parse-css.pegjs index 9f7d972..a392658 100644 --- a/src/parse-css.pegjs +++ b/src/parse-css.pegjs @@ -136,6 +136,11 @@ declaration / border_bottom_color_dec / border_left_color_dec / border_color_dec + / border_top_left_radius_dec + / border_top_right_radius_dec + / border_bottom_right_radius_dec + / border_bottom_left_radius_dec + / border_radius_dec / border_dec / width_dec / height_dec @@ -666,6 +671,72 @@ border_color_dec return setTopRightBottomLeft({}, 'border', 'Color', s, s, s, s); } +border_top_left_radius_dec + = 'border-top-left-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + return {borderTopLeftRadius: {horizontal: h, vertical: v}}; + } + / 'border-top-left-radius'i S* ':' S* r:(LENGTH / default) { + return {borderTopLeftRadius: r}; + } + +border_top_right_radius_dec + = 'border-top-right-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + return {borderTopRightRadius: {horizontal: h, vertical: v}}; + } + / 'border-top-right-radius'i S* ':' S* r:(LENGTH / default) { + return {borderTopRightRadius: r}; + } + +border_bottom_right_radius_dec + = 'border-bottom-right-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + return {borderBottomRightRadius: {horizontal: h, vertical: v}}; + } + / 'border-bottom-right-radius'i S* ':' S* r:(LENGTH / default) { + return {borderBottomRightRadius: r}; + } + +border_bottom_left_radius_dec + = 'border-bottom-left-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + return {borderBottomLeftRadius: {horizontal: h, vertical: v}}; + } + / 'border-bottom-left-radius'i S* ':' S* r:(LENGTH / default) { + return {borderBottomLeftRadius: r}; + } + +border_radius_dec + = 'border-radius'i S* ':' S* tl:LENGTH S* tr:LENGTH S* br:LENGTH S* bl:LENGTH { + return { + borderTopLeftRadius: tl, + borderTopRightRadius: tr, + borderBottomRightRadius: br, + borderBottomLeftRadius: bl + }; + } + / 'border-radius'i S* ':' S* t:LENGTH S* h:LENGTH S* b:LENGTH { + return { + borderTopLeftRadius: t, + borderTopRightRadius: h, + borderBottomRightRadius: b, + borderBottomLeftRadius: h + }; + } + / 'border-radius'i S* ':' S* v:LENGTH S* h:LENGTH { + return { + borderTopLeftRadius: v, + borderTopRightRadius: h, + borderBottomRightRadius: v, + borderBottomLeftRadius: h + }; + } + / 'border-radius'i S* ':' S* r:(LENGTH / default) { + return { + borderTopLeftRadius: r, + borderTopRightRadius: r, + borderBottomRightRadius: r, + borderBottomLeftRadius: r + }; + } + border_s = '-top' / '-right' / '-bottom' / '-left' border_dec @@ -697,21 +768,7 @@ border_dec if (s) setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); return ret; } - / 'border'i t:border_s? S* ':' S* c:color S* s:border_style S* w:LENGTH? { - const ret = {}; - setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); - setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); - if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); - return ret; - } - / 'border'i t:border_s? S* ':' S* s:border_style S* c:color S* w:LENGTH? { - const ret = {}; - setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); - setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); - if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); - return ret; - } - / 'border'i t:border_s? S* ':' S* w:LENGTH S* { + / 'border'i t:border_s? S* ':' S* c:color S* w:LENGTH S* { return setTopRightBottomLeftOr(t, {}, 'border', 'Width', w, w, w, w); } / 'border'i t:border_s? S* ':' S* c:color S* { diff --git a/src/style.ts b/src/style.ts index 1fdc32a..c390012 100644 --- a/src/style.ts +++ b/src/style.ts @@ -1,124 +1,156 @@ -import {HTMLElement, TextNode} from './dom.ts'; -import {Box} from './layout-box.ts'; +import { HTMLElement, TextNode } from "./dom.ts"; +import { Box } from "./layout-box.ts"; -export const inherited = Symbol('inherited'); +export const inherited = Symbol("inherited"); type Inherited = typeof inherited; -export const initial = Symbol('initial'); +export const initial = Symbol("initial"); type Initial = typeof initial; const LogicalMaps = Object.freeze({ - 'horizontal-tb': Object.freeze({ - marginBlockStart: 'marginTop', - marginBlockEnd: 'marginBottom', - marginLineLeft: 'marginLeft', - marginLineRight: 'marginRight', - paddingBlockStart: 'paddingTop', - paddingBlockEnd: 'paddingBottom', - paddingLineLeft: 'paddingLeft', - paddingLineRight: 'paddingRight', - borderBlockStartWidth: 'borderTopWidth', - borderBlockEndWidth: 'borderBottomWidth', - borderLineLeftWidth: 'borderLeftWidth', - borderLineRightWidth: 'borderRightWidth', - borderBlockStartStyle: 'borderTopStyle', - borderBlockEndStyle: 'borderBottomStyle', - borderLineLeftStyle: 'borderLeftStyle', - borderLineRightStyle: 'borderRightStyle', - blockSize: 'height', - inlineSize: 'width' + "horizontal-tb": Object.freeze({ + marginBlockStart: "marginTop", + marginBlockEnd: "marginBottom", + marginLineLeft: "marginLeft", + marginLineRight: "marginRight", + paddingBlockStart: "paddingTop", + paddingBlockEnd: "paddingBottom", + paddingLineLeft: "paddingLeft", + paddingLineRight: "paddingRight", + borderBlockStartWidth: "borderTopWidth", + borderBlockEndWidth: "borderBottomWidth", + borderLineLeftWidth: "borderLeftWidth", + borderLineRightWidth: "borderRightWidth", + borderBlockStartStyle: "borderTopStyle", + borderBlockEndStyle: "borderBottomStyle", + borderLineLeftStyle: "borderLeftStyle", + borderLineRightStyle: "borderRightStyle", + blockSize: "height", + inlineSize: "width", }), - 'vertical-lr': Object.freeze({ - marginBlockStart: 'marginLeft', - marginBlockEnd: 'marginRight', - marginLineLeft: 'marginTop', - marginLineRight: 'marginBottom', - paddingBlockStart: 'paddingLeft', - paddingBlockEnd: 'paddingRight', - paddingLineLeft: 'paddingTop', - paddingLineRight: 'paddingBottom', - borderBlockStartWidth: 'borderLeftWidth', - borderBlockEndWidth: 'borderRightWidth', - borderLineLeftWidth: 'borderTopWidth', - borderLineRightWidth: 'borderBottomWidth', - borderBlockStartStyle: 'borderLeftStyle', - borderBlockEndStyle: 'borderRightStyle', - borderLineLeftStyle: 'borderTopStyle', - borderLineRightStyle: 'borderBottomStyle', - blockSize: 'width', - inlineSize: 'height' + "vertical-lr": Object.freeze({ + marginBlockStart: "marginLeft", + marginBlockEnd: "marginRight", + marginLineLeft: "marginTop", + marginLineRight: "marginBottom", + paddingBlockStart: "paddingLeft", + paddingBlockEnd: "paddingRight", + paddingLineLeft: "paddingTop", + paddingLineRight: "paddingBottom", + borderBlockStartWidth: "borderLeftWidth", + borderBlockEndWidth: "borderRightWidth", + borderLineLeftWidth: "borderTopWidth", + borderLineRightWidth: "borderBottomWidth", + borderBlockStartStyle: "borderLeftStyle", + borderBlockEndStyle: "borderRightStyle", + borderLineLeftStyle: "borderTopStyle", + borderLineRightStyle: "borderBottomStyle", + blockSize: "width", + inlineSize: "height", + }), + "vertical-rl": Object.freeze({ + marginBlockStart: "marginRight", + marginBlockEnd: "marginLeft", + marginLineLeft: "marginTop", + marginLineRight: "marginBottom", + paddingBlockStart: "paddingRight", + paddingBlockEnd: "paddingLeft", + paddingLineLeft: "paddingTop", + paddingLineRight: "paddingBottom", + borderBlockStartWidth: "borderRightWidth", + borderBlockEndWidth: "borderLeftWidth", + borderLineLeftWidth: "borderTopWidth", + borderLineRightWidth: "borderBottomWidth", + borderBlockStartStyle: "borderRightStyle", + borderBlockEndStyle: "borderLeftStyle", + borderLineLeftStyle: "borderTopStyle", + borderLineRightStyle: "borderBottomStyle", + blockSize: "width", + inlineSize: "height", }), - 'vertical-rl': Object.freeze({ - marginBlockStart: 'marginRight', - marginBlockEnd: 'marginLeft', - marginLineLeft: 'marginTop', - marginLineRight: 'marginBottom', - paddingBlockStart: 'paddingRight', - paddingBlockEnd: 'paddingLeft', - paddingLineLeft: 'paddingTop', - paddingLineRight: 'paddingBottom', - borderBlockStartWidth: 'borderRightWidth', - borderBlockEndWidth: 'borderLeftWidth', - borderLineLeftWidth: 'borderTopWidth', - borderLineRightWidth: 'borderBottomWidth', - borderBlockStartStyle: 'borderRightStyle', - borderBlockEndStyle: 'borderLeftStyle', - borderLineLeftStyle: 'borderTopStyle', - borderLineRightStyle: 'borderBottomStyle', - blockSize: 'width', - inlineSize: 'height' - }) }); -export type WhiteSpace = 'normal' | 'nowrap' | 'pre-wrap' | 'pre-line' | 'pre'; +export type WhiteSpace = "normal" | "nowrap" | "pre-wrap" | "pre-line" | "pre"; + +type Length = + | number + | { value: number; unit: "em" | "pt" | "pc" | "px" | "cm" | "mm" | "in" }; -type Length = number | {value: number, unit: 'em'}; +type Percentage = { value: number; unit: "%" }; -type Percentage = {value: number, unit: '%'}; +type Number = { value: number; unit: null }; -type Number = {value: number, unit: null}; +export type FontWeight = number | "normal" | "bold" | "bolder" | "lighter"; -export type FontWeight = number | 'normal' | 'bold' | 'bolder' | 'lighter'; +export type FontStyle = "normal" | "italic" | "oblique"; -export type FontStyle = 'normal' | 'italic' | 'oblique'; +export type FontVariant = "normal" | "small-caps"; -export type FontVariant = 'normal' | 'small-caps'; +export type FontStretch = + | "normal" + | "ultra-condensed" + | "extra-condensed" + | "condensed" + | "semi-condensed" + | "semi-expanded" + | "expanded" + | "extra-expanded" + | "ultra-expanded"; -export type FontStretch = 'normal' | 'ultra-condensed' | 'extra-condensed' | 'condensed' - | 'semi-condensed' | 'semi-expanded' | 'expanded' - | 'extra-expanded' | 'ultra-expanded'; +type VerticalAlign = + | "baseline" + | "middle" + | "sub" + | "super" + | "text-top" + | "text-bottom" + | Length + | Percentage + | "top" + | "bottom"; -type VerticalAlign = 'baseline' | 'middle' | 'sub' | 'super' | 'text-top' - | 'text-bottom' | Length | Percentage | 'top' | 'bottom'; +type BackgroundClip = "border-box" | "padding-box" | "content-box"; -type BackgroundClip = 'border-box' | 'padding-box' | 'content-box'; +export type Direction = "ltr" | "rtl"; -export type Direction = 'ltr' | 'rtl'; +type Display = { outer: OuterDisplay; inner: InnerDisplay }; -type Display = {outer: OuterDisplay, inner: InnerDisplay}; +export type WritingMode = "horizontal-tb" | "vertical-lr" | "vertical-rl"; -export type WritingMode = 'horizontal-tb' | 'vertical-lr' | 'vertical-rl'; +type Position = "absolute" | "relative" | "static"; -type Position = 'absolute' | 'relative' | 'static'; +export type Color = { r: number; g: number; b: number; a: number }; -export type Color = {r: number, g: number, b: number, a: number}; +type OuterDisplay = "inline" | "block" | "none"; -type OuterDisplay = 'inline' | 'block' | 'none'; +type InnerDisplay = "flow" | "flow-root" | "none"; -type InnerDisplay = 'flow' | 'flow-root' | 'none'; +type BorderStyle = + | "none" + | "hidden" + | "dotted" + | "dashed" + | "solid" + | "double" + | "groove" + | "ridge" + | "inset" + | "outset"; -type BorderStyle = 'none' | 'hidden' | 'dotted' | 'dashed' | 'solid' - | 'double' | 'groove' | 'ridge' | 'inset' | 'outset' +type BoxSizing = "border-box" | "content-box" | "padding-box"; -type BoxSizing = 'border-box' | 'content-box' | 'padding-box'; +export type TextAlign = "start" | "end" | "left" | "right" | "center"; -export type TextAlign = 'start' | 'end' | 'left' | 'right' | 'center'; +type Float = "left" | "right" | "none"; -type Float = 'left' | 'right' | 'none'; +type Clear = "left" | "right" | "both" | "none"; -type Clear = 'left' | 'right' | 'both' | 'none'; +type BorderRadius = + | Length + | Percentage + | { horizontal: Length | Percentage; vertical: Length | Percentage }; export interface DeclaredStyleProperties { zoom?: number | Percentage | Inherited | Initial; @@ -130,7 +162,7 @@ export interface DeclaredStyleProperties { fontStyle?: FontStyle | Inherited | Initial; fontStretch?: FontStretch | Inherited | Initial; fontFamily?: string[] | Inherited | Initial; - lineHeight?: 'normal' | Length | Percentage | Number | Inherited | Initial; + lineHeight?: "normal" | Length | Percentage | Number | Inherited | Initial; verticalAlign?: VerticalAlign; backgroundColor?: Color | Inherited | Initial; backgroundClip?: BackgroundClip | Inherited | Initial; @@ -149,30 +181,34 @@ export interface DeclaredStyleProperties { borderRightColor?: Color | Inherited | Initial; borderBottomColor?: Color | Inherited | Initial; borderLeftColor?: Color | Inherited | Initial; + borderTopLeftRadius?: BorderRadius | Inherited | Initial; + borderTopRightRadius?: BorderRadius | Inherited | Initial; + borderBottomRightRadius?: BorderRadius | Inherited | Initial; + borderBottomLeftRadius?: BorderRadius | Inherited | Initial; paddingTop?: Length | Percentage | Inherited | Initial; paddingRight?: Length | Percentage | Inherited | Initial; paddingBottom?: Length | Percentage | Inherited | Initial; paddingLeft?: Length | Percentage | Inherited | Initial; - marginTop?: Length | Percentage | 'auto' | Inherited | Initial; - marginRight?: Length | Percentage | 'auto' | Inherited | Initial; - marginBottom?: Length | Percentage | 'auto' | Inherited | Initial; - marginLeft?: Length | Percentage | 'auto' | Inherited | Initial; + marginTop?: Length | Percentage | "auto" | Inherited | Initial; + marginRight?: Length | Percentage | "auto" | Inherited | Initial; + marginBottom?: Length | Percentage | "auto" | Inherited | Initial; + marginLeft?: Length | Percentage | "auto" | Inherited | Initial; tabSize?: Length | Number | Inherited | Initial; position?: Position | Inherited | Initial; - width?: Length | Percentage | 'auto' | Inherited | Initial; - height?: Length | Percentage | 'auto' | Inherited | Initial; - top?: Length | Percentage | 'auto' | Inherited | Initial; - right?: Length | Percentage | 'auto' | Inherited | Initial; - bottom?: Length | Percentage | 'auto' | Inherited | Initial; - left?: Length | Percentage | 'auto' | Inherited | Initial; + width?: Length | Percentage | "auto" | Inherited | Initial; + height?: Length | Percentage | "auto" | Inherited | Initial; + top?: Length | Percentage | "auto" | Inherited | Initial; + right?: Length | Percentage | "auto" | Inherited | Initial; + bottom?: Length | Percentage | "auto" | Inherited | Initial; + left?: Length | Percentage | "auto" | Inherited | Initial; boxSizing?: BoxSizing | Inherited | Initial; textAlign?: TextAlign | Inherited | Initial; float?: Float | Inherited | Initial; clear?: Clear | Inherited | Initial; - zIndex?: number | 'auto' | Inherited | Initial; - wordBreak?: 'break-word' | 'normal' | Inherited | Initial; - overflowWrap?: 'anywhere' | 'break-word' | 'normal' | Inherited | Initial; - overflow?: 'visible' | 'hidden' | Inherited | Initial; + zIndex?: number | "auto" | Inherited | Initial; + wordBreak?: "break-word" | "normal" | Inherited | Initial; + overflowWrap?: "anywhere" | "break-word" | "normal" | Inherited | Initial; + overflow?: "visible" | "hidden" | Inherited | Initial; } const EMPTY_ARRAY: readonly number[] = Object.freeze([]); @@ -198,12 +234,16 @@ export class DeclaredStyle { /** `styles` must be sorted */ isComposedOf(styles: DeclaredStyle[]) { - return this.composition.length === styles.length - && this.composition.every((id, i) => id === styles[i].id); + return ( + this.composition.length === styles.length && + this.composition.every((id, i) => id === styles[i].id) + ); } } -export function createDeclaredStyle(properties: DeclaredStyleProperties): DeclaredStyle { +export function createDeclaredStyle( + properties: DeclaredStyleProperties +): DeclaredStyle { return new DeclaredStyle(properties); } @@ -212,13 +252,13 @@ export const EMPTY_STYLE = createDeclaredStyle({}); /** `styles` must be sorted */ function createCascadedStyle(styles: DeclaredStyle[]) { if (styles.length > 0) { - const composition = styles.map(s => s.id); + const composition = styles.map((s) => s.id); let properties; if (styles.length === 2) { - properties = {...styles[0].properties, ...styles[1].properties}; + properties = { ...styles[0].properties, ...styles[1].properties }; } else { - properties = Object.assign({}, ...styles.map(s => s.properties)); + properties = Object.assign({}, ...styles.map((s) => s.properties)); } return new DeclaredStyle(properties, composition); @@ -237,7 +277,7 @@ interface ComputedStyle { fontStyle: FontStyle; fontStretch: FontStretch; fontFamily: string[]; - lineHeight: 'normal' | number | {value: number, unit: null}; + lineHeight: "normal" | number | { value: number; unit: null }; verticalAlign: VerticalAlign; backgroundColor: Color; backgroundClip: BackgroundClip; @@ -256,44 +296,65 @@ interface ComputedStyle { borderRightColor: Color; borderBottomColor: Color; borderLeftColor: Color; + borderTopLeftRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; + borderTopRightRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; + borderBottomRightRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; + borderBottomLeftRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; paddingTop: number | Percentage; paddingRight: number | Percentage; paddingBottom: number | Percentage; paddingLeft: number | Percentage; - marginTop: number | Percentage | 'auto'; - marginRight: number | Percentage | 'auto'; - marginBottom: number | Percentage | 'auto'; - marginLeft: number | Percentage | 'auto'; + marginTop: number | Percentage | "auto"; + marginRight: number | Percentage | "auto"; + marginBottom: number | Percentage | "auto"; + marginLeft: number | Percentage | "auto"; tabSize: number | Number; position: Position; - width: number | Percentage | 'auto'; - height: number | Percentage | 'auto'; - top: number | Percentage | 'auto'; - right: number | Percentage | 'auto'; - bottom: number | Percentage | 'auto'; - left: number | Percentage | 'auto'; + width: number | Percentage | "auto"; + height: number | Percentage | "auto"; + top: number | Percentage | "auto"; + right: number | Percentage | "auto"; + bottom: number | Percentage | "auto"; + left: number | Percentage | "auto"; boxSizing: BoxSizing; textAlign: TextAlign; float: Float; clear: Clear; - zIndex: number | 'auto'; - wordBreak: 'break-word' | 'normal'; - overflowWrap: 'anywhere' | 'break-word' | 'normal'; - overflow: 'visible' | 'hidden'; + zIndex: number | "auto"; + wordBreak: "break-word" | "normal"; + overflowWrap: "anywhere" | "break-word" | "normal"; + overflow: "visible" | "hidden"; } -function resolvePercent(box: Box, cssVal: number | {value: number, unit: '%'}) { - if (typeof cssVal === 'object') { - if (box.containingBlock.width === undefined) throw new Error('Assertion failed'); - const inlineSize = box.containingBlock[LogicalMaps[box.writingModeAsParticipant].inlineSize]; - if (inlineSize === undefined) throw new Error('Assertion failed'); - return cssVal.value / 100 * inlineSize; +function resolvePercent( + box: Box, + cssVal: number | { value: number; unit: "%" } +) { + if (typeof cssVal === "object") { + if (box.containingBlock.width === undefined) + throw new Error("Assertion failed"); + const inlineSize = + box.containingBlock[LogicalMaps[box.writingModeAsParticipant].inlineSize]; + if (inlineSize === undefined) throw new Error("Assertion failed"); + return (cssVal.value / 100) * inlineSize; } return cssVal; } -function percentGtZero(cssVal: number | {value: number, unit: '%'}) { - return typeof cssVal === 'object' ? cssVal.value > 0 : cssVal > 0; +function percentGtZero(cssVal: number | { value: number; unit: "%" }) { + return typeof cssVal === "object" ? cssVal.value > 0 : cssVal > 0; } export class Style { @@ -315,7 +376,7 @@ export class Style { fontStyle: FontStyle; fontStretch: FontStretch; fontFamily: string[]; - lineHeight: 'normal' | number; + lineHeight: "normal" | number; verticalAlign: VerticalAlign; backgroundColor: Color; backgroundClip: BackgroundClip; @@ -334,38 +395,54 @@ export class Style { borderRightColor: Color; borderBottomColor: Color; borderLeftColor: Color; + borderTopLeftRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; + borderTopRightRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; + borderBottomRightRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; + borderBottomLeftRadius: + | number + | Percentage + | { horizontal: number | Percentage; vertical: number | Percentage }; paddingTop: number | Percentage; paddingRight: number | Percentage; paddingBottom: number | Percentage; paddingLeft: number | Percentage; - marginTop: number | Percentage | 'auto'; - marginRight: number | Percentage | 'auto'; - marginBottom: number | Percentage | 'auto'; - marginLeft: number | Percentage | 'auto'; + marginTop: number | Percentage | "auto"; + marginRight: number | Percentage | "auto"; + marginBottom: number | Percentage | "auto"; + marginLeft: number | Percentage | "auto"; tabSize: number | Number; position: Position; - width: number | Percentage | 'auto'; - height: number | Percentage | 'auto'; - top: number | Percentage | 'auto'; - right: number | Percentage | 'auto'; - bottom: number | Percentage | 'auto'; - left: number | Percentage | 'auto'; + width: number | Percentage | "auto"; + height: number | Percentage | "auto"; + top: number | Percentage | "auto"; + right: number | Percentage | "auto"; + bottom: number | Percentage | "auto"; + left: number | Percentage | "auto"; boxSizing: BoxSizing; textAlign: TextAlign; float: Float; clear: Clear; - zIndex: number | 'auto'; - wordBreak: 'break-word' | 'normal'; - overflowWrap: 'anywhere' | 'break-word' | 'normal'; - overflow: 'visible' | 'hidden'; + zIndex: number | "auto"; + wordBreak: "break-word" | "normal"; + overflowWrap: "anywhere" | "break-word" | "normal"; + overflow: "visible" | "hidden"; // This section reduces to used values as much as possible // Be careful accessing off of "this" since these are called in the ctor private usedLineHeight(style: ComputedStyle) { - if (typeof style.lineHeight === 'object') { + if (typeof style.lineHeight === "object") { return style.lineHeight.value * this.fontSize; - } else if (typeof style.lineHeight === 'number') { + } else if (typeof style.lineHeight === "number") { return this.usedLength(style.lineHeight); } else { return style.lineHeight; @@ -382,10 +459,14 @@ export class Style { } private usedMaybeLength(length: T) { - return typeof length === 'number' ? this.usedLength(length) : length; + return typeof length === "number" ? this.usedLength(length) : length; } - constructor(style: ComputedStyle, parent?: Style, cascadedStyle?: DeclaredStyle) { + constructor( + style: ComputedStyle, + parent?: Style, + cascadedStyle?: DeclaredStyle + ) { this.id = ++id; this.computed = style; this.blockified = false; @@ -444,29 +525,33 @@ export class Style { this.wordBreak = style.wordBreak; this.overflowWrap = style.overflowWrap; this.overflow = style.overflow; + this.borderTopLeftRadius = style.borderTopLeftRadius; + this.borderTopRightRadius = style.borderTopRightRadius; + this.borderBottomRightRadius = style.borderBottomRightRadius; + this.borderBottomLeftRadius = style.borderBottomLeftRadius; } blockify() { - if (!this.blockified && this.display.outer === 'inline') { - this.display = {outer: 'block', inner: this.display.inner}; + if (!this.blockified && this.display.outer === "inline") { + this.display = { outer: "block", inner: this.display.inner }; this.blockified = true; } } getTextAlign() { - if (this.textAlign === 'start') { - if (this.direction === 'ltr') { - return 'left'; + if (this.textAlign === "start") { + if (this.direction === "ltr") { + return "left"; } else { - return 'right'; + return "right"; } } - if (this.textAlign === 'end') { - if (this.direction === 'ltr') { - return 'right'; + if (this.textAlign === "end") { + if (this.direction === "ltr") { + return "right"; } else { - return 'left'; + return "left"; } } @@ -474,129 +559,157 @@ export class Style { } isOutOfFlow() { - return this.float !== 'none'; // TODO: or this.position === 'absolute' + return this.float !== "none"; // TODO: or this.position === 'absolute' } isWsCollapsible() { const whiteSpace = this.whiteSpace; - return whiteSpace === 'normal' - || whiteSpace === 'nowrap' - || whiteSpace === 'pre-line'; + return ( + whiteSpace === "normal" || + whiteSpace === "nowrap" || + whiteSpace === "pre-line" + ); } hasPaddingArea() { - return percentGtZero(this.paddingTop) - || percentGtZero(this.paddingRight) - || percentGtZero(this.paddingBottom) - || percentGtZero(this.paddingLeft); + return ( + percentGtZero(this.paddingTop) || + percentGtZero(this.paddingRight) || + percentGtZero(this.paddingBottom) || + percentGtZero(this.paddingLeft) + ); } hasBorderArea() { - return this.borderTopWidth > 0 && this.borderTopStyle !== 'none' - || this.borderRightWidth > 0 && this.borderRightStyle !== 'none' - || this.borderBottomWidth > 0 && this.borderBottomStyle !== 'none' - || this.borderLeftWidth > 0 && this.borderLeftStyle !== 'none'; + return ( + (this.borderTopWidth > 0 && this.borderTopStyle !== "none") || + (this.borderRightWidth > 0 && this.borderRightStyle !== "none") || + (this.borderBottomWidth > 0 && this.borderBottomStyle !== "none") || + (this.borderLeftWidth > 0 && this.borderLeftStyle !== "none") + ); } hasPaint() { - return this.backgroundColor.a > 0 - || this.borderTopWidth > 0 - && this.borderTopColor.a > 0 - && this.borderTopStyle !== 'none' - || this.borderRightWidth > 0 - && this.borderRightColor.a > 0 - && this.borderRightStyle !== 'none' - || this.borderBottomWidth > 0 - && this.borderBottomColor.a > 0 - && this.borderBottomStyle !== 'none' - || this.borderLeftWidth > 0 - && this.borderLeftColor.a > 0 - && this.borderLeftStyle !== 'none'; + return ( + this.backgroundColor.a > 0 || + (this.borderTopWidth > 0 && + this.borderTopColor.a > 0 && + this.borderTopStyle !== "none") || + (this.borderRightWidth > 0 && + this.borderRightColor.a > 0 && + this.borderRightStyle !== "none") || + (this.borderBottomWidth > 0 && + this.borderBottomColor.a > 0 && + this.borderBottomStyle !== "none") || + (this.borderLeftWidth > 0 && + this.borderLeftColor.a > 0 && + this.borderLeftStyle !== "none") + ); } getMarginBlockStart(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].marginBlockStart]; - if (cssVal === 'auto') return cssVal; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].marginBlockStart]; + if (cssVal === "auto") return cssVal; return resolvePercent(box, cssVal); } getMarginBlockEnd(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].marginBlockEnd]; - if (cssVal === 'auto') return cssVal; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].marginBlockEnd]; + if (cssVal === "auto") return cssVal; return resolvePercent(box, cssVal); } getMarginLineLeft(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].marginLineLeft]; - if (cssVal === 'auto') return cssVal; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].marginLineLeft]; + if (cssVal === "auto") return cssVal; return resolvePercent(box, cssVal); } getMarginLineRight(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].marginLineRight]; - if (cssVal === 'auto') return cssVal; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].marginLineRight]; + if (cssVal === "auto") return cssVal; return resolvePercent(box, cssVal); } getPaddingBlockStart(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].paddingBlockStart]; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].paddingBlockStart]; return resolvePercent(box, cssVal); } getPaddingBlockEnd(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].paddingBlockEnd]; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].paddingBlockEnd]; return resolvePercent(box, cssVal); } getPaddingLineLeft(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].paddingLineLeft]; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].paddingLineLeft]; return resolvePercent(box, cssVal); } getPaddingLineRight(box: Box) { - const cssVal = this[LogicalMaps[box.writingModeAsParticipant].paddingLineRight]; + const cssVal = + this[LogicalMaps[box.writingModeAsParticipant].paddingLineRight]; return resolvePercent(box, cssVal); } getBorderBlockStartWidth(box: Box) { - let cssStyleVal = this[LogicalMaps[box.writingModeAsParticipant].borderBlockStartStyle]; - if (cssStyleVal === 'none') return 0; - const cssWidthVal = this[LogicalMaps[box.writingModeAsParticipant].borderBlockStartWidth]; + let cssStyleVal = + this[LogicalMaps[box.writingModeAsParticipant].borderBlockStartStyle]; + if (cssStyleVal === "none") return 0; + const cssWidthVal = + this[LogicalMaps[box.writingModeAsParticipant].borderBlockStartWidth]; return resolvePercent(box, cssWidthVal); } getBorderBlockEndWidth(box: Box) { - const cssStyleVal = this[LogicalMaps[box.writingModeAsParticipant].borderBlockEndStyle]; - if (cssStyleVal === 'none') return 0; - const cssWidthVal = this[LogicalMaps[box.writingModeAsParticipant].borderBlockEndWidth]; + const cssStyleVal = + this[LogicalMaps[box.writingModeAsParticipant].borderBlockEndStyle]; + if (cssStyleVal === "none") return 0; + const cssWidthVal = + this[LogicalMaps[box.writingModeAsParticipant].borderBlockEndWidth]; return resolvePercent(box, cssWidthVal); } getBorderLineLeftWidth(box: Box) { - const cssStyleVal = this[LogicalMaps[box.writingModeAsParticipant].borderLineLeftStyle]; - if (cssStyleVal === 'none') return 0; - const cssWidthVal = this[LogicalMaps[box.writingModeAsParticipant].borderLineLeftWidth] + const cssStyleVal = + this[LogicalMaps[box.writingModeAsParticipant].borderLineLeftStyle]; + if (cssStyleVal === "none") return 0; + const cssWidthVal = + this[LogicalMaps[box.writingModeAsParticipant].borderLineLeftWidth]; return resolvePercent(box, cssWidthVal); } getBorderLineRightWidth(box: Box) { - const cssStyleVal = this[LogicalMaps[box.writingModeAsParticipant].borderLineRightStyle]; - if (cssStyleVal === 'none') return 0; - const cssWidthVal = this[LogicalMaps[box.writingModeAsParticipant].borderLineRightWidth]; + const cssStyleVal = + this[LogicalMaps[box.writingModeAsParticipant].borderLineRightStyle]; + if (cssStyleVal === "none") return 0; + const cssWidthVal = + this[LogicalMaps[box.writingModeAsParticipant].borderLineRightWidth]; return resolvePercent(box, cssWidthVal); } getBlockSize(box: Box) { let cssVal = this[LogicalMaps[box.writingModeAsParticipant].blockSize]; - if (typeof cssVal === 'object') { - const parentBlockSize = box.containingBlock[LogicalMaps[box.writingModeAsParticipant].blockSize]; - if (parentBlockSize === undefined) return 'auto' as const; // §CSS2 10.5 - cssVal = cssVal.value / 100 * parentBlockSize; + if (typeof cssVal === "object") { + const parentBlockSize = + box.containingBlock[ + LogicalMaps[box.writingModeAsParticipant].blockSize + ]; + if (parentBlockSize === undefined) return "auto" as const; // §CSS2 10.5 + cssVal = (cssVal.value / 100) * parentBlockSize; } - if (this.boxSizing !== 'content-box' && cssVal !== 'auto') { + if (this.boxSizing !== "content-box" && cssVal !== "auto") { cssVal -= this.getPaddingBlockStart(box) + this.getPaddingBlockEnd(box); - if (this.boxSizing === 'border-box') { - cssVal -= this.getBorderBlockStartWidth(box) + this.getBorderBlockEndWidth(box); + if (this.boxSizing === "border-box") { + cssVal -= + this.getBorderBlockStartWidth(box) + this.getBorderBlockEndWidth(box); } cssVal = Math.max(0, cssVal); } @@ -605,15 +718,16 @@ export class Style { getInlineSize(box: Box) { let cssVal = this[LogicalMaps[box.writingModeAsParticipant].inlineSize]; - if (cssVal === 'auto') { - cssVal = 'auto'; + if (cssVal === "auto") { + cssVal = "auto"; } else { cssVal = resolvePercent(box, cssVal); } - if (this.boxSizing !== 'content-box' && cssVal !== 'auto') { + if (this.boxSizing !== "content-box" && cssVal !== "auto") { cssVal -= this.getPaddingLineLeft(box) + this.getPaddingLineRight(box); - if (this.boxSizing === 'border-box') { - cssVal -= this.getBorderLineLeftWidth(box) + this.getBorderLineRightWidth(box); + if (this.boxSizing === "border-box") { + cssVal -= + this.getBorderLineLeftWidth(box) + this.getBorderLineRightWidth(box); } cssVal = Math.max(0, cssVal); } @@ -623,37 +737,94 @@ export class Style { hasLineLeftGap(box: Box) { const writingMode = box.writingModeAsParticipant; const marginLineLeft = this[LogicalMaps[writingMode].marginLineLeft]; - if (marginLineLeft === 'auto') return false; - if (typeof marginLineLeft === 'object' && marginLineLeft.value !== 0) return true; - if (typeof marginLineLeft !== 'object' && marginLineLeft !== 0) return true; + if (marginLineLeft === "auto") return false; + if (typeof marginLineLeft === "object" && marginLineLeft.value !== 0) + return true; + if (typeof marginLineLeft !== "object" && marginLineLeft !== 0) return true; const paddingLineLeft = this[LogicalMaps[writingMode].paddingLineLeft]; - if (typeof paddingLineLeft === 'object' && paddingLineLeft.value > 0) return true; - if (typeof paddingLineLeft !== 'object' && paddingLineLeft > 0) return true; - if (this[LogicalMaps[writingMode].borderLineLeftStyle] === 'none') return false; + if (typeof paddingLineLeft === "object" && paddingLineLeft.value > 0) + return true; + if (typeof paddingLineLeft !== "object" && paddingLineLeft > 0) return true; + if (this[LogicalMaps[writingMode].borderLineLeftStyle] === "none") + return false; if (this[LogicalMaps[writingMode].borderLineLeftWidth] > 0) return true; } hasLineRightGap(box: Box) { const writingMode = box.writingModeAsParticipant; const marginLineRight = this[LogicalMaps[writingMode].marginLineRight]; - if (marginLineRight === 'auto') return false; - if (typeof marginLineRight === 'object' && marginLineRight.value !== 0) return true; - if (typeof marginLineRight !== 'object' && marginLineRight !== 0) return true; + if (marginLineRight === "auto") return false; + if (typeof marginLineRight === "object" && marginLineRight.value !== 0) + return true; + if (typeof marginLineRight !== "object" && marginLineRight !== 0) + return true; const paddingLineRight = this[LogicalMaps[writingMode].paddingLineRight]; - if (typeof paddingLineRight === 'object' && paddingLineRight.value > 0) return true; - if (typeof paddingLineRight !== 'object' && paddingLineRight > 0) return true; - if (this[LogicalMaps[writingMode].borderLineRightStyle] === 'none') return false; + if (typeof paddingLineRight === "object" && paddingLineRight.value > 0) + return true; + if (typeof paddingLineRight !== "object" && paddingLineRight > 0) + return true; + if (this[LogicalMaps[writingMode].borderLineRightStyle] === "none") + return false; if (this[LogicalMaps[writingMode].borderLineRightWidth] > 0) return true; } + getBorderTopLeftRadius(box: Box) { + const radius = this.borderTopLeftRadius; + if (typeof radius === "object" && "horizontal" in radius) { + return { + horizontal: resolvePercent(box, radius.horizontal), + vertical: resolvePercent(box, radius.vertical), + }; + } + const resolved = resolvePercent(box, radius); + return { horizontal: resolved, vertical: resolved }; + } + + getBorderTopRightRadius(box: Box) { + const radius = this.borderTopRightRadius; + if (typeof radius === "object" && "horizontal" in radius) { + return { + horizontal: resolvePercent(box, radius.horizontal), + vertical: resolvePercent(box, radius.vertical), + }; + } + const resolved = resolvePercent(box, radius); + return { horizontal: resolved, vertical: resolved }; + } + + getBorderBottomRightRadius(box: Box) { + const radius = this.borderBottomRightRadius; + if (typeof radius === "object" && "horizontal" in radius) { + return { + horizontal: resolvePercent(box, radius.horizontal), + vertical: resolvePercent(box, radius.vertical), + }; + } + const resolved = resolvePercent(box, radius); + return { horizontal: resolved, vertical: resolved }; + } + + getBorderBottomLeftRadius(box: Box) { + const radius = this.borderBottomLeftRadius; + if (typeof radius === "object" && "horizontal" in radius) { + return { + horizontal: resolvePercent(box, radius.horizontal), + vertical: resolvePercent(box, radius.vertical), + }; + } + const resolved = resolvePercent(box, radius); + return { horizontal: resolved, vertical: resolved }; + } + fontsEqual(style: Style, size = true) { if ( - size && this.fontSize !== style.fontSize || + (size && this.fontSize !== style.fontSize) || this.fontVariant !== style.fontVariant || this.fontWeight !== style.fontWeight || this.fontStyle !== style.fontStyle || this.fontFamily.length !== style.fontFamily.length - ) return false; + ) + return false; for (let i = 0, l = style.fontFamily.length; i < l; i++) { if (style.fontFamily[i] !== this.fontFamily[i]) return false; @@ -669,33 +840,33 @@ export class Style { // "computed value"s as described in CSS Cascading and Inheritance Level 4 § 4.4 const initialPlainStyle: ComputedStyle = Object.freeze({ zoom: 1, - whiteSpace: 'normal', - color: {r: 0, g: 0, b: 0, a: 1}, + whiteSpace: "normal", + color: { r: 0, g: 0, b: 0, a: 1 }, fontSize: 16, fontWeight: 400, - fontVariant: 'normal', - fontStyle: 'normal', - fontFamily: ['Helvetica'], - fontStretch: 'normal', - lineHeight: 'normal', - verticalAlign: 'baseline', - backgroundColor: {r: 0, g: 0, b: 0, a: 0}, - backgroundClip: 'border-box', - display: {outer: 'inline' as const, inner: 'flow' as const}, - direction: 'ltr', - writingMode: 'horizontal-tb', + fontVariant: "normal", + fontStyle: "normal", + fontFamily: ["Helvetica"], + fontStretch: "normal", + lineHeight: "normal", + verticalAlign: "baseline", + backgroundColor: { r: 0, g: 0, b: 0, a: 0 }, + backgroundClip: "border-box", + display: { outer: "inline" as const, inner: "flow" as const }, + direction: "ltr", + writingMode: "horizontal-tb", borderTopWidth: 0, borderRightWidth: 0, borderBottomWidth: 0, borderLeftWidth: 0, - borderTopStyle: 'none', - borderRightStyle: 'none', - borderBottomStyle: 'none', - borderLeftStyle: 'none', - borderTopColor: {r: 0, g: 0, b: 0, a: 0}, - borderRightColor: {r: 0, g: 0, b: 0, a: 0}, - borderBottomColor: {r: 0, g: 0, b: 0, a: 0}, - borderLeftColor: {r: 0, g: 0, b: 0, a: 0}, + borderTopStyle: "none", + borderRightStyle: "none", + borderBottomStyle: "none", + borderLeftStyle: "none", + borderTopColor: { r: 0, g: 0, b: 0, a: 0 }, + borderRightColor: { r: 0, g: 0, b: 0, a: 0 }, + borderBottomColor: { r: 0, g: 0, b: 0, a: 0 }, + borderLeftColor: { r: 0, g: 0, b: 0, a: 0 }, paddingTop: 0, paddingRight: 0, paddingBottom: 0, @@ -704,22 +875,26 @@ const initialPlainStyle: ComputedStyle = Object.freeze({ marginRight: 0, marginBottom: 0, marginLeft: 0, - tabSize: {value: 8, unit: null}, - position: 'static', - width: 'auto', - height: 'auto', - top: 'auto', - right: 'auto', - bottom: 'auto', - left: 'auto', - boxSizing: 'content-box', - textAlign: 'start', - float: 'none', - clear: 'none', - zIndex: 'auto', - wordBreak: 'normal', - overflowWrap: 'normal', - overflow: 'visible' + tabSize: { value: 8, unit: null }, + position: "static", + width: "auto", + height: "auto", + top: "auto", + right: "auto", + bottom: "auto", + left: "auto", + boxSizing: "content-box", + textAlign: "start", + float: "none", + clear: "none", + zIndex: "auto", + wordBreak: "normal", + overflowWrap: "normal", + overflow: "visible", + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + borderBottomLeftRadius: 0, }); let originStyle = new Style(initialPlainStyle); @@ -739,10 +914,12 @@ export function getOriginStyle() { * called when devicePixelRatio actually changes. */ export function setOriginStyle(style: Partial) { - originStyle = new Style({...initialPlainStyle, ...style}); + originStyle = new Style({ ...initialPlainStyle, ...style }); } -type InheritedStyleDefinitions = {[K in keyof DeclaredStyleProperties]: boolean}; +type InheritedStyleDefinitions = { + [K in keyof DeclaredStyleProperties]: boolean; +}; // Each CSS property defines whether or not it's inherited const inheritedStyle: InheritedStyleDefinitions = Object.freeze({ @@ -797,102 +974,106 @@ const inheritedStyle: InheritedStyleDefinitions = Object.freeze({ zIndex: false, wordBreak: true, overflowWrap: true, - overflow: false + overflow: false, + borderTopLeftRadius: false, + borderTopRightRadius: false, + borderBottomRightRadius: false, + borderBottomLeftRadius: false, }); -type UaDeclaredStyles = {[tagName: string]: DeclaredStyle}; +type UaDeclaredStyles = { [tagName: string]: DeclaredStyle }; export const uaDeclaredStyles: UaDeclaredStyles = Object.freeze({ div: createDeclaredStyle({ - display: {outer: 'block', inner: 'flow'} + display: { outer: "block", inner: "flow" }, }), span: createDeclaredStyle({ - display: {outer: 'inline', inner: 'flow'} + display: { outer: "inline", inner: "flow" }, }), p: createDeclaredStyle({ - display: {outer: 'block', inner: 'flow'}, - marginTop: {value: 1, unit: 'em'}, - marginBottom: {value: 1, unit: 'em'} + display: { outer: "block", inner: "flow" }, + marginTop: { value: 1, unit: "em" }, + marginBottom: { value: 1, unit: "em" }, }), strong: createDeclaredStyle({ - fontWeight: 700 + fontWeight: 700, }), b: createDeclaredStyle({ - fontWeight: 700 + fontWeight: 700, }), em: createDeclaredStyle({ - fontStyle: 'italic' + fontStyle: "italic", }), i: createDeclaredStyle({ - fontStyle: 'italic' + fontStyle: "italic", }), sup: createDeclaredStyle({ - fontSize: {value: 1/1.2, unit: 'em'}, - verticalAlign: 'super' + fontSize: { value: 1 / 1.2, unit: "em" }, + verticalAlign: "super", }), sub: createDeclaredStyle({ - fontSize: {value: 1/1.2, unit: 'em'}, - verticalAlign: 'sub' + fontSize: { value: 1 / 1.2, unit: "em" }, + verticalAlign: "sub", }), img: createDeclaredStyle({ - display: {outer: 'inline', inner: 'flow'} + display: { outer: "inline", inner: "flow" }, }), h1: createDeclaredStyle({ - fontSize: {value: 2, unit: 'em'}, - display: {outer: 'block', inner: 'flow'}, - marginTop: {value: 0.67, unit: 'em'}, - marginBottom: {value: 0.67, unit: 'em'} + fontSize: { value: 2, unit: "em" }, + display: { outer: "block", inner: "flow" }, + marginTop: { value: 0.67, unit: "em" }, + marginBottom: { value: 0.67, unit: "em" }, }), h2: createDeclaredStyle({ - fontSize: {value: 1.5, unit: 'em'}, - display: {outer: 'block', inner: 'flow'}, - marginTop: {value: 0.83, unit: 'em'}, - marginBottom: {value: 0.83, unit: 'em'}, - fontWeight: 700 + fontSize: { value: 1.5, unit: "em" }, + display: { outer: "block", inner: "flow" }, + marginTop: { value: 0.83, unit: "em" }, + marginBottom: { value: 0.83, unit: "em" }, + fontWeight: 700, }), h3: createDeclaredStyle({ - fontSize: {value: 1.17, unit: 'em'}, - display: {outer: 'block', inner: 'flow'}, - marginTop: {value: 1, unit: 'em'}, - marginBottom: {value: 1, unit: 'em'}, - fontWeight: 700 + fontSize: { value: 1.17, unit: "em" }, + display: { outer: "block", inner: "flow" }, + marginTop: { value: 1, unit: "em" }, + marginBottom: { value: 1, unit: "em" }, + fontWeight: 700, }), h4: createDeclaredStyle({ - display: {outer: 'block', inner: 'flow'}, - marginTop: {value: 1.33, unit: 'em'}, - marginBottom: {value: 1.33, unit: 'em'}, - fontWeight: 700 + display: { outer: "block", inner: "flow" }, + marginTop: { value: 1.33, unit: "em" }, + marginBottom: { value: 1.33, unit: "em" }, + fontWeight: 700, }), h5: createDeclaredStyle({ - fontSize: {value: 0.83, unit: 'em'}, - display: {outer: 'block', inner: 'flow'}, - marginTop: {value: 1.67, unit: 'em'}, - marginBottom: {value: 1.67, unit: 'em'}, - fontWeight: 700 + fontSize: { value: 0.83, unit: "em" }, + display: { outer: "block", inner: "flow" }, + marginTop: { value: 1.67, unit: "em" }, + marginBottom: { value: 1.67, unit: "em" }, + fontWeight: 700, }), h6: createDeclaredStyle({ - fontSize: {value: 0.67, unit: 'em'}, - display: {outer: 'block', inner: 'flow'}, - marginTop: {value: 2.33, unit: 'em'}, - marginBottom: {value: 2.33, unit: 'em'}, - fontWeight: 700 - }) + fontSize: { value: 0.67, unit: "em" }, + display: { outer: "block", inner: "flow" }, + marginTop: { value: 2.33, unit: "em" }, + marginBottom: { value: 2.33, unit: "em" }, + fontWeight: 700, + }), }); // https://github.com/nodejs/node/blob/238104c531219db05e3421521c305404ce0c0cce/deps/v8/src/utils/utils.h#L213 // Thomas Wang, Integer Hash Functions. // http://www.concentric.net/~Ttwang/tech/inthash.htm` function hash(hash: number) { - hash = ~hash + (hash << 15); // hash = (hash << 15) - hash - 1; + hash = ~hash + (hash << 15); // hash = (hash << 15) - hash - 1; hash = hash ^ (hash >> 12); hash = hash + (hash << 2); hash = hash ^ (hash >> 4); - hash = hash * 2057; // hash = (hash + (hash << 3)) + (hash << 11); + hash = hash * 2057; // hash = (hash + (hash << 3)) + (hash << 11); hash = hash ^ (hash >> 16); return hash & 0x3fffffff; } -const cascadeCache = new Map; +const cascadeCache = new Map(); export function cascadeStyles(styles: DeclaredStyle[]): DeclaredStyle { if (styles.length === 0) return EMPTY_STYLE; @@ -932,9 +1113,15 @@ function defaultProperty( p: keyof DeclaredStyleProperties ) { const properties = style.properties; - if (properties[p] === inherited || !(p in properties) && inheritedStyle[p]) { + if ( + properties[p] === inherited || + (!(p in properties) && inheritedStyle[p]) + ) { return parentStyle.computed[p]; - } else if (properties[p] === initial || !(p in properties) && !inheritedStyle[p]) { + } else if ( + properties[p] === initial || + (!(p in properties) && !inheritedStyle[p]) + ) { return initialPlainStyle[p]; } else { return properties[p]; @@ -945,8 +1132,22 @@ function resolveEm( value: DeclaredStyleProperties[keyof DeclaredStyleProperties], fontSize: number ) { - if (typeof value === 'object' && 'unit' in value && value.unit === 'em') { - return fontSize * value.value; + if (typeof value === "object" && "unit" in value) { + if (value.unit === "em") { + return fontSize * value.value; + } else if (value.unit === "pt") { + return (value.value * 4) / 3; + } else if (value.unit === "pc") { + return (value.value * 6) / 5; + } else if (value.unit === "px") { + return value.value; + } else if (value.unit === "cm") { + return (value.value * 96) / 2.54; + } else if (value.unit === "mm") { + return (value.value * 96) / 25.4; + } else if (value.unit === "in") { + return value.value * 96; + } } else { return value; } @@ -958,11 +1159,17 @@ function computeStyle(parentStyle: Style, cascadedStyle: DeclaredStyle) { const working = {} as DeclaredStyleProperties; // Compute fontSize first since em values depend on it - const specifiedFontSize = defaultProperty(parentStyle, cascadedStyle, 'fontSize'); - let fontSize = resolveEm(specifiedFontSize, parentFontSize) as number | Percentage; - - if (typeof fontSize === 'object') { - fontSize = fontSize.value / 100 * parentFontSize; + const specifiedFontSize = defaultProperty( + parentStyle, + cascadedStyle, + "fontSize" + ); + let fontSize = resolveEm(specifiedFontSize, parentFontSize) as + | number + | Percentage; + + if (typeof fontSize === "object") { + fontSize = (fontSize.value / 100) * parentFontSize; } // Default and inherit @@ -977,8 +1184,11 @@ function computeStyle(parentStyle: Style, cascadedStyle: DeclaredStyle) { working.fontSize = fontSize; // https://www.w3.org/TR/css-fonts-4/#relative-weights - if (properties.fontWeight === 'bolder' || properties.fontWeight === 'lighter') { - const bolder = properties.fontWeight === 'bolder'; + if ( + properties.fontWeight === "bolder" || + properties.fontWeight === "lighter" + ) { + const bolder = properties.fontWeight === "bolder"; const pWeight = parentStyle.computed.fontWeight; if (pWeight < 100) { working.fontWeight = bolder ? 400 : parentStyle.computed.fontWeight; @@ -995,14 +1205,17 @@ function computeStyle(parentStyle: Style, cascadedStyle: DeclaredStyle) { } } - if (typeof properties.lineHeight === 'object' && properties.lineHeight.unit === '%') { - working.lineHeight = properties.lineHeight.value / 100 * fontSize; + if ( + typeof properties.lineHeight === "object" && + properties.lineHeight.unit === "%" + ) { + working.lineHeight = (properties.lineHeight.value / 100) * fontSize; } // At this point we've reduced all value types to their computed counterparts const computed = working as ComputedStyle; - if (typeof properties.zoom === 'object') { + if (typeof properties.zoom === "object") { computed.zoom = properties.zoom.value / 100; } @@ -1012,19 +1225,23 @@ function computeStyle(parentStyle: Style, cascadedStyle: DeclaredStyle) { // Blockify floats (TODO: abspos too) (CSS Display §2.7). This drives what // type of box is created (-> not an inline), but otherwise has no effect. - if (computed.float !== 'none') style.blockify(); + if (computed.float !== "none") style.blockify(); return style; } -const computedCache = new Map; +const computedCache = new Map(); export function createStyle(parentStyle: Style, cascadedStyle: DeclaredStyle) { const key = hash(parentStyle.id) ^ hash(cascadedStyle.id); let style = computedCache.get(key) ?? null; let prev = null; while (style) { - if (style.parentId === parentStyle.id && style.cascadeId === cascadedStyle.id) return style; + if ( + style.parentId === parentStyle.id && + style.cascadeId === cascadedStyle.id + ) + return style; prev = style; style = style.nextInCache; } @@ -1044,9 +1261,9 @@ export function createStyle(parentStyle: Style, cascadedStyle: DeclaredStyle) { // required styles that always come last in the cascade const rootDeclaredStyle = createDeclaredStyle({ display: { - outer: 'block', - inner: 'flow-root' - } + outer: "block", + inner: "flow-root", + }, }); rootDeclaredStyle.id = 0x7fffffff; // max SMI diff --git a/test/paint-spy.js b/test/paint-spy.js index 1447cc4..b4b1929 100644 --- a/test/paint-spy.js +++ b/test/paint-spy.js @@ -2,24 +2,24 @@ function hex(r, g, b, a) { a *= 255; if ( - (r >>> 4 === (r & 0xf)) && - (g >>> 4 === (g & 0xf)) && - (b >>> 4 === (b & 0xf)) && - (a >>> 4 === (a & 0xf)) + r >>> 4 === (r & 0xf) && + g >>> 4 === (g & 0xf) && + b >>> 4 === (b & 0xf) && + a >>> 4 === (a & 0xf) ) { r = (r >>> 4).toString(16); g = (g >>> 4).toString(16); b = (b >>> 4).toString(16); - a = a === 255 ? '' : (a >>> 4).toString(16); + a = a === 255 ? "" : (a >>> 4).toString(16); } else { r = r.toString(16); g = g.toString(16); b = b.toString(16); - a = a === 255 ? '' : a.toString(16); - if (r.length == 1) r = '0' + r; - if (g.length == 1) g = '0' + g; - if (b.length == 1) b = '0' + b; - if (a.length == 1) a = '0' + a; + a = a === 255 ? "" : a.toString(16); + if (r.length == 1) r = "0" + r; + if (g.length == 1) g = "0" + g; + if (b.length == 1) b = "0" + b; + if (a.length == 1) a = "0" + a; } return `#${r}${g}${b}${a}`; @@ -33,18 +33,33 @@ export default class PaintSpy { edge(x, y, length, side) { const strokeColor = this.strokeColor; const lineWidth = this.lineWidth; - this.calls.push({t: 'edge', x, y, length, side, strokeColor, lineWidth}); + this.calls.push({ t: "edge", x, y, length, side, strokeColor, lineWidth }); } rect(x, y, width, height) { const fillColor = this.fillColor; - this.calls.push({t: 'rect', x, y, width, height, fillColor}); + this.calls.push({ t: "rect", x, y, width, height, fillColor }); + } + + path(pathData) { + const strokeColor = this.strokeColor; + const lineWidth = this.lineWidth; + const strokeDasharray = this.strokeDasharray; + const strokeLinecap = this.strokeLinecap; + this.calls.push({ + t: "path", + pathData, + strokeColor, + lineWidth, + strokeDasharray, + strokeLinecap, + }); } text(x, y, item, textStart, textEnd) { const fillColor = this.fillColor; const text = item.paragraph.slice(textStart, textEnd); - this.calls.push({t: 'text', x, y, text, fillColor}); + this.calls.push({ t: "text", x, y, text, fillColor }); } image(x, y, width, height, image) { @@ -52,31 +67,34 @@ export default class PaintSpy { } drewText(text) { - const calls = this.calls.filter(call => call.t === 'text'); - const ret = calls.find(c => c.text === text); + const calls = this.calls.filter((call) => call.t === "text"); + const ret = calls.find((c) => c.text === text); if (!ret) { - const c = calls.slice(-10).map(c => ` "${c.text}"`).join('\n'); + const c = calls + .slice(-10) + .map((c) => ` "${c.text}"`) + .join("\n"); throw new Error(`No call for "${text}". Last 10:\n${c}`); } return ret; } pushClip(x, y, width, height) { - this.calls.push({t: 'pushClip', x, y, width, height}); + this.calls.push({ t: "pushClip", x, y, width, height }); } popClip() { - this.calls.push({t: 'popClip'}); + this.calls.push({ t: "popClip" }); } getCalls() { - return this.calls.map(call => { - if (call.t === 'rect' || call.t === 'text') { - const {r, g, b, a} = call.fillColor; - return {...call, fillColor: hex(r, g, b, a)}; - } else if (call.t === 'edge') { - const {r, g, b, a} = call.strokeColor; - return {...call, strokeColor: hex(r, g, b, a)}; + return this.calls.map((call) => { + if (call.t === "rect" || call.t === "text") { + const { r, g, b, a } = call.fillColor; + return { ...call, fillColor: hex(r, g, b, a) }; + } else if (call.t === "edge" || call.t === "path") { + const { r, g, b, a } = call.strokeColor; + return { ...call, strokeColor: hex(r, g, b, a) }; } else { return call; } diff --git a/tsconfig.json b/tsconfig.json index a0fb01b..59b1585 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,12 @@ { - "include": ["*.ts", "src/**/*", "test/**/*", "*.d.ts", "examples/*.ts", "assets/register.ts"], + "include": [ + "*.ts", + "src/**/*", + "test/**/*", + "*.d.ts", + "examples/*.ts", + "assets/register.ts" + ], "exclude": [ "node_modules", // https://github.com/Brooooooklyn/canvas/issues/659 @@ -12,7 +19,7 @@ "outDir": "dist", "allowJs": true, "declaration": true, - "lib": ["esnext"], + "lib": ["esnext", "dom"], "target": "esnext", "module": "node16", "strict": true, From 133c909f226b8cf0445bb316b2dbe48b980226aa Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:03:30 -0700 Subject: [PATCH 02/11] Refactor paintFormattingBoxBackground to use style object for border radius values and add blockify method to Style class for block display handling. --- src/style.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/style.ts b/src/style.ts index c390012..bd3c9ba 100644 --- a/src/style.ts +++ b/src/style.ts @@ -562,6 +562,13 @@ export class Style { return this.float !== "none"; // TODO: or this.position === 'absolute' } + blockify() { + this.blockified = true; + if (this.display.outer !== "none") { + this.display = { outer: "block", inner: this.display.inner }; + } + } + isWsCollapsible() { const whiteSpace = this.whiteSpace; return ( From 16f59d02b0ddbeb818e77de7e91e204cba5ced1f Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:04:42 -0700 Subject: [PATCH 03/11] Moved blockify to match upstream --- src/style.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/style.ts b/src/style.ts index bd3c9ba..c390012 100644 --- a/src/style.ts +++ b/src/style.ts @@ -562,13 +562,6 @@ export class Style { return this.float !== "none"; // TODO: or this.position === 'absolute' } - blockify() { - this.blockified = true; - if (this.display.outer !== "none") { - this.display = { outer: "block", inner: this.display.inner }; - } - } - isWsCollapsible() { const whiteSpace = this.whiteSpace; return ( From 208d1b29ac34492a927f073d2d494922acf4c4ff Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:44:03 -0700 Subject: [PATCH 04/11] Add layout system documentation and examples - Introduced a new layout system specification document detailing the Dropflow layout engine, including core components, layout processes, and border drawing integration. - Added a .prettierignore file to exclude all project files from formatting. - Created a new examples.js file containing various HTML examples to demonstrate layout capabilities, including typography, grid layout, and box layout with different border styles. - Updated index.html and index.js to integrate the examples and enhance the user interface with a toolbar for selecting examples. --- .prettierignore | 2 + {specifications => docs}/layout-system.md | 0 site/examples.js | 444 ++++++++++++ site/index.html | 12 +- site/index.js | 181 +++-- src/box-border.ts | 568 +++++++++++++++ src/paint-canvas.ts | 10 +- src/paint-html.ts | 4 +- src/paint-svg.ts | 4 +- src/paint.ts | 807 ++-------------------- test/paint-spy.js | 12 +- 11 files changed, 1189 insertions(+), 855 deletions(-) create mode 100644 .prettierignore rename {specifications => docs}/layout-system.md (100%) create mode 100644 site/examples.js create mode 100644 src/box-border.ts diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..da6b717 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore all files in this project +* \ No newline at end of file diff --git a/specifications/layout-system.md b/docs/layout-system.md similarity index 100% rename from specifications/layout-system.md rename to docs/layout-system.md diff --git a/site/examples.js b/site/examples.js new file mode 100644 index 0000000..2e089c3 --- /dev/null +++ b/site/examples.js @@ -0,0 +1,444 @@ +export const examples = [ + { + id: 1, + name: "Intro", + html: ` +

+ + playground +

+

this is all being rendered to a canvas

+

edit the html to the left to see live updates

+ +
+
NOTE
+ Using dropflow to render to a browser canvas is rarely better than + native HTML and CSS (but there are cases for it). This is a demo to + show the capabilities you could use for server-generated images and PDFs. +
+ +
+ To the left! +
+
+ To the right! +
+

+ To the left and right are examples of floats. + Floats are placed as they are encountered + in text, so text that comes after them won't collide with them. + If this text doesn't go underneath the floats, resize your browser window. +

+ +
+ +
+ Another difficult feature is inline-blocks. +
+ Here's one right here. +
+ That's what they do: the "inline" part means that + it's inline-level, and + the "block" part is short for block container. +
+ +
+ +
+ You may want to have some text in a paragraph to be raised or + lowered. That's done with vertical-align. + + alignment is relative to the + parent, except top + and bottom, + which are broken out and aligned to the line + as an atomic unit. +
+ +
+ +
+ The + zoom + property makes everything bigger! (or smaller) +
+ +
+ +
+ Finally, when + painting inline backgrounds, the inline element must not interrupt + font shaping features like ligatures, or kerning, such as the text + "AV". When an inline is + + relatively positioned, + + this does interrupt shaping boundaries. +
+` + }, + { + id: 2, + name: "Typography", + html: ` +

+ Typography Showcase +

+ +
+

Font Weights & Styles

+

Light weight text (300)

+

Regular weight text (400)

+

Semi-bold weight text (600)

+

Bold weight text (700)

+

Italic text style

+

❌ Underlined text

+

❌ Strikethrough text

+
+ +
+

Text Alignment

+
+

Left aligned text

+

Center aligned text

+

Right aligned text

+

Justified text that spreads across the full width of the container, creating even margins on both sides.

+
+
+ +

Bidirectional Text Example

+ +
+ +
+ Info Box
+
+ + معلومات بالعربية +
+ +

+ Here is some standard English (Left-to-Right) text to start. We can seamlessly embed Arabic, which is a Right-to-Left language: مرحبا بالعالم، كيف حالك اليوم؟. Notice how the text flows correctly. The sentence continues in English, and now we will add some Hebrew (also RTL): שלום עולם!. The layout respects the directionality of each script, even when they are mixed together. We can finish the paragraph with some Spanish (LTR): ¡Hola, Mundo! +

+ +
+ +
+

Text Sizes & Spacing

+

Small text (0.8em)

+

Normal text (1em)

+

Large text (1.2em)

+

Extra large text (1.5em)

+

Text with increased line height for better readability

+

Text with letter spacing

+

Text with word spacing

+
+ +
+

Special Characters & Symbols

+

Mathematical symbols: α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω

+

Currency symbols: $ € £ ¥ ₹ ₿

+

Arrows: ← → ↑ ↓ ↔ ↕ ⇐ ⇒ ⇑ ⇓ ⇔ ⇕

+

Emojis: 🚀 💻 🎨 📱 🌟 ✨

+
+` + }, + { + id: 3, + name: "Layout Grid", + html: ` +

CSS Grid Layout Demo

+ +
+
+

Grid Item 1

+

This is the first grid item with some sample content.

+
+ +
+

Grid Item 2

+

Second grid item with different styling.

+
+ +
+

Grid Item 3

+

Third grid item with green theme.

+
+ +
+

Grid Item 4

+

Fourth item with orange accent.

+
+ +
+

Grid Item 5

+

Fifth item with pink styling.

+
+ +
+

Grid Item 6

+

Sixth and final grid item.

+
+
+ +
+
+

Main Content Area

+

This is the main content area that takes up more space in the grid. It demonstrates how CSS Grid can create flexible layouts with different column widths.

+

The grid system allows for responsive design and easy maintenance of layout structure.

+
+ +
+

Sidebar

+
    +
  • Navigation item 1
  • +
  • Navigation item 2
  • +
  • Navigation item 3
  • +
  • Navigation item 4
  • +
+
+
+ +
+
+

Auto-fit Grid

+

This demonstrates auto-fit with minmax for responsive behavior.

+
+ +
+

Responsive Layout

+

Items automatically adjust based on available space.

+
+ +
+

Flexible Sizing

+

Minimum 200px width, but can grow to fill space.

+
+
+` + }, + { + id: 4, + name: "Box Layout", + html: ` +

Box Layout & Border Styles

+ + +
+ +
+

Solid Border

+
+ +
+

Dashed Border

+
+ +
+

Dotted Border

+
+ +
+

Double Border

+
+
+ + +
+ +
+

Alternating Radius

+
+ +
+

Ellipse

+
+ + +
+

Rounded Pill

+
+
+ + +
+

Nested Box Layout

+ +
+

Outer Container

+

This is the outer container with a dark border.

+ +
+

Middle Layer

+

This is a nested container with a dashed border.

+ +
+
Inner Container
+

This is the innermost container with a thin border.

+
+
+
+
+ + +
+

Mixed Border Styles

+ +
+

Mixed Border Types

+

Each side has a different border style: solid top, dashed right, double bottom, dotted left.

+
+ +
+

Varied Border Radius

+

Each corner has a different radius: 20px, 5px, 15px, 10px (clockwise from top-left).

+
+
+ + +
+

Box Shadows

+ +
+

Subtle Shadow

+

Light shadow for depth without being too prominent.

+
+ +
+

Medium Shadow

+

More pronounced shadow for greater depth perception.

+
+
+` + } +]; \ No newline at end of file diff --git a/site/index.html b/site/index.html index 7b32c5b..5b3712d 100644 --- a/site/index.html +++ b/site/index.html @@ -5,7 +5,17 @@ -
+
+
+
+
{ @@ -46,107 +54,13 @@ const watch = EditorView.updateListener.of((update) => { }); const state = EditorState.create({ - doc: ` -

- - playground -

-

this is all being rendered to a canvas

-

edit the html to the left to see live updates

- -
-
NOTE
- Using dropflow to render to a browser canvas is rarely better than - native HTML and CSS (but there are cases for it). This is a demo to - show the capabilities you could use for server-generated images and PDFs. -
- -
- To the left! -
-
- To the right! -
-

- To the left and right are examples of floats. - Floats are placed as they are encountered - in text, so text that comes after them won't collide with them. - If this text doesn't go underneath the floats, resize your browser window. -

- -
- -
- Another difficult feature is inline-blocks. -
- Here's one right here. -
- That's what they do: the "inline" part means that - it's inline-level, and - the "block" part is short for block container. -
- -
- -
- You may want to have some text in a paragraph to be raised or - lowered. That's done with vertical-align. - - alignment is relative to the - parent, except top - and bottom, - which are broken out and aligned to the line - as an atomic unit. -
- -
- -
- The - zoom - property makes everything bigger! (or smaller) -
- -
- -
- Finally, when - painting inline backgrounds, the inline element must not interrupt - font shaping features like ligatures, or kerning, such as the text - "AV". When an inline is - - relatively positioned, - - this does interrupt shaping boundaries. -
-`, + doc: examples[0].html, extensions: [basicSetup, html(), watch, solarizedDark], }); const view = new EditorView({ state, - parent: document.querySelector("#editor"), + parent: document.querySelector("#editor") || document.body, }); let documentElement; @@ -155,10 +69,68 @@ let blockContainer; function parseGenerate() { documentElement = parse(view.state.doc.toString()); blockContainer = flow.generate(documentElement); + // @ts-ignore window.blockContainer = blockContainer; + // @ts-ignore window.documentElement = documentElement; } +function updateButtonStyles(activeId) { + examples.forEach((example) => { + const button = document.getElementById(`sample-${example.id}`); + if (button) { + if (example.id === activeId) { + button.style.backgroundColor = "#3498db"; + button.style.color = "white"; + } else { + button.style.backgroundColor = "#34495e"; + button.style.color = "#bdc3c7"; + } + } + }); +} + +function loadExample(exampleId) { + const example = examples.find(ex => ex.id === exampleId); + if (example) { + view.dispatch({ + changes: { + from: 0, + to: view.state.doc.length, + insert: example.html + } + }); + updateButtonStyles(exampleId); + } +} + +function createToolbarButtons() { + const toolbarContainer = document.getElementById("toolbar-buttons"); + if (!toolbarContainer) return; + + examples.forEach((example, index) => { + const button = document.createElement("button"); + button.id = `sample-${example.id}`; + button.textContent = example.name; + button.style.cssText = ` + padding: 0.5em 1em; + background-color: ${index === 0 ? "#3498db" : "#34495e"}; + color: ${index === 0 ? "white" : "#bdc3c7"}; + border: none; + border-radius: 4px; + cursor: pointer; + font-weight: bold; + white-space: nowrap; + `; + + button.addEventListener("click", () => loadExample(example.id)); + toolbarContainer.appendChild(button); + }); +} + +// Initialize toolbar buttons +createToolbarButtons(); + parseGenerate(); loadLayoutPaint(); @@ -179,6 +151,7 @@ window.addEventListener("resize", function () { observer.observe(document.body); +// @ts-ignore window.flow = flow; view.dom.style.height = "100%"; diff --git a/src/box-border.ts b/src/box-border.ts new file mode 100644 index 0000000..55ec0a6 --- /dev/null +++ b/src/box-border.ts @@ -0,0 +1,568 @@ +import { Color } from "./style.js"; + +// Border rendering constants + +export const BORDER_DASHED_ARRAY = [4, 4]; +export const BORDER_DOTTED_ARRAY = [0, 2]; + +export type BorderStyle = "solid" | "dashed" | "dotted" | "double" | "hidden" | "groove" | "ridge" | "inset" | "outset" | "none"; + +export interface BorderInfo { + width: number; + style: BorderStyle; + color: Color; +} + +export interface BorderRadius { + horizontal: number; + vertical: number; +} + +export interface BoxBorder { + left: BorderInfo; + top: BorderInfo; + right: BorderInfo; + bottom: BorderInfo; + topLeft: BorderRadius; + topRight: BorderRadius; + bottomRight: BorderRadius; + bottomLeft: BorderRadius; +} + +export interface BoxBorderSegment { + firstSide: "left" | "top" | "right" | "bottom"; + left: boolean; + top: boolean; + right: boolean; + bottom: boolean; +} + +// Get stroke properties for a border style +export function getStrokePropertiesFromBorder(border: BorderInfo): { + strokeWidth: number; + strokeColor: Color; + strokeDasharray?: string; + strokeLinecap?: "butt" | "round" | "square"; +} { + const base = { + strokeWidth: border.width, + strokeColor: border.color, + }; + + switch (border.style) { + case "dashed": + return { + ...base, + strokeDasharray: BORDER_DASHED_ARRAY.map((x) => x * border.width).join( + " " + ), + strokeLinecap: "butt", + }; + case "dotted": + return { + ...base, + strokeDasharray: BORDER_DOTTED_ARRAY.map((x) => x * border.width).join( + " " + ), + strokeLinecap: "round", + }; + case "double": + return { + ...base, + strokeWidth: base.strokeWidth / 3, + }; + case "solid": + default: + return { + ...base, + strokeLinecap: "butt", + }; + } +} +// Check if a border is visible (has width, style, and color alpha > 0) +export function isBorderVisible(border: BorderInfo): boolean { + return ( + border.width > 0 && + border.style !== "none" && + border.style !== "hidden" && + border.color.a > 0 + ); +} +// Check if two borders match (same width, style, and color) +function bordersMatch(border1: BorderInfo, border2: BorderInfo): boolean { + return ( + border1.width === border2.width && + border1.style === border2.style && + border1.color.r === border2.color.r && + border1.color.g === border2.color.g && + border1.color.b === border2.color.b && + border1.color.a === border2.color.a + ); +} +// Detect contiguous border segments for optimization +export function getBorderSegments(borders: { + left: BorderInfo; + top: BorderInfo; + right: BorderInfo; + bottom: BorderInfo; +}): BoxBorderSegment[] { + const { left, top, right, bottom } = borders; + + // Check for full uniform border + if (isBorderVisible(left) && + isBorderVisible(top) && + isBorderVisible(right) && + isBorderVisible(bottom) && + bordersMatch(left, top) && + bordersMatch(top, right) && + bordersMatch(right, bottom)) { + return [ + { + firstSide: "left", + left: true, + top: true, + right: true, + bottom: true, + }, + ]; + } + + // Check for three-side matches + const segments: BoxBorderSegment[] = []; + + // Left-Top-Right + if (isBorderVisible(left) && + isBorderVisible(top) && + isBorderVisible(right) && + bordersMatch(left, top) && + bordersMatch(top, right)) { + segments.push({ + firstSide: "left", + left: true, + top: true, + right: true, + bottom: false, + }); + if (isBorderVisible(bottom)) { + segments.push({ + firstSide: "bottom", + left: false, + top: false, + right: false, + bottom: true, + }); + } + return segments; + } + + // Top-Right-Bottom + if (isBorderVisible(top) && + isBorderVisible(right) && + isBorderVisible(bottom) && + bordersMatch(top, right) && + bordersMatch(right, bottom)) { + segments.push({ + firstSide: "top", + left: false, + top: true, + right: true, + bottom: true, + }); + if (isBorderVisible(left)) { + segments.push({ + firstSide: "left", + left: true, + top: false, + right: false, + bottom: false, + }); + } + return segments; + } + + // Right-Bottom-Left + if (isBorderVisible(right) && + isBorderVisible(bottom) && + isBorderVisible(left) && + bordersMatch(right, bottom) && + bordersMatch(bottom, left)) { + segments.push({ + firstSide: "right", + left: true, + top: false, + right: true, + bottom: true, + }); + if (isBorderVisible(top)) { + segments.push({ + firstSide: "top", + left: false, + top: true, + right: false, + bottom: false, + }); + } + return segments; + } + + // Bottom-Left-Top + if (isBorderVisible(bottom) && + isBorderVisible(left) && + isBorderVisible(top) && + bordersMatch(bottom, left) && + bordersMatch(left, top)) { + segments.push({ + firstSide: "bottom", + left: true, + top: true, + right: false, + bottom: true, + }); + if (isBorderVisible(right)) { + segments.push({ + firstSide: "right", + left: false, + top: false, + right: true, + bottom: false, + }); + } + return segments; + } + + // Check for two-side matches + // Left-Top + if (isBorderVisible(left) && + isBorderVisible(top) && + bordersMatch(left, top)) { + segments.push({ + firstSide: "left", + left: true, + top: true, + right: false, + bottom: false, + }); + } else { + if (isBorderVisible(left)) { + segments.push({ + firstSide: "left", + left: true, + top: false, + right: false, + bottom: false, + }); + } + if (isBorderVisible(top)) { + segments.push({ + firstSide: "top", + left: false, + top: true, + right: false, + bottom: false, + }); + } + } + + // Right-Bottom + if (isBorderVisible(right) && + isBorderVisible(bottom) && + bordersMatch(right, bottom)) { + segments.push({ + firstSide: "right", + left: false, + top: false, + right: true, + bottom: true, + }); + } else { + if (isBorderVisible(right)) { + segments.push({ + firstSide: "right", + left: false, + top: false, + right: true, + bottom: false, + }); + } + if (isBorderVisible(bottom)) { + segments.push({ + firstSide: "bottom", + left: false, + top: false, + right: false, + bottom: true, + }); + } + } + + return segments; +} +// Generate SVG path for a border segment with radius support +function generateSegmentPath( + x: number, + y: number, + width: number, + height: number, + radii: { + tlHor: number; + tlVer: number; + trHor: number; + trVer: number; + brHor: number; + brVer: number; + blHor: number; + blVer: number; + }, + segment: BoxBorderSegment +): string { + let path = ""; + let started = false; + + const addLineOrMoveTo = (targetX: number, targetY: number) => { + if (!started) { + path += `M ${targetX} ${targetY}`; + started = true; + } else { + path += ` L ${targetX} ${targetY}`; + } + }; + + const addArcTo = ( + rx: number, + ry: number, + targetX: number, + targetY: number + ) => { + if (rx > 0 && ry > 0) { + path += ` A ${rx} ${ry} 0 0 1 ${targetX} ${targetY}`; + } else { + addLineOrMoveTo(targetX, targetY); + } + }; + + // Handle each side of the segment + if (segment.left) { + // Left side: bottom-left corner to top-left corner + if (segment.bottom) { + // Start from bottom-left corner (after radius) + addLineOrMoveTo(x, y + height - radii.blVer); + } else { + // Start from bottom of left side + addLineOrMoveTo(x, y + height); + } + + // Draw to top-left corner (before radius) + if (radii.tlHor > 0 || radii.tlVer > 0) { + addLineOrMoveTo(x, y + radii.tlVer); + if (segment.top) { + // Draw top-left arc + addArcTo(radii.tlHor, radii.tlVer, x + radii.tlHor, y); + } + } else { + addLineOrMoveTo(x, y); + if (segment.top) { + addLineOrMoveTo(x + radii.tlHor, y); + } + } + } + + if (segment.top) { + // Top side: top-left corner to top-right corner + if (!segment.left) { + // Start from left end of top side + addLineOrMoveTo(x, y); + } + + // Draw to top-right corner (before radius) + if (radii.trHor > 0 || radii.trVer > 0) { + addLineOrMoveTo(x + width - radii.trHor, y); + if (segment.right) { + // Draw top-right arc + addArcTo(radii.trHor, radii.trVer, x + width, y + radii.trVer); + } + } else { + addLineOrMoveTo(x + width, y); + if (segment.right) { + addLineOrMoveTo(x + width, y + radii.trVer); + } + } + } + + if (segment.right) { + // Right side: top-right corner to bottom-right corner + if (!segment.top) { + // Start from top of right side + addLineOrMoveTo(x + width, y); + } + + // Draw to bottom-right corner (before radius) + if (radii.brHor > 0 || radii.brVer > 0) { + addLineOrMoveTo(x + width, y + height - radii.brVer); + if (segment.bottom) { + // Draw bottom-right arc + addArcTo(radii.brHor, radii.brVer, x + width - radii.brHor, y + height); + } + } else { + addLineOrMoveTo(x + width, y + height); + if (segment.bottom) { + addLineOrMoveTo(x + width - radii.brHor, y + height); + } + } + } + + if (segment.bottom) { + // Bottom side: bottom-right corner to bottom-left corner + if (!segment.right) { + // Start from right end of bottom side + addLineOrMoveTo(x + width, y + height); + } + + // Draw to bottom-left corner (before radius) + if (radii.blHor > 0 || radii.blVer > 0) { + addLineOrMoveTo(x + radii.blHor, y + height); + if (segment.left) { + // Draw bottom-left arc + addArcTo(radii.blHor, radii.blVer, x, y + height - radii.blVer); + } + } else { + addLineOrMoveTo(x, y + height); + if (segment.left) { + addLineOrMoveTo(x, y + height - radii.blVer); + } + } + } + + // Close path only if the segment includes all four sides + if (segment.left && segment.top && segment.right && segment.bottom) { + path += " Z"; + } + + return path; +} +// Generate border path with radius support +export function generateBorderPath( + x: number, + y: number, + width: number, + height: number, + border: BoxBorder, + segment: BoxBorderSegment, + style: BorderStyle): string { + const isDouble = style === "double"; + + // Calculate geometry based on inset factor + const calculateGeometry = (factor: number) => { + const leftInset = (border.left.width * factor) / 2; + const topInset = (border.top.width * factor) / 2; + const rightInset = (border.right.width * factor) / 2; + const bottomInset = (border.bottom.width * factor) / 2; + + const adjustedX = x + leftInset; + const adjustedY = y + topInset; + const adjustedWidth = width - leftInset - rightInset; + const adjustedHeight = height - topInset - bottomInset; + + // Scale border radii proportionally with proper overlap handling + // Only apply corner radii when both adjacent borders are present in the segment + let tlHor = segment.left && segment.top + ? Math.max(0, border.topLeft.horizontal - leftInset) + : 0; + let tlVer = segment.left && segment.top + ? Math.max(0, border.topLeft.vertical - topInset) + : 0; + let trHor = segment.top && segment.right + ? Math.max(0, border.topRight.horizontal - rightInset) + : 0; + let trVer = segment.top && segment.right + ? Math.max(0, border.topRight.vertical - topInset) + : 0; + let brHor = segment.right && segment.bottom + ? Math.max(0, border.bottomRight.horizontal - rightInset) + : 0; + let brVer = segment.right && segment.bottom + ? Math.max(0, border.bottomRight.vertical - bottomInset) + : 0; + let blHor = segment.bottom && segment.left + ? Math.max(0, border.bottomLeft.horizontal - leftInset) + : 0; + let blVer = segment.bottom && segment.left + ? Math.max(0, border.bottomLeft.vertical - bottomInset) + : 0; + + // Adjust radii ratios if they overlap (like in reference implementation) + const topRatio = adjustedWidth / (tlHor + trHor || 1); + const rightRatio = adjustedHeight / (trVer + brVer || 1); + const bottomRatio = adjustedWidth / (blHor + brHor || 1); + const leftRatio = adjustedHeight / (blVer + tlVer || 1); + const smallestRatio = Math.min( + topRatio, + rightRatio, + bottomRatio, + leftRatio + ); + + if (smallestRatio < 1) { + tlHor *= smallestRatio; + tlVer *= smallestRatio; + trHor *= smallestRatio; + trVer *= smallestRatio; + brHor *= smallestRatio; + brVer *= smallestRatio; + blHor *= smallestRatio; + blVer *= smallestRatio; + } + + return { + x: adjustedX, + y: adjustedY, + width: adjustedWidth, + height: adjustedHeight, + radii: { + tlHor, + tlVer, + trHor, + trVer, + brHor, + brVer, + blHor, + blVer, + }, + }; + }; + + if (isDouble) { + // Outer path (inset factor 1/3) + const outerGeo = calculateGeometry(1 / 3); + const path1 = generateSegmentPath( + outerGeo.x, + outerGeo.y, + outerGeo.width, + outerGeo.height, + outerGeo.radii, + segment, + ); + + // Inner path (inset factor 5/3) + const innerGeo = calculateGeometry(5 / 3); + const path2 = generateSegmentPath( + innerGeo.x, + innerGeo.y, + innerGeo.width, + innerGeo.height, + innerGeo.radii, + segment, + ); + + return path1 + (path1 && path2 ? " " : "") + path2; // Combine paths + } else { + const geometry = calculateGeometry(1); + return generateSegmentPath( + geometry.x, + geometry.y, + geometry.width, + geometry.height, + geometry.radii, + segment + ); + } +} diff --git a/src/paint-canvas.ts b/src/paint-canvas.ts index e694116..b4b5503 100644 --- a/src/paint-canvas.ts +++ b/src/paint-canvas.ts @@ -42,7 +42,7 @@ export interface CanvasRenderingContext2D { scale(x: number, y: number): void; stroke(): void; stroke(path?: any): void; // For Path2D - fill(): void; + fill(path?: any): void; // For Path2D beginPath(): void; closePath(): void; save(): void; @@ -331,10 +331,12 @@ export default class CanvasPaintBackend implements PaintBackend { path(pathData: string) { const { r, g, b, a } = this.strokeColor; + const { r: fr, g: fg, b: fb, a: fa } = this.fillColor; this.ctx.beginPath(); // Apply stroke properties this.ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${a})`; + this.ctx.fillStyle = `rgba(${fr}, ${fg}, ${fb}, ${fa})`; this.ctx.lineWidth = this.lineWidth; if (this.strokeLinecap) { @@ -358,6 +360,9 @@ export default class CanvasPaintBackend implements PaintBackend { if (typeof Path2D !== "undefined") { try { const path2D = new Path2D(pathData); + if ( fa > 0 ) { + this.ctx.fill(path2D); + } this.ctx.stroke(path2D); return; } catch (e) { @@ -367,6 +372,9 @@ export default class CanvasPaintBackend implements PaintBackend { // Basic SVG path parsing for simple cases (fallback for node-canvas and older browsers) this.parseSvgPath(pathData); + if ( fa > 0 ) { + this.ctx.fill(); + } this.ctx.stroke(); } diff --git a/src/paint-html.ts b/src/paint-html.ts index 5ed2210..a203b2a 100644 --- a/src/paint-html.ts +++ b/src/paint-html.ts @@ -142,7 +142,9 @@ export default class HtmlPaintBackend implements PaintBackend { path(pathData: string) { // For HTML backend, render SVG path as an inline SVG element const { r, g, b, a } = this.strokeColor; + const { r: fr, g: fg, b: fb, a: fa } = this.fillColor; const stroke = `rgba(${r}, ${g}, ${b}, ${a})`; + const fill = fa > 0 ? `rgba(${fr}, ${fg}, ${fb}, ${fa})` : "none"; const strokeDasharray = this.strokeDasharray ? `stroke-dasharray="${this.strokeDasharray}"` @@ -156,7 +158,7 @@ export default class HtmlPaintBackend implements PaintBackend { // Create a minimal SVG container for the path this.s += ``; - this.s += ``; + this.s += ``; this.s += ``; } diff --git a/src/paint-svg.ts b/src/paint-svg.ts index f447e11..c2f92cb 100644 --- a/src/paint-svg.ts +++ b/src/paint-svg.ts @@ -143,7 +143,9 @@ export default class SvgPaintBackend implements PaintBackend { path(pathData: string) { const { r, g, b, a } = this.strokeColor; + const { r: fr, g: fg, b: fb, a: fa } = this.fillColor; const stroke = `rgba(${r}, ${g}, ${b}, ${a})`; + const fill = fa > 0 ? `rgba(${fr}, ${fg}, ${fb}, ${fa})` : "none"; const rect = this.clips.at(-1); const clipPath = rect ? `clip-path="url(#${rect.id}) "` : " "; @@ -157,7 +159,7 @@ export default class SvgPaintBackend implements PaintBackend { ? `stroke-linejoin="${this.strokeLinejoin}" ` : ""; - this.main += ``; + this.main += ``; } pushClip(x: number, y: number, width: number, height: number) { diff --git a/src/paint.ts b/src/paint.ts index 6a0bcbb..a22c996 100644 --- a/src/paint.ts +++ b/src/paint.ts @@ -14,24 +14,8 @@ import type { InlineLevel, BlockLevel } from "./layout-flow.ts"; import type { BackgroundBox } from "./layout-text.ts"; import type { Color } from "./style.ts"; import type { LoadedFontFace } from "./text-font.ts"; - -// Border rendering constants -const BORDER_DASHED_ARRAY = [4, 4]; -const BORDER_DOTTED_ARRAY = [0, 2]; - -interface BorderInfo { - width: number; - style: string; - color: Color; -} - -interface BoxBorderSegment { - firstSide: "left" | "top" | "right" | "bottom"; - left: boolean; - top: boolean; - right: boolean; - bottom: boolean; -} +import { getBorderSegments, isBorderVisible, getStrokePropertiesFromBorder, generateBorderPath, } from "./box-border.ts"; +import type { BoxBorderSegment, BoxBorder, BorderInfo } from "./box-border.ts"; export interface PaintBackend { fillColor: Color; @@ -186,25 +170,8 @@ function paintFormattingBoxBackground( const style = box.style; const borderArea = box.getBorderArea(); - if (!isRoot) { - const paddingArea = box.getPaddingArea(); - const contentArea = box.getContentArea(); - const { backgroundColor, backgroundClip } = style; - const area = - backgroundClip === "border-box" - ? borderArea - : backgroundClip === "padding-box" - ? paddingArea - : contentArea; - - if (backgroundColor.a > 0) { - b.fillColor = backgroundColor; - b.rect(area.x, area.y, area.width, area.height); - } - } - // Advanced border rendering with path-based system - const borders = { + const borders: BoxBorder = { left: { width: style.borderLeftWidth, style: style.borderLeftStyle, @@ -235,13 +202,58 @@ function paintFormattingBoxBackground( bottomLeft: box.style.getBorderBottomLeftRadius(box), }; + const hasRadius = borders.topLeft.horizontal > 0 || borders.topLeft.vertical > 0 || borders.topRight.horizontal > 0 || borders.topRight.vertical > 0 || borders.bottomRight.horizontal > 0 || borders.bottomRight.vertical > 0 || borders.bottomLeft.horizontal > 0 || borders.bottomLeft.vertical > 0; + + if (!isRoot) { + const paddingArea = box.getPaddingArea(); + const contentArea = box.getContentArea(); + const { backgroundColor, backgroundClip } = style; + const area = + backgroundClip === "border-box" + ? borderArea + : backgroundClip === "padding-box" + ? paddingArea + : contentArea; + + if (backgroundColor.a > 0) { + b.fillColor = backgroundColor; + // if a border radius is set we'll need to draw the background with a path; + // otherwise we can just draw a rect + if (hasRadius) { + // for background we pretend there's a solid border on all sides; we just care about + // capturing radius and the theoretical border area; we set the width of each border to 0 + // so that the fill is not inset + const emptyBorder: BorderInfo = { + width: 0, + style: "solid", + color: { r: 0, g: 0, b: 0, a: 0 }, + }; + const backgroundSegment: BoxBorderSegment = { + firstSide: "top", + left: true, + top: true, + right: true, + bottom: true, + }; + + b.fillColor = { r: backgroundColor.r, g: backgroundColor.g, b: backgroundColor.b, a: backgroundColor.a }; + b.lineWidth = 0; + b.strokeColor = { r: 0, g: 0, b: 0, a: 0 }; + b.path(generateBorderPath(area.x, area.y, area.width, area.height, { ...borders, left: emptyBorder, top: emptyBorder, right: emptyBorder, bottom: emptyBorder }, backgroundSegment, "solid")); + } else { + b.rect(area.x, area.y, area.width, area.height); + } + } + } + + // determine which border segments need to be drawn, and which is first const segments = getBorderSegments(borders); for (const segment of segments) { - const border = borders[segment.firstSide]; - if (!isBorderVisible(border)) continue; + const localBorder = borders[segment.firstSide]; + if (!isBorderVisible(localBorder)) continue; - const strokeProps = getStrokePropertiesFromBorder(border); + const strokeProps = getStrokePropertiesFromBorder(localBorder); // Apply stroke properties to backend b.strokeColor = strokeProps.strokeColor; @@ -249,54 +261,16 @@ function paintFormattingBoxBackground( b.strokeDasharray = strokeProps.strokeDasharray; b.strokeLinecap = strokeProps.strokeLinecap; - if (border.style === "double") { - // For double borders, we need to draw two separate lines - // Each line should be 1/3 of the total border width - // The outer line is at the border edge, the inner line is 2/3 inset - - // Set stroke width to 1/3 of border width for double borders - const doubleStrokeWidth = border.width > 0 ? border.width / 3 : 0; - b.lineWidth = doubleStrokeWidth; - - // Draw the outer border (at border edge) - const outerPath = generateBorderPathForDouble( - borderArea.x, - borderArea.y, - borderArea.width, - borderArea.height, - borders, - borderRadii, - segment, - "outer" - ); - b.path(outerPath); - - // Draw the inner border (inset by 2/3 of border width) - const innerPath = generateBorderPathForDouble( - borderArea.x, - borderArea.y, - borderArea.width, - borderArea.height, - borders, - borderRadii, - segment, - "inner" - ); - b.path(innerPath); - } else { - // For all other border styles, draw a single path - const path = generateBorderPath( - borderArea.x, - borderArea.y, - borderArea.width, - borderArea.height, - borders, - borderRadii, - segment, - 1 - ); - b.path(path); - } + const path = generateBorderPath( + borderArea.x, + borderArea.y, + borderArea.width, + borderArea.height, + borders, + segment, + localBorder.style + ); + b.path(path); // Reset stroke properties to defaults b.strokeDasharray = undefined; @@ -985,663 +959,4 @@ export default function paint(block: BlockContainer, b: PaintBackend) { } } -// Get stroke properties for a border style -function getStrokePropertiesFromBorder(border: BorderInfo): { - strokeWidth: number; - strokeColor: Color; - strokeDasharray?: string; - strokeLinecap?: "butt" | "round" | "square"; -} { - const base = { - strokeWidth: border.width, - strokeColor: border.color, - }; - switch (border.style) { - case "dashed": - return { - ...base, - strokeDasharray: BORDER_DASHED_ARRAY.map((x) => x * border.width).join( - " " - ), - strokeLinecap: "butt", - }; - case "dotted": - return { - ...base, - strokeDasharray: BORDER_DOTTED_ARRAY.map((x) => x * border.width).join( - " " - ), - strokeLinecap: "round", - }; - case "solid": - case "double": - default: - return { - ...base, - strokeLinecap: "butt", - }; - } -} - -// Check if a border is visible (has width, style, and color alpha > 0) -function isBorderVisible(border: BorderInfo): boolean { - return ( - border.width > 0 && - border.style !== "none" && - border.style !== "hidden" && - border.color.a > 0 - ); -} - -// Check if two borders match (same width, style, and color) -function bordersMatch(border1: BorderInfo, border2: BorderInfo): boolean { - return ( - border1.width === border2.width && - border1.style === border2.style && - border1.color.r === border2.color.r && - border1.color.g === border2.color.g && - border1.color.b === border2.color.b && - border1.color.a === border2.color.a - ); -} - -// Detect contiguous border segments for optimization -function getBorderSegments(borders: { - left: BorderInfo; - top: BorderInfo; - right: BorderInfo; - bottom: BorderInfo; -}): BoxBorderSegment[] { - const { left, top, right, bottom } = borders; - - // Check for full uniform border - if ( - isBorderVisible(left) && - isBorderVisible(top) && - isBorderVisible(right) && - isBorderVisible(bottom) && - bordersMatch(left, top) && - bordersMatch(top, right) && - bordersMatch(right, bottom) - ) { - return [ - { - firstSide: "left", - left: true, - top: true, - right: true, - bottom: true, - }, - ]; - } - - // Check for three-side matches - const segments: BoxBorderSegment[] = []; - - // Left-Top-Right - if ( - isBorderVisible(left) && - isBorderVisible(top) && - isBorderVisible(right) && - bordersMatch(left, top) && - bordersMatch(top, right) - ) { - segments.push({ - firstSide: "left", - left: true, - top: true, - right: true, - bottom: false, - }); - if (isBorderVisible(bottom)) { - segments.push({ - firstSide: "bottom", - left: false, - top: false, - right: false, - bottom: true, - }); - } - return segments; - } - - // Top-Right-Bottom - if ( - isBorderVisible(top) && - isBorderVisible(right) && - isBorderVisible(bottom) && - bordersMatch(top, right) && - bordersMatch(right, bottom) - ) { - segments.push({ - firstSide: "top", - left: false, - top: true, - right: true, - bottom: true, - }); - if (isBorderVisible(left)) { - segments.push({ - firstSide: "left", - left: true, - top: false, - right: false, - bottom: false, - }); - } - return segments; - } - - // Right-Bottom-Left - if ( - isBorderVisible(right) && - isBorderVisible(bottom) && - isBorderVisible(left) && - bordersMatch(right, bottom) && - bordersMatch(bottom, left) - ) { - segments.push({ - firstSide: "right", - left: true, - top: false, - right: true, - bottom: true, - }); - if (isBorderVisible(top)) { - segments.push({ - firstSide: "top", - left: false, - top: true, - right: false, - bottom: false, - }); - } - return segments; - } - - // Bottom-Left-Top - if ( - isBorderVisible(bottom) && - isBorderVisible(left) && - isBorderVisible(top) && - bordersMatch(bottom, left) && - bordersMatch(left, top) - ) { - segments.push({ - firstSide: "bottom", - left: true, - top: true, - right: false, - bottom: true, - }); - if (isBorderVisible(right)) { - segments.push({ - firstSide: "right", - left: false, - top: false, - right: true, - bottom: false, - }); - } - return segments; - } - - // Check for two-side matches - // Left-Top - if ( - isBorderVisible(left) && - isBorderVisible(top) && - bordersMatch(left, top) - ) { - segments.push({ - firstSide: "left", - left: true, - top: true, - right: false, - bottom: false, - }); - } else { - if (isBorderVisible(left)) { - segments.push({ - firstSide: "left", - left: true, - top: false, - right: false, - bottom: false, - }); - } - if (isBorderVisible(top)) { - segments.push({ - firstSide: "top", - left: false, - top: true, - right: false, - bottom: false, - }); - } - } - - // Right-Bottom - if ( - isBorderVisible(right) && - isBorderVisible(bottom) && - bordersMatch(right, bottom) - ) { - segments.push({ - firstSide: "right", - left: false, - top: false, - right: true, - bottom: true, - }); - } else { - if (isBorderVisible(right)) { - segments.push({ - firstSide: "right", - left: false, - top: false, - right: true, - bottom: false, - }); - } - if (isBorderVisible(bottom)) { - segments.push({ - firstSide: "bottom", - left: false, - top: false, - right: false, - bottom: true, - }); - } - } - - return segments; -} - -// Generate SVG path for a border segment with radius support -function generateSegmentPath( - x: number, - y: number, - width: number, - height: number, - radii: { - tlHor: number; - tlVer: number; - trHor: number; - trVer: number; - brHor: number; - brVer: number; - blHor: number; - blVer: number; - }, - segment: BoxBorderSegment -): string { - let path = ""; - let started = false; - - const addLineOrMoveTo = (targetX: number, targetY: number) => { - if (!started) { - path += `M ${targetX} ${targetY}`; - started = true; - } else { - path += ` L ${targetX} ${targetY}`; - } - }; - - const addArcTo = ( - rx: number, - ry: number, - targetX: number, - targetY: number - ) => { - if (rx > 0 && ry > 0) { - path += ` A ${rx} ${ry} 0 0 1 ${targetX} ${targetY}`; - } else { - addLineOrMoveTo(targetX, targetY); - } - }; - - // Handle each side of the segment - if (segment.left) { - // Left side: bottom-left corner to top-left corner - if (segment.bottom) { - // Start from bottom-left corner (after radius) - addLineOrMoveTo(x, y + height - radii.blVer); - } else { - // Start from bottom of left side - addLineOrMoveTo(x, y + height); - } - - // Draw to top-left corner (before radius) - if (radii.tlHor > 0 || radii.tlVer > 0) { - addLineOrMoveTo(x, y + radii.tlVer); - if (segment.top) { - // Draw top-left arc - addArcTo(radii.tlHor, radii.tlVer, x + radii.tlHor, y); - } - } else { - addLineOrMoveTo(x, y); - if (segment.top) { - addLineOrMoveTo(x + radii.tlHor, y); - } - } - } - - if (segment.top) { - // Top side: top-left corner to top-right corner - if (!segment.left) { - // Start from left end of top side - addLineOrMoveTo(x, y); - } - - // Draw to top-right corner (before radius) - if (radii.trHor > 0 || radii.trVer > 0) { - addLineOrMoveTo(x + width - radii.trHor, y); - if (segment.right) { - // Draw top-right arc - addArcTo(radii.trHor, radii.trVer, x + width, y + radii.trVer); - } - } else { - addLineOrMoveTo(x + width, y); - if (segment.right) { - addLineOrMoveTo(x + width, y + radii.trVer); - } - } - } - - if (segment.right) { - // Right side: top-right corner to bottom-right corner - if (!segment.top) { - // Start from top of right side - addLineOrMoveTo(x + width, y); - } - - // Draw to bottom-right corner (before radius) - if (radii.brHor > 0 || radii.brVer > 0) { - addLineOrMoveTo(x + width, y + height - radii.brVer); - if (segment.bottom) { - // Draw bottom-right arc - addArcTo(radii.brHor, radii.brVer, x + width - radii.brHor, y + height); - } - } else { - addLineOrMoveTo(x + width, y + height); - if (segment.bottom) { - addLineOrMoveTo(x + width - radii.brHor, y + height); - } - } - } - - if (segment.bottom) { - // Bottom side: bottom-right corner to bottom-left corner - if (!segment.right) { - // Start from right end of bottom side - addLineOrMoveTo(x + width, y + height); - } - - // Draw to bottom-left corner (before radius) - if (radii.blHor > 0 || radii.blVer > 0) { - addLineOrMoveTo(x + radii.blHor, y + height); - if (segment.left) { - // Draw bottom-left arc - addArcTo(radii.blHor, radii.blVer, x, y + height - radii.blVer); - } - } else { - addLineOrMoveTo(x, y + height); - if (segment.left) { - addLineOrMoveTo(x, y + height - radii.blVer); - } - } - } - - // Close path only if the segment includes all four sides - if (segment.left && segment.top && segment.right && segment.bottom) { - path += " Z"; - } - - return path; -} - -// Generate border path with radius support -function generateBorderPath( - x: number, - y: number, - width: number, - height: number, - borders: { - left: BorderInfo; - top: BorderInfo; - right: BorderInfo; - bottom: BorderInfo; - }, - borderRadii: { - topLeft: { horizontal: number; vertical: number }; - topRight: { horizontal: number; vertical: number }; - bottomRight: { horizontal: number; vertical: number }; - bottomLeft: { horizontal: number; vertical: number }; - }, - segment: BoxBorderSegment, - insetFactor: number = 1 -): string { - // Calculate geometry based on inset factor - const calculateGeometry = (factor: number) => { - const leftInset = (borders.left.width * factor) / 2; - const topInset = (borders.top.width * factor) / 2; - const rightInset = (borders.right.width * factor) / 2; - const bottomInset = (borders.bottom.width * factor) / 2; - - const adjustedX = x + leftInset; - const adjustedY = y + topInset; - const adjustedWidth = width - leftInset - rightInset; - const adjustedHeight = height - topInset - bottomInset; - - // Scale border radii proportionally with proper overlap handling - // Only apply corner radii when both adjacent borders are present in the segment - let tlHor = - segment.left && segment.top - ? Math.max(0, borderRadii.topLeft.horizontal - leftInset) - : 0; - let tlVer = - segment.left && segment.top - ? Math.max(0, borderRadii.topLeft.vertical - topInset) - : 0; - let trHor = - segment.top && segment.right - ? Math.max(0, borderRadii.topRight.horizontal - rightInset) - : 0; - let trVer = - segment.top && segment.right - ? Math.max(0, borderRadii.topRight.vertical - topInset) - : 0; - let brHor = - segment.right && segment.bottom - ? Math.max(0, borderRadii.bottomRight.horizontal - rightInset) - : 0; - let brVer = - segment.right && segment.bottom - ? Math.max(0, borderRadii.bottomRight.vertical - bottomInset) - : 0; - let blHor = - segment.bottom && segment.left - ? Math.max(0, borderRadii.bottomLeft.horizontal - leftInset) - : 0; - let blVer = - segment.bottom && segment.left - ? Math.max(0, borderRadii.bottomLeft.vertical - bottomInset) - : 0; - - // Adjust radii ratios if they overlap (like in reference implementation) - const topRatio = adjustedWidth / (tlHor + trHor || 1); - const rightRatio = adjustedHeight / (trVer + brVer || 1); - const bottomRatio = adjustedWidth / (blHor + brHor || 1); - const leftRatio = adjustedHeight / (blVer + tlVer || 1); - const smallestRatio = Math.min( - topRatio, - rightRatio, - bottomRatio, - leftRatio - ); - - if (smallestRatio < 1) { - tlHor *= smallestRatio; - tlVer *= smallestRatio; - trHor *= smallestRatio; - trVer *= smallestRatio; - brHor *= smallestRatio; - brVer *= smallestRatio; - blHor *= smallestRatio; - blVer *= smallestRatio; - } - - return { - x: adjustedX, - y: adjustedY, - width: adjustedWidth, - height: adjustedHeight, - radii: { - tlHor, - tlVer, - trHor, - trVer, - brHor, - brVer, - blHor, - blVer, - }, - }; - }; - - const geometry = calculateGeometry(insetFactor); - return generateSegmentPath( - geometry.x, - geometry.y, - geometry.width, - geometry.height, - geometry.radii, - segment - ); -} - -// Generate path for double borders with proper positioning -function generateBorderPathForDouble( - x: number, - y: number, - width: number, - height: number, - borders: { - left: BorderInfo; - top: BorderInfo; - right: BorderInfo; - bottom: BorderInfo; - }, - borderRadii: { - topLeft: { horizontal: number; vertical: number }; - topRight: { horizontal: number; vertical: number }; - bottomRight: { horizontal: number; vertical: number }; - bottomLeft: { horizontal: number; vertical: number }; - }, - segment: BoxBorderSegment, - position: "outer" | "inner" -): string { - // For double borders, each line is 1/3 of the total border width - // Gap between lines is also 1/3 of the total border width - // Outer offset: strokeWidth/2 (centers stroke on border edge) - // Inner offset: strokeWidth/2 + gapWidth + strokeWidth/2 = 1.5 * strokeWidth - - const totalWidth = borders[segment.firstSide].width; - const strokeWidth = totalWidth > 0 ? totalWidth / 3 : 0; - const gapWidth = strokeWidth; - - let insetFactor: number; - if (position === "outer") { - // Outer border: inset by strokeWidth/2 to center on border edge - insetFactor = strokeWidth / 2 / totalWidth; - } else { - // Inner border: inset by strokeWidth/2 + gapWidth + strokeWidth/2 - insetFactor = (strokeWidth / 2 + gapWidth + strokeWidth / 2) / totalWidth; - } - - // Calculate per-side insets using the inset factor - const leftInset = borders.left.width * insetFactor; - const topInset = borders.top.width * insetFactor; - const rightInset = borders.right.width * insetFactor; - const bottomInset = borders.bottom.width * insetFactor; - - const adjustedX = x + leftInset; - const adjustedY = y + topInset; - const adjustedWidth = width - leftInset - rightInset; - const adjustedHeight = height - topInset - bottomInset; - - // Scale border radii proportionally with proper overlap handling - // Only apply corner radii when both adjacent borders are present in the segment - let tlHor = - segment.left && segment.top - ? Math.max(0, borderRadii.topLeft.horizontal - leftInset) - : 0; - let tlVer = - segment.left && segment.top - ? Math.max(0, borderRadii.topLeft.vertical - topInset) - : 0; - let trHor = - segment.top && segment.right - ? Math.max(0, borderRadii.topRight.horizontal - rightInset) - : 0; - let trVer = - segment.top && segment.right - ? Math.max(0, borderRadii.topRight.vertical - topInset) - : 0; - let brHor = - segment.right && segment.bottom - ? Math.max(0, borderRadii.bottomRight.horizontal - rightInset) - : 0; - let brVer = - segment.right && segment.bottom - ? Math.max(0, borderRadii.bottomRight.vertical - bottomInset) - : 0; - let blHor = - segment.bottom && segment.left - ? Math.max(0, borderRadii.bottomLeft.horizontal - leftInset) - : 0; - let blVer = - segment.bottom && segment.left - ? Math.max(0, borderRadii.bottomLeft.vertical - bottomInset) - : 0; - - // Adjust radii ratios if they overlap (like in reference implementation) - const topRatio = adjustedWidth / (tlHor + trHor || 1); - const rightRatio = adjustedHeight / (trVer + brVer || 1); - const bottomRatio = adjustedWidth / (blHor + brHor || 1); - const leftRatio = adjustedHeight / (blVer + tlVer || 1); - const smallestRatio = Math.min(topRatio, rightRatio, bottomRatio, leftRatio); - - if (smallestRatio < 1) { - tlHor *= smallestRatio; - tlVer *= smallestRatio; - trHor *= smallestRatio; - trVer *= smallestRatio; - brHor *= smallestRatio; - brVer *= smallestRatio; - blHor *= smallestRatio; - blVer *= smallestRatio; - } - - const adjustedRadii = { - tlHor, - tlVer, - trHor, - trVer, - brHor, - brVer, - blHor, - blVer, - }; - - return generateSegmentPath( - adjustedX, - adjustedY, - adjustedWidth, - adjustedHeight, - adjustedRadii, - segment - ); -} diff --git a/test/paint-spy.js b/test/paint-spy.js index b4b1929..8816c98 100644 --- a/test/paint-spy.js +++ b/test/paint-spy.js @@ -43,6 +43,7 @@ export default class PaintSpy { path(pathData) { const strokeColor = this.strokeColor; + const fillColor = this.fillColor; const lineWidth = this.lineWidth; const strokeDasharray = this.strokeDasharray; const strokeLinecap = this.strokeLinecap; @@ -50,6 +51,7 @@ export default class PaintSpy { t: "path", pathData, strokeColor, + fillColor, lineWidth, strokeDasharray, strokeLinecap, @@ -92,9 +94,17 @@ export default class PaintSpy { if (call.t === "rect" || call.t === "text") { const { r, g, b, a } = call.fillColor; return { ...call, fillColor: hex(r, g, b, a) }; - } else if (call.t === "edge" || call.t === "path") { + } else if (call.t === "edge") { const { r, g, b, a } = call.strokeColor; return { ...call, strokeColor: hex(r, g, b, a) }; + } else if (call.t === "path") { + const { r, g, b, a } = call.strokeColor; + const { r: fr, g: fg, b: fb, a: fa } = call.fillColor; + return { + ...call, + strokeColor: hex(r, g, b, a), + fillColor: hex(fr, fg, fb, fa) + }; } else { return call; } From bec84605e9cd7894e69b512ef3a88a09e07322ff Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:14:25 -0700 Subject: [PATCH 05/11] Enhance border handling and parsing - Updated the BoxBorder interface to use ComputedBorderRadius for corner radius values, improving type consistency. - Introduced a new hasBorderRadius function to determine if a border has a radius based on its properties. - Refactored paintFormattingBoxBackground to utilize the new hasBorderRadius function for better clarity and efficiency. - Modified CSS parsing rules to replace LENGTH with length_side for border radius properties, ensuring accurate parsing of side lengths. - Adjusted style definitions to align with the new ComputedBorderRadius type for better integration across the codebase. --- src/box-border.ts | 46 ++++++++++++++--- src/paint.ts | 6 +-- src/parse-css.js | 44 ++++++++-------- src/parse-css.pegjs | 24 ++++----- src/style.ts | 121 +++++++++++++++++++------------------------- 5 files changed, 125 insertions(+), 116 deletions(-) diff --git a/src/box-border.ts b/src/box-border.ts index 55ec0a6..f1ceecf 100644 --- a/src/box-border.ts +++ b/src/box-border.ts @@ -1,19 +1,17 @@ -import { Color } from "./style.js"; +import { Color, BorderStyle, BorderRadius } from "./style.js"; // Border rendering constants export const BORDER_DASHED_ARRAY = [4, 4]; export const BORDER_DOTTED_ARRAY = [0, 2]; -export type BorderStyle = "solid" | "dashed" | "dotted" | "double" | "hidden" | "groove" | "ridge" | "inset" | "outset" | "none"; - export interface BorderInfo { width: number; style: BorderStyle; color: Color; } -export interface BorderRadius { +export type ComputedBorderRadius = { horizontal: number; vertical: number; } @@ -23,10 +21,10 @@ export interface BoxBorder { top: BorderInfo; right: BorderInfo; bottom: BorderInfo; - topLeft: BorderRadius; - topRight: BorderRadius; - bottomRight: BorderRadius; - bottomLeft: BorderRadius; + topLeft: ComputedBorderRadius; + topRight: ComputedBorderRadius; + bottomRight: ComputedBorderRadius; + bottomLeft: ComputedBorderRadius; } export interface BoxBorderSegment { @@ -99,6 +97,38 @@ function bordersMatch(border1: BorderInfo, border2: BorderInfo): boolean { border1.color.a === border2.color.a ); } +// Check if a border has a radius +export function hasBorderRadius(border: BoxBorder): boolean { + // for each corner, there's a radius if the value is a number or a percentage + // that's greater than 0, or if it's an object and either horizontal or vertical is greater than 0 + // Percentages have a value and unit property; horizontal and vertical can be numbers or a Lenght or Percentage + // each of which has a value property + // NOTE: at the moment this only gets called with a ComputedBorderRadius, but leaving the code for + // BorderRadius for now in case we need to support this directly... + function hasRadius(radius: BorderRadius): boolean { + if ( typeof radius === "number" && radius > 0 ) { + return true; + } else if ( typeof radius === "object" && "value" in radius && "unit" in radius && radius.value > 0 ) { + return true; + } else if ( typeof radius === "object" && "horizontal" in radius && "vertical" in radius ) + { + if ( typeof radius.horizontal === "number" && radius.horizontal > 0 ) { + return true; + } else if ( typeof radius.horizontal === "object" && "value" in radius.horizontal && radius.horizontal.value > 0 ) { + return true; + } + if ( typeof radius.vertical === "number" && radius.vertical > 0 ) { + return true; + } else if ( typeof radius.vertical === "object" && "value" in radius.vertical && radius.vertical.value > 0 ) { + return true; + } + } + return false; + } + + return hasRadius(border.topLeft) || hasRadius(border.topRight) || hasRadius(border.bottomRight) || hasRadius(border.bottomLeft); +} + // Detect contiguous border segments for optimization export function getBorderSegments(borders: { left: BorderInfo; diff --git a/src/paint.ts b/src/paint.ts index a22c996..78e87ed 100644 --- a/src/paint.ts +++ b/src/paint.ts @@ -14,7 +14,7 @@ import type { InlineLevel, BlockLevel } from "./layout-flow.ts"; import type { BackgroundBox } from "./layout-text.ts"; import type { Color } from "./style.ts"; import type { LoadedFontFace } from "./text-font.ts"; -import { getBorderSegments, isBorderVisible, getStrokePropertiesFromBorder, generateBorderPath, } from "./box-border.ts"; +import { getBorderSegments, isBorderVisible, getStrokePropertiesFromBorder, generateBorderPath, hasBorderRadius } from "./box-border.ts"; import type { BoxBorderSegment, BoxBorder, BorderInfo } from "./box-border.ts"; export interface PaintBackend { @@ -202,8 +202,6 @@ function paintFormattingBoxBackground( bottomLeft: box.style.getBorderBottomLeftRadius(box), }; - const hasRadius = borders.topLeft.horizontal > 0 || borders.topLeft.vertical > 0 || borders.topRight.horizontal > 0 || borders.topRight.vertical > 0 || borders.bottomRight.horizontal > 0 || borders.bottomRight.vertical > 0 || borders.bottomLeft.horizontal > 0 || borders.bottomLeft.vertical > 0; - if (!isRoot) { const paddingArea = box.getPaddingArea(); const contentArea = box.getContentArea(); @@ -219,7 +217,7 @@ function paintFormattingBoxBackground( b.fillColor = backgroundColor; // if a border radius is set we'll need to draw the background with a path; // otherwise we can just draw a rect - if (hasRadius) { + if (hasBorderRadius(borders)) { // for background we pretend there's a solid border on all sides; we just care about // capturing radius and the theoretical border area; we set the width of each border to 0 // so that the fill is not inset diff --git a/src/parse-css.js b/src/parse-css.js index f79e0c3..90f7629 100644 --- a/src/parse-css.js +++ b/src/parse-css.js @@ -7747,7 +7747,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -7755,7 +7755,7 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parseLENGTH(); + s7 = peg$parselength_side(); if (s7 !== peg$FAILED) { peg$savedPos = s0; s0 = peg$f108(s5, s7); @@ -7805,7 +7805,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 === peg$FAILED) { s5 = peg$parsedefault(); } @@ -7861,7 +7861,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -7869,7 +7869,7 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parseLENGTH(); + s7 = peg$parselength_side(); if (s7 !== peg$FAILED) { peg$savedPos = s0; s0 = peg$f110(s5, s7); @@ -7919,7 +7919,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 === peg$FAILED) { s5 = peg$parsedefault(); } @@ -7975,7 +7975,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -7983,7 +7983,7 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parseLENGTH(); + s7 = peg$parselength_side(); if (s7 !== peg$FAILED) { peg$savedPos = s0; s0 = peg$f112(s5, s7); @@ -8033,7 +8033,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 === peg$FAILED) { s5 = peg$parsedefault(); } @@ -8089,7 +8089,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -8097,7 +8097,7 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parseLENGTH(); + s7 = peg$parselength_side(); if (s7 !== peg$FAILED) { peg$savedPos = s0; s0 = peg$f114(s5, s7); @@ -8147,7 +8147,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 === peg$FAILED) { s5 = peg$parsedefault(); } @@ -8203,7 +8203,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -8211,7 +8211,7 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parseLENGTH(); + s7 = peg$parselength_side(); if (s7 !== peg$FAILED) { s8 = []; s9 = peg$parseS(); @@ -8219,7 +8219,7 @@ function peg$parse(input, options) { s8.push(s9); s9 = peg$parseS(); } - s9 = peg$parseLENGTH(); + s9 = peg$parselength_side(); if (s9 !== peg$FAILED) { s10 = []; s11 = peg$parseS(); @@ -8227,7 +8227,7 @@ function peg$parse(input, options) { s10.push(s11); s11 = peg$parseS(); } - s11 = peg$parseLENGTH(); + s11 = peg$parselength_side(); if (s11 !== peg$FAILED) { peg$savedPos = s0; s0 = peg$f116(s5, s7, s9, s11); @@ -8285,7 +8285,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -8293,7 +8293,7 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parseLENGTH(); + s7 = peg$parselength_side(); if (s7 !== peg$FAILED) { s8 = []; s9 = peg$parseS(); @@ -8301,7 +8301,7 @@ function peg$parse(input, options) { s8.push(s9); s9 = peg$parseS(); } - s9 = peg$parseLENGTH(); + s9 = peg$parselength_side(); if (s9 !== peg$FAILED) { peg$savedPos = s0; s0 = peg$f117(s5, s7, s9); @@ -8355,7 +8355,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -8363,7 +8363,7 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parseLENGTH(); + s7 = peg$parselength_side(); if (s7 !== peg$FAILED) { peg$savedPos = s0; s0 = peg$f118(s5, s7); @@ -8413,7 +8413,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parseLENGTH(); + s5 = peg$parselength_side(); if (s5 === peg$FAILED) { s5 = peg$parsedefault(); } diff --git a/src/parse-css.pegjs b/src/parse-css.pegjs index a392658..30b2c4e 100644 --- a/src/parse-css.pegjs +++ b/src/parse-css.pegjs @@ -672,39 +672,39 @@ border_color_dec } border_top_left_radius_dec - = 'border-top-left-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + = 'border-top-left-radius'i S* ':' S* h:length_side S* v:length_side { return {borderTopLeftRadius: {horizontal: h, vertical: v}}; } - / 'border-top-left-radius'i S* ':' S* r:(LENGTH / default) { + / 'border-top-left-radius'i S* ':' S* r:(length_side / default) { return {borderTopLeftRadius: r}; } border_top_right_radius_dec - = 'border-top-right-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + = 'border-top-right-radius'i S* ':' S* h:length_side S* v:length_side { return {borderTopRightRadius: {horizontal: h, vertical: v}}; } - / 'border-top-right-radius'i S* ':' S* r:(LENGTH / default) { + / 'border-top-right-radius'i S* ':' S* r:(length_side / default) { return {borderTopRightRadius: r}; } border_bottom_right_radius_dec - = 'border-bottom-right-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + = 'border-bottom-right-radius'i S* ':' S* h:length_side S* v:length_side { return {borderBottomRightRadius: {horizontal: h, vertical: v}}; } - / 'border-bottom-right-radius'i S* ':' S* r:(LENGTH / default) { + / 'border-bottom-right-radius'i S* ':' S* r:(length_side / default) { return {borderBottomRightRadius: r}; } border_bottom_left_radius_dec - = 'border-bottom-left-radius'i S* ':' S* h:LENGTH S* v:LENGTH { + = 'border-bottom-left-radius'i S* ':' S* h:length_side S* v:length_side { return {borderBottomLeftRadius: {horizontal: h, vertical: v}}; } - / 'border-bottom-left-radius'i S* ':' S* r:(LENGTH / default) { + / 'border-bottom-left-radius'i S* ':' S* r:(length_side / default) { return {borderBottomLeftRadius: r}; } border_radius_dec - = 'border-radius'i S* ':' S* tl:LENGTH S* tr:LENGTH S* br:LENGTH S* bl:LENGTH { + = 'border-radius'i S* ':' S* tl:length_side S* tr:length_side S* br:length_side S* bl:length_side { return { borderTopLeftRadius: tl, borderTopRightRadius: tr, @@ -712,7 +712,7 @@ border_radius_dec borderBottomLeftRadius: bl }; } - / 'border-radius'i S* ':' S* t:LENGTH S* h:LENGTH S* b:LENGTH { + / 'border-radius'i S* ':' S* t:length_side S* h:length_side S* b:length_side { return { borderTopLeftRadius: t, borderTopRightRadius: h, @@ -720,7 +720,7 @@ border_radius_dec borderBottomLeftRadius: h }; } - / 'border-radius'i S* ':' S* v:LENGTH S* h:LENGTH { + / 'border-radius'i S* ':' S* v:length_side S* h:length_side { return { borderTopLeftRadius: v, borderTopRightRadius: h, @@ -728,7 +728,7 @@ border_radius_dec borderBottomLeftRadius: h }; } - / 'border-radius'i S* ':' S* r:(LENGTH / default) { + / 'border-radius'i S* ':' S* r:(length_side / default) { return { borderTopLeftRadius: r, borderTopRightRadius: r, diff --git a/src/style.ts b/src/style.ts index c390012..ab33b80 100644 --- a/src/style.ts +++ b/src/style.ts @@ -1,5 +1,6 @@ import { HTMLElement, TextNode } from "./dom.ts"; import { Box } from "./layout-box.ts"; +import type { ComputedBorderRadius } from "./box-border.ts"; export const inherited = Symbol("inherited"); @@ -127,7 +128,7 @@ type OuterDisplay = "inline" | "block" | "none"; type InnerDisplay = "flow" | "flow-root" | "none"; -type BorderStyle = +export type BorderStyle = | "none" | "hidden" | "dotted" @@ -147,7 +148,7 @@ type Float = "left" | "right" | "none"; type Clear = "left" | "right" | "both" | "none"; -type BorderRadius = +export type BorderRadius = | Length | Percentage | { horizontal: Length | Percentage; vertical: Length | Percentage }; @@ -296,22 +297,10 @@ interface ComputedStyle { borderRightColor: Color; borderBottomColor: Color; borderLeftColor: Color; - borderTopLeftRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; - borderTopRightRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; - borderBottomRightRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; - borderBottomLeftRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; + borderTopLeftRadius: BorderRadius; + borderTopRightRadius: BorderRadius; + borderBottomRightRadius: BorderRadius; + borderBottomLeftRadius: BorderRadius; paddingTop: number | Percentage; paddingRight: number | Percentage; paddingBottom: number | Percentage; @@ -395,22 +384,10 @@ export class Style { borderRightColor: Color; borderBottomColor: Color; borderLeftColor: Color; - borderTopLeftRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; - borderTopRightRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; - borderBottomRightRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; - borderBottomLeftRadius: - | number - | Percentage - | { horizontal: number | Percentage; vertical: number | Percentage }; + borderTopLeftRadius: BorderRadius; + borderTopRightRadius: BorderRadius; + borderBottomRightRadius: BorderRadius; + borderBottomLeftRadius: BorderRadius; paddingTop: number | Percentage; paddingRight: number | Percentage; paddingBottom: number | Percentage; @@ -768,52 +745,56 @@ export class Style { if (this[LogicalMaps[writingMode].borderLineRightWidth] > 0) return true; } + getRadius(radius: BorderRadius, box: Box): ComputedBorderRadius { + if ( typeof radius === "number" && radius > 0 ) { + return { horizontal: radius, vertical: radius }; + } else if ( typeof radius === "object" && "value" in radius && "unit" in radius && radius.unit === "%" ) { + return { horizontal: resolvePercent(box, radius.value), vertical: resolvePercent(box, radius.value) }; + } else if ( typeof radius === "object" && "value" in radius && "unit" in radius ) { + // em and fixed length units + // TODO: check if this is correct -- can we count on it being a number here? + return { horizontal: resolveEm(radius.value, this.fontSize) as number, vertical: resolveEm(radius.value, this.fontSize) as number }; + } else if ( typeof radius === "object" && "horizontal" in radius && "vertical" in radius ) + { + let h = 0; + let v = 0; + if ( typeof radius.horizontal === "number") { + h = radius.horizontal; + } else if ( typeof radius.horizontal === "object" && "value" in radius.horizontal ) { + if ( radius.horizontal.unit === "%" ) { + h = resolvePercent(box, radius.horizontal.value); + } else { + h = resolveEm(radius.horizontal.value, this.fontSize) as number; + } + } + if ( typeof radius.vertical === "number" ) { + v = radius.vertical; + } else if ( typeof radius.vertical === "object" && "value" in radius.vertical ) { + if ( radius.vertical.unit === "%" ) { + v = resolvePercent(box, radius.vertical.value); + } else { + v = resolveEm(radius.vertical.value, this.fontSize) as number; + } + } + return { horizontal: h, vertical: v }; + } + return { horizontal: 0, vertical: 0 }; + } + getBorderTopLeftRadius(box: Box) { - const radius = this.borderTopLeftRadius; - if (typeof radius === "object" && "horizontal" in radius) { - return { - horizontal: resolvePercent(box, radius.horizontal), - vertical: resolvePercent(box, radius.vertical), - }; - } - const resolved = resolvePercent(box, radius); - return { horizontal: resolved, vertical: resolved }; + return this.getRadius(this.borderTopLeftRadius, box); } getBorderTopRightRadius(box: Box) { - const radius = this.borderTopRightRadius; - if (typeof radius === "object" && "horizontal" in radius) { - return { - horizontal: resolvePercent(box, radius.horizontal), - vertical: resolvePercent(box, radius.vertical), - }; - } - const resolved = resolvePercent(box, radius); - return { horizontal: resolved, vertical: resolved }; + return this.getRadius(this.borderTopRightRadius, box); } getBorderBottomRightRadius(box: Box) { - const radius = this.borderBottomRightRadius; - if (typeof radius === "object" && "horizontal" in radius) { - return { - horizontal: resolvePercent(box, radius.horizontal), - vertical: resolvePercent(box, radius.vertical), - }; - } - const resolved = resolvePercent(box, radius); - return { horizontal: resolved, vertical: resolved }; + return this.getRadius(this.borderBottomRightRadius, box); } getBorderBottomLeftRadius(box: Box) { - const radius = this.borderBottomLeftRadius; - if (typeof radius === "object" && "horizontal" in radius) { - return { - horizontal: resolvePercent(box, radius.horizontal), - vertical: resolvePercent(box, radius.vertical), - }; - } - const resolved = resolvePercent(box, radius); - return { horizontal: resolved, vertical: resolved }; + return this.getRadius(this.borderBottomLeftRadius, box); } fontsEqual(style: Style, size = true) { From 15c08fc840ecef9bfc65ca7134bcc4025d0a73ea Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:08:59 -0700 Subject: [PATCH 06/11] Restoring patterns AI removed --- src/parse-css.pegjs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/parse-css.pegjs b/src/parse-css.pegjs index 30b2c4e..c96f187 100644 --- a/src/parse-css.pegjs +++ b/src/parse-css.pegjs @@ -768,7 +768,21 @@ border_dec if (s) setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); return ret; } - / 'border'i t:border_s? S* ':' S* c:color S* w:LENGTH S* { + / 'border'i t:border_s? S* ':' S* c:color S* s:border_style S* w:LENGTH? { + const ret = {}; + setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); + setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); + if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); + return ret; + } + / 'border'i t:border_s? S* ':' S* s:border_style S* c:color S* w:LENGTH? { + const ret = {}; + setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); + setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); + if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); + return ret; + } + / 'border'i t:border_s? S* ':' S* w:LENGTH S* { return setTopRightBottomLeftOr(t, {}, 'border', 'Width', w, w, w, w); } / 'border'i t:border_s? S* ':' S* c:color S* { From 2f453d7c40692534d8e2ebb0a7c32a2cdc8587aa Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:17:19 -0700 Subject: [PATCH 07/11] Update typography examples in examples.js - Renamed "Typography Showcase" to "Typography Tests" for clarity. - Enhanced typography section with detailed font weights, styles, and semantic elements. - Improved text examples to include various text decorations and styles for better demonstration of capabilities. --- site/examples.js | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/site/examples.js b/site/examples.js index 2e089c3..02732b4 100644 --- a/site/examples.js +++ b/site/examples.js @@ -103,18 +103,19 @@ export const examples = [ name: "Typography", html: `

- Typography Showcase + Typography Tests

- + +
+
+
archicenter (är-ki-sen'tər), n. [Gr. ἀρχή-, first, + κέντρον, center.] An archetype; an organism or an organ which may be regarded as the primitive or ancestral or central or unspecialized type from which allied organisms or homologous organs are descended. Also archecenter. Encyc. Brit., XXVIII. 343.
+

Font Weights & Styles

-

Light weight text (300)

-

Regular weight text (400)

-

Semi-bold weight text (600)

-

Bold weight text (700)

-

Italic text style

-

❌ Underlined text

-

❌ Strikethrough text

+

Thin (100), Extra Light (200), Light (300), Regular (400), Medium (500), Semi-Bold (600), Bold (700), Extra Bold (800), Black (900)

+

Italic text, Oblique text, Small Caps, All Small Caps

+

Underlined text, Overlined text, Strikethrough text, and Underlined and Overlined text

+

Dotted underlined text with offset, Dashed underlined text, Solid underlined, Double underlined text with offset, Wavy Underlined text with big offset

@@ -149,18 +150,25 @@ export const examples = [
-
+
+

Semantic Elements

+

The API integrates seamlessly with our system.

+

Execute the runProcess() function to start the task.

+

This feature was recently added to enhance performance.

+

The old method was deprecated due to security issues.

+

Check the output.log file for debug information.

+

Key updates are marked with Ctrl + S for quick saves.

+

The userCount variable tracks active users.

+
+

Text Sizes & Spacing

Small text (0.8em)

Normal text (1em)

Large text (1.2em)

Extra large text (1.5em)

-

Text with increased line height for better readability

Text with letter spacing

Text with word spacing

-
-

Special Characters & Symbols

Mathematical symbols: α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω

Currency symbols: $ € £ ¥ ₹ ₿

From 8df723d3eefe0f1b6a27ca225e745abcdcc1ab7f Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:54:06 -0700 Subject: [PATCH 08/11] Refactor border-radius parsing and handling - Updated the border-radius parsing rules to support both horizontal and vertical radius values, allowing for more flexible CSS definitions. - Introduced helper functions for expanding radius values and handling slash-separated radius lists. - Refactored related functions to improve clarity and maintainability, ensuring consistent handling of border radius properties across the codebase. --- src/parse-css.js | 366 +++++++++++++++++++------------------------- src/parse-css.pegjs | 74 +++++---- 2 files changed, 206 insertions(+), 234 deletions(-) diff --git a/src/parse-css.js b/src/parse-css.js index 90f7629..c09ccf9 100644 --- a/src/parse-css.js +++ b/src/parse-css.js @@ -1127,38 +1127,50 @@ function peg$parse(input, options) { var peg$f115 = function(r) { return {borderBottomLeftRadius: r}; }; - var peg$f116 = function(tl, tr, br, bl) { - return { - borderTopLeftRadius: tl, - borderTopRightRadius: tr, - borderBottomRightRadius: br, - borderBottomLeftRadius: bl - }; - }; - var peg$f117 = function(t, h, b) { - return { - borderTopLeftRadius: t, - borderTopRightRadius: h, - borderBottomRightRadius: b, - borderBottomLeftRadius: h - }; - }; - var peg$f118 = function(v, h) { + var peg$f116 = function(horiz, vert) { + // If "vert" is undefined then no "/" was present – vertical radii reuse horizontal ones + const hasSlash = vert !== null && vert !== undefined; + + const hVals = horiz; + const vVals = hasSlash ? vert : horiz; + + function expand(list) { + switch (list.length) { + case 1: return [list[0], list[0], list[0], list[0]]; + case 2: return [list[0], list[1], list[0], list[1]]; + case 3: return [list[0], list[1], list[2], list[1]]; + default: /* 4 or more – use first four */ return [list[0], list[1], list[2], list[3]]; + } + } + + const h = expand(hVals); + const v = expand(vVals); + + function make(idx) { + return hasSlash ? { horizontal: h[idx], vertical: v[idx] } : h[idx]; + } + return { - borderTopLeftRadius: v, - borderTopRightRadius: h, - borderBottomRightRadius: v, - borderBottomLeftRadius: h + borderTopLeftRadius: make(0), + borderTopRightRadius: make(1), + borderBottomRightRadius: make(2), + borderBottomLeftRadius: make(3) }; }; - var peg$f119 = function(r) { + var peg$f117 = function(d) { + // Global keywords expand to all four corners return { - borderTopLeftRadius: r, - borderTopRightRadius: r, - borderBottomRightRadius: r, - borderBottomLeftRadius: r + borderTopLeftRadius: d, + borderTopRightRadius: d, + borderBottomRightRadius: d, + borderBottomLeftRadius: d }; }; + var peg$f118 = function(first, rest) { + const values = [first].concat(rest ? rest.map(r => r[1]) : []); + return values.slice(0, 4); // ignore extras beyond 4 if present +}; + var peg$f119 = function(r) { return r; }; var peg$f120 = function(t, w, s, c) { const ret = {}; setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); @@ -1180,14 +1192,14 @@ function peg$parse(input, options) { if (s) setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); return ret; }; - var peg$f123 = function(t, c, w, s) { + var peg$f123 = function(t, c, s, w) { const ret = {}; - setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); - if (s) setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); + setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); + if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); return ret; }; - var peg$f124 = function(t, c, w) { + var peg$f124 = function(t, w) { return setTopRightBottomLeftOr(t, {}, 'border', 'Width', w, w, w, w); }; var peg$f125 = function(t, c) { @@ -8172,7 +8184,7 @@ function peg$parse(input, options) { } function peg$parseborder_radius_dec() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11; + var s0, s1, s2, s3, s4, s5, s6, s7; s0 = peg$currPos; if (input.substr(peg$currPos, 13).toLowerCase() === peg$c138) { @@ -8203,7 +8215,7 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parselength_side(); + s5 = peg$parseradius_list(); if (s5 !== peg$FAILED) { s6 = []; s7 = peg$parseS(); @@ -8211,38 +8223,12 @@ function peg$parse(input, options) { s6.push(s7); s7 = peg$parseS(); } - s7 = peg$parselength_side(); - if (s7 !== peg$FAILED) { - s8 = []; - s9 = peg$parseS(); - while (s9 !== peg$FAILED) { - s8.push(s9); - s9 = peg$parseS(); - } - s9 = peg$parselength_side(); - if (s9 !== peg$FAILED) { - s10 = []; - s11 = peg$parseS(); - while (s11 !== peg$FAILED) { - s10.push(s11); - s11 = peg$parseS(); - } - s11 = peg$parselength_side(); - if (s11 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f116(s5, s7, s9, s11); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; + s7 = peg$parseslash_radius(); + if (s7 === peg$FAILED) { + s7 = null; } + peg$savedPos = s0; + s0 = peg$f116(s5, s7); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8285,34 +8271,10 @@ function peg$parse(input, options) { s4.push(s5); s5 = peg$parseS(); } - s5 = peg$parselength_side(); + s5 = peg$parsedefault(); if (s5 !== peg$FAILED) { - s6 = []; - s7 = peg$parseS(); - while (s7 !== peg$FAILED) { - s6.push(s7); - s7 = peg$parseS(); - } - s7 = peg$parselength_side(); - if (s7 !== peg$FAILED) { - s8 = []; - s9 = peg$parseS(); - while (s9 !== peg$FAILED) { - s8.push(s9); - s9 = peg$parseS(); - } - s9 = peg$parselength_side(); - if (s9 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f117(s5, s7, s9); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$f117(s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -8325,115 +8287,119 @@ function peg$parse(input, options) { peg$currPos = s0; s0 = peg$FAILED; } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 13).toLowerCase() === peg$c138) { - s1 = input.substr(peg$currPos, 13); - peg$currPos += 13; + } + + return s0; + } + + function peg$parseradius_value() { + var s0; + + s0 = peg$parselength_side(); + if (s0 === peg$FAILED) { + s0 = peg$parsedefault(); + } + + return s0; + } + + function peg$parseradius_list() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseradius_value(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = []; + s5 = peg$parseS(); + if (s5 !== peg$FAILED) { + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseS(); + } + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s5 = peg$parseradius_value(); + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } + peg$currPos = s3; + s3 = peg$FAILED; } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseS(); - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseS(); - } - if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c1; - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } - } - if (s3 !== peg$FAILED) { - s4 = []; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = []; + s5 = peg$parseS(); + if (s5 !== peg$FAILED) { + while (s5 !== peg$FAILED) { + s4.push(s5); s5 = peg$parseS(); - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$parseS(); - } - s5 = peg$parselength_side(); - if (s5 !== peg$FAILED) { - s6 = []; - s7 = peg$parseS(); - while (s7 !== peg$FAILED) { - s6.push(s7); - s7 = peg$parseS(); - } - s7 = peg$parselength_side(); - if (s7 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f118(s5, s7); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; } } else { - peg$currPos = s0; - s0 = peg$FAILED; + s4 = peg$FAILED; } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 13).toLowerCase() === peg$c138) { - s1 = input.substr(peg$currPos, 13); - peg$currPos += 13; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e143); } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseS(); - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseS(); - } - if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c1; - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } - } - if (s3 !== peg$FAILED) { - s4 = []; - s5 = peg$parseS(); - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$parseS(); - } - s5 = peg$parselength_side(); - if (s5 === peg$FAILED) { - s5 = peg$parsedefault(); - } - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f119(s5); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (s4 !== peg$FAILED) { + s5 = peg$parseradius_value(); + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s3; + s3 = peg$FAILED; } + } else { + peg$currPos = s3; + s3 = peg$FAILED; } } + peg$savedPos = s0; + s0 = peg$f118(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslash_radius() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 47) { + s1 = peg$c2; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseS(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseS(); + } + s3 = peg$parseradius_list(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f119(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; } return s0; @@ -8740,7 +8706,7 @@ function peg$parse(input, options) { s7.push(s8); s8 = peg$parseS(); } - s8 = peg$parseLENGTH(); + s8 = peg$parseborder_style(); if (s8 !== peg$FAILED) { s9 = []; s10 = peg$parseS(); @@ -8748,7 +8714,7 @@ function peg$parse(input, options) { s9.push(s10); s10 = peg$parseS(); } - s10 = peg$parseborder_style(); + s10 = peg$parseLENGTH(); if (s10 === peg$FAILED) { s10 = null; } @@ -8804,7 +8770,7 @@ function peg$parse(input, options) { s5.push(s6); s6 = peg$parseS(); } - s6 = peg$parsecolor(); + s6 = peg$parseLENGTH(); if (s6 !== peg$FAILED) { s7 = []; s8 = peg$parseS(); @@ -8812,20 +8778,8 @@ function peg$parse(input, options) { s7.push(s8); s8 = peg$parseS(); } - s8 = peg$parseLENGTH(); - if (s8 !== peg$FAILED) { - s9 = []; - s10 = peg$parseS(); - while (s10 !== peg$FAILED) { - s9.push(s10); - s10 = peg$parseS(); - } - peg$savedPos = s0; - s0 = peg$f124(s2, s6, s8); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$f124(s2, s6); } else { peg$currPos = s0; s0 = peg$FAILED; diff --git a/src/parse-css.pegjs b/src/parse-css.pegjs index c96f187..de4edcd 100644 --- a/src/parse-css.pegjs +++ b/src/parse-css.pegjs @@ -704,39 +704,57 @@ border_bottom_left_radius_dec } border_radius_dec - = 'border-radius'i S* ':' S* tl:length_side S* tr:length_side S* br:length_side S* bl:length_side { - return { - borderTopLeftRadius: tl, - borderTopRightRadius: tr, - borderBottomRightRadius: br, - borderBottomLeftRadius: bl - }; - } - / 'border-radius'i S* ':' S* t:length_side S* h:length_side S* b:length_side { - return { - borderTopLeftRadius: t, - borderTopRightRadius: h, - borderBottomRightRadius: b, - borderBottomLeftRadius: h - }; - } - / 'border-radius'i S* ':' S* v:length_side S* h:length_side { + = 'border-radius'i S* ':' S* horiz:radius_list S* vert:slash_radius? { + // If "vert" is undefined then no "/" was present – vertical radii reuse horizontal ones + const hasSlash = vert !== null && vert !== undefined; + + const hVals = horiz; + const vVals = hasSlash ? vert : horiz; + + function expand(list) { + switch (list.length) { + case 1: return [list[0], list[0], list[0], list[0]]; + case 2: return [list[0], list[1], list[0], list[1]]; + case 3: return [list[0], list[1], list[2], list[1]]; + default: /* 4 or more – use first four */ return [list[0], list[1], list[2], list[3]]; + } + } + + const h = expand(hVals); + const v = expand(vVals); + + function make(idx) { + return hasSlash ? { horizontal: h[idx], vertical: v[idx] } : h[idx]; + } + return { - borderTopLeftRadius: v, - borderTopRightRadius: h, - borderBottomRightRadius: v, - borderBottomLeftRadius: h + borderTopLeftRadius: make(0), + borderTopRightRadius: make(1), + borderBottomRightRadius: make(2), + borderBottomLeftRadius: make(3) }; } - / 'border-radius'i S* ':' S* r:(length_side / default) { + / 'border-radius'i S* ':' S* d:default { + // Global keywords expand to all four corners return { - borderTopLeftRadius: r, - borderTopRightRadius: r, - borderBottomRightRadius: r, - borderBottomLeftRadius: r + borderTopLeftRadius: d, + borderTopRightRadius: d, + borderBottomRightRadius: d, + borderBottomLeftRadius: d }; } +// Helper rules -------------------------------------------------------------- + +radius_value = length_side / default + +radius_list = first:radius_value rest:(S+ radius_value)* { + const values = [first].concat(rest ? rest.map(r => r[1]) : []); + return values.slice(0, 4); // ignore extras beyond 4 if present +} + +slash_radius = '/' S* r:radius_list { return r; } + border_s = '-top' / '-right' / '-bottom' / '-left' border_dec @@ -761,7 +779,7 @@ border_dec if (s) setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); return ret; } - / 'border'i t:border_s? S* ':' S* c:color S* w:LENGTH S* s:border_style? { + / 'border'i t:border_s? S* ':' S* c:color S* w:LENGTH S* s:border_style? { const ret = {}; setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); @@ -775,7 +793,7 @@ border_dec if (w) setTopRightBottomLeftOr(t, ret, 'border', 'Width', w, w, w, w); return ret; } - / 'border'i t:border_s? S* ':' S* s:border_style S* c:color S* w:LENGTH? { + / 'border'i t:border_s? S* ':' S* s:border_style S* c:color S* w:LENGTH? { const ret = {}; setTopRightBottomLeftOr(t, ret, 'border', 'Color', c, c, c, c); setTopRightBottomLeftOr(t, ret, 'border', 'Style', s, s, s, s); From f63d0a5363dd51f96db2dcec4db795499527c1cd Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Thu, 25 Sep 2025 12:06:47 -0700 Subject: [PATCH 09/11] Update package-lock.json and refactor type imports - Bump version from 0.5.1 to 0.6.0 in package-lock.json. - Update @ddietr/codemirror-themes dependency to version 1.5.1. - Add @types/codemirror and @types/tern as new dev dependencies. - Change import statements in box-border.ts to use type imports for better clarity. - Remove unnecessary border radius object in paint.ts to streamline code. --- package-lock.json | 31 +++++++++++++++++++++++++++---- src/box-border.ts | 2 +- src/paint.ts | 4 ---- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index d83157d..6d0b909 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "dropflow", - "version": "0.5.1", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dropflow", - "version": "0.5.1", + "version": "0.6.0", "license": "MIT", "devDependencies": { "@codemirror/lang-html": "^6.4.8", "@codemirror/state": "^6.4.1", - "@ddietr/codemirror-themes": "^1.4.2", + "@ddietr/codemirror-themes": "^1.5.1", "@rollup/plugin-node-resolve": "^15.2.3", + "@types/codemirror": "^5.60.15", "@types/mocha": "^8.0.3", "@types/node": "^20.17.10", "canvas": "^3.1.0", @@ -145,7 +146,9 @@ } }, "node_modules/@ddietr/codemirror-themes": { - "version": "1.4.2", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ddietr/codemirror-themes/-/codemirror-themes-1.5.2.tgz", + "integrity": "sha512-hynMyioOgFM5FHqGtFcaH3kFFluo9KD2sKpDc/l86oGryahZWaKbOv46d8oPAoCmbggMm8WP5m9b64ZzIrPTDA==", "dev": true, "license": "MIT", "dependencies": { @@ -984,6 +987,16 @@ "win32" ] }, + "node_modules/@types/codemirror": { + "version": "5.60.16", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.16.tgz", + "integrity": "sha512-V/yHdamffSS075jit+fDxaOAmdP2liok8NSNJnAZfDJErzOheuygHZEhAJrfmk5TEyM32MhkZjwo/idX791yxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/tern": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1011,6 +1024,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "dev": true, diff --git a/src/box-border.ts b/src/box-border.ts index f1ceecf..7fc3026 100644 --- a/src/box-border.ts +++ b/src/box-border.ts @@ -1,4 +1,4 @@ -import { Color, BorderStyle, BorderRadius } from "./style.js"; +import type { Color, BorderStyle, BorderRadius } from "./style.js"; // Border rendering constants diff --git a/src/paint.ts b/src/paint.ts index 78e87ed..1c6e3d6 100644 --- a/src/paint.ts +++ b/src/paint.ts @@ -192,10 +192,6 @@ function paintFormattingBoxBackground( style: style.borderBottomStyle, color: style.borderBottomColor, }, - }; - - // Get border radius values from the style - const borderRadii = { topLeft: box.style.getBorderTopLeftRadius(box), topRight: box.style.getBorderTopRightRadius(box), bottomRight: box.style.getBorderBottomRightRadius(box), From ecff7e2a0773db2a429bb87b7c4dfd26c81d2dad Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:56:57 -0700 Subject: [PATCH 10/11] Refactor border-radius percentage calculations in Style class - Updated the handling of percentage values for border-radius to calculate horizontal and vertical radii based on the box's border area, improving accuracy. - Simplified the resolveEm function to handle percentage units more effectively, ensuring consistent unit resolution across styles. --- src/style.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/style.ts b/src/style.ts index ab33b80..529e656 100644 --- a/src/style.ts +++ b/src/style.ts @@ -749,7 +749,12 @@ export class Style { if ( typeof radius === "number" && radius > 0 ) { return { horizontal: radius, vertical: radius }; } else if ( typeof radius === "object" && "value" in radius && "unit" in radius && radius.unit === "%" ) { - return { horizontal: resolvePercent(box, radius.value), vertical: resolvePercent(box, radius.value) }; + // Percentages for border-radius: horizontal relative to box width, vertical relative to box height + const area = box.getBorderArea(); + return { + horizontal: (radius.value / 100) * area.width, + vertical: (radius.value / 100) * area.height, + }; } else if ( typeof radius === "object" && "value" in radius && "unit" in radius ) { // em and fixed length units // TODO: check if this is correct -- can we count on it being a number here? @@ -762,7 +767,8 @@ export class Style { h = radius.horizontal; } else if ( typeof radius.horizontal === "object" && "value" in radius.horizontal ) { if ( radius.horizontal.unit === "%" ) { - h = resolvePercent(box, radius.horizontal.value); + const area = box.getBorderArea(); + h = (radius.horizontal.value / 100) * area.width; } else { h = resolveEm(radius.horizontal.value, this.fontSize) as number; } @@ -771,7 +777,8 @@ export class Style { v = radius.vertical; } else if ( typeof radius.vertical === "object" && "value" in radius.vertical ) { if ( radius.vertical.unit === "%" ) { - v = resolvePercent(box, radius.vertical.value); + const area = box.getBorderArea(); + v = (radius.vertical.value / 100) * area.height; } else { v = resolveEm(radius.vertical.value, this.fontSize) as number; } @@ -1129,9 +1136,9 @@ function resolveEm( } else if (value.unit === "in") { return value.value * 96; } - } else { - return value; - } + } + // handles unit === "%" as well as number + return value; } function computeStyle(parentStyle: Style, cascadedStyle: DeclaredStyle) { From 4dd8f0b6d1536675351a5f165839c947413b76d8 Mon Sep 17 00:00:00 2001 From: Bob Pritchett <26860966+BobPritchett@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:10:06 -0700 Subject: [PATCH 11/11] resolveEm takes values that may have units --- src/style.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/style.ts b/src/style.ts index 529e656..145a25a 100644 --- a/src/style.ts +++ b/src/style.ts @@ -758,7 +758,7 @@ export class Style { } else if ( typeof radius === "object" && "value" in radius && "unit" in radius ) { // em and fixed length units // TODO: check if this is correct -- can we count on it being a number here? - return { horizontal: resolveEm(radius.value, this.fontSize) as number, vertical: resolveEm(radius.value, this.fontSize) as number }; + return { horizontal: resolveEm(radius, this.fontSize) as number, vertical: resolveEm(radius, this.fontSize) as number }; } else if ( typeof radius === "object" && "horizontal" in radius && "vertical" in radius ) { let h = 0; @@ -770,7 +770,7 @@ export class Style { const area = box.getBorderArea(); h = (radius.horizontal.value / 100) * area.width; } else { - h = resolveEm(radius.horizontal.value, this.fontSize) as number; + h = resolveEm(radius.horizontal, this.fontSize) as number; } } if ( typeof radius.vertical === "number" ) { @@ -780,7 +780,7 @@ export class Style { const area = box.getBorderArea(); v = (radius.vertical.value / 100) * area.height; } else { - v = resolveEm(radius.vertical.value, this.fontSize) as number; + v = resolveEm(radius.vertical, this.fontSize) as number; } } return { horizontal: h, vertical: v };