Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"eslint.format.enable": true,
"eslint.packageManager": "yarn",
Expand Down
2 changes: 2 additions & 0 deletions benchmark/Benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ void setStylesFromJson(const json& j, YGNodeRef node) {
YGNodeStyleSetDisplay(node, displayFromString(value));
} else if (key == "position-type") {
YGNodeStyleSetPositionType(node, positionTypeFromString(value));
} else if (key == "clip-path") {
YGNodeStyleSetClipPath(node, value.get_ref<const std::string&>().c_str());
} else if (key == "flex-grow") {
YGNodeStyleSetFlexGrow(node, value);
} else if (key == "flex-shrink") {
Expand Down
15 changes: 14 additions & 1 deletion capture/NodeToString.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static void appendYGValueIfNotDefault(
}
}

static void appendEnumValueIfNotDefault(
static void appendStringIfNotDefault(
json& j,
std::string_view key,
std::string_view value,
Expand All @@ -52,6 +52,14 @@ static void appendEnumValueIfNotDefault(
}
}

static void appendEnumValueIfNotDefault(
json& j,
std::string_view key,
std::string_view value,
std::string_view defaultValue) {
appendStringIfNotDefault(j, key, value, defaultValue);
}

static void appendBoolIfNotDefault(
json& j,
std::string_view key,
Expand Down Expand Up @@ -197,6 +205,11 @@ static void serializeTreeImpl(
"position-type",
YGPositionTypeToString(YGNodeStyleGetPositionType(node)),
YGPositionTypeToString(YGNodeStyleGetPositionType(defaultNode.get())));
appendStringIfNotDefault(
j["style"],
"clip-path",
YGNodeStyleGetClipPath(node),
YGNodeStyleGetClipPath(defaultNode.get()));

appendFloatIfNotDefault(
j["style"],
Expand Down
35 changes: 35 additions & 0 deletions gentest/fixtures/YGClipPathTest.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div
id="clip_path_circle_does_not_affect_layout"
style="width: 120px; height: 80px; clip-path: circle(35% at 50% 40%)">
<div style="width: 160px; height: 30px"></div>
</div>

<div
id="clip_path_polygon_does_not_affect_layout"
style="
width: 100px;
height: 100px;
clip-path: polygon(evenodd, 0 0, 100% 0, 50% 100%);
">
<div style="width: 40px; height: 125px"></div>
</div>

<div
id="clip_path_quoted_path_does_not_affect_layout"
style="
width: 140px;
height: 90px;
clip-path: path('M 0 0 H 140 V 90 H 0 Z') border-box;
">
<div style="width: 175px; height: 45px"></div>
</div>

<div
id="clip_path_data_url_with_semicolon_does_not_affect_layout"
style="
width: 110px;
height: 70px;
clip-path: url('data:image/svg+xml;utf8,%3Csvg%3E%3CclipPath%20id%3D%22clip%22/%3E%3C/svg%3E#clip');
">
<div style="width: 135px; height: 35px"></div>
</div>
51 changes: 50 additions & 1 deletion gentest/src/CssToYoga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,49 @@ import type {ParsedStyles, ValueWithUnit} from './types.ts';

const INVISIBLE_BORDER_STYLES = new Set(['none', 'initial']);

function splitDeclarations(styleAttr: string): string[] {
const declarations: string[] = [];
let declarationStart = 0;
let parenthesisDepth = 0;
let quote: "'" | '"' | null = null;
let escaped = false;

for (let i = 0; i < styleAttr.length; i++) {
const character = styleAttr[i];

if (escaped) {
escaped = false;
continue;
}

if (character === '\\') {
escaped = true;
continue;
}

if (quote != null) {
if (character === quote) {
quote = null;
}
continue;
}

if (character === "'" || character === '"') {
quote = character;
} else if (character === '(') {
parenthesisDepth++;
} else if (character === ')' && parenthesisDepth > 0) {
parenthesisDepth--;
} else if (character === ';' && parenthesisDepth === 0) {
declarations.push(styleAttr.slice(declarationStart, i));
declarationStart = i + 1;
}
}

declarations.push(styleAttr.slice(declarationStart));
return declarations;
}

/**
* Parse a raw inline style attribute string into a Map of property → value.
* Expands common shorthands used in fixtures so individual longhand
Expand All @@ -21,7 +64,7 @@ export function parseStyleAttribute(styleAttr: string): ParsedStyles {
const styles: ParsedStyles = new Map();
if (!styleAttr) return styles;

const declarations = styleAttr.split(';');
const declarations = splitDeclarations(styleAttr);
for (const decl of declarations) {
const trimmed = decl.trim();
if (!trimmed) continue;
Expand Down Expand Up @@ -148,6 +191,12 @@ export function applyStyles(
}
break;

case 'clip-path':
if (value !== 'none') {
emitter.setClipPath(nodeName, value);
}
break;

case 'display':
if (value !== 'flex') {
emitter.setDisplay(nodeName, displayValue(value));
Expand Down
7 changes: 7 additions & 0 deletions gentest/src/emitters/CppEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import Emitter from './Emitter.ts';
import type {ValueWithUnit} from '../types.ts';
import {quoteCppString} from './stringEscaping.ts';

function toValueCpp(value: string | number): string {
const n = value.toString().replace('px', '').replace('%', '');
Expand Down Expand Up @@ -229,6 +230,12 @@ export class CppEmitter extends Emitter {
);
}

setClipPath(node: string, value: string): void {
this.push(
'YGNodeStyleSetClipPath(' + node + ', ' + quoteCppString(value) + ');',
);
}

setDisplay(node: string, value: string): void {
this.push(
'YGNodeStyleSetDisplay(' + node + ', ' + toValueCpp(value) + ');',
Expand Down
1 change: 1 addition & 0 deletions gentest/src/emitters/Emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export default abstract class Emitter {
abstract setPositionType(node: string, value: string): void;
abstract setFlexWrap(node: string, value: string): void;
abstract setOverflow(node: string, value: string): void;
abstract setClipPath(node: string, value: string): void;
abstract setDisplay(node: string, value: string): void;
abstract setBoxSizing(node: string, value: string): void;
abstract setFlexGrow(node: string, value: string): void;
Expand Down
5 changes: 5 additions & 0 deletions gentest/src/emitters/JavaEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import Emitter from './Emitter.ts';
import type {ValueWithUnit} from '../types.ts';
import {quoteJavaString} from './stringEscaping.ts';

function toValueJava(value: string | number): string {
const n = value.toString().replace('px', '').replace('%', '');
Expand Down Expand Up @@ -296,6 +297,10 @@ export class JavaEmitter extends Emitter {
this.push(node + '.setOverflow(' + this.tr(value) + ');');
}

setClipPath(node: string, value: string): void {
this.push(node + '.setClipPath(' + quoteJavaString(value) + ');');
}

setDisplay(node: string, value: string): void {
this.push(node + '.setDisplay(' + this.tr(value) + ');');
}
Expand Down
5 changes: 5 additions & 0 deletions gentest/src/emitters/JavascriptEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import Emitter from './Emitter.ts';
import type {ValueWithUnit} from '../types.ts';
import {quoteJavascriptString} from './stringEscaping.ts';

function toValueJavascript(value: ValueWithUnit): string {
switch (value.type) {
Expand Down Expand Up @@ -250,6 +251,10 @@ export class JavascriptEmitter extends Emitter {
this.push(node + '.setOverflow(' + this.tr(value) + ');');
}

setClipPath(node: string, value: string): void {
this.push(node + '.setClipPath(' + quoteJavascriptString(value) + ');');
}

setDisplay(node: string, value: string): void {
this.push(node + '.setDisplay(' + this.tr(value) + ');');
}
Expand Down
73 changes: 73 additions & 0 deletions gentest/src/emitters/stringEscaping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

function quoteWithCEscapes(value: string, verticalTabEscape: string): string {
let literal = '"';

for (const character of value) {
switch (character) {
case '"':
literal += '\\"';
break;
case '\\':
literal += '\\\\';
break;
case '\b':
literal += '\\b';
break;
case '\t':
literal += '\\t';
break;
case '\n':
literal += '\\n';
break;
case '\v':
literal += verticalTabEscape;
break;
case '\f':
literal += '\\f';
break;
case '\r':
literal += '\\r';
break;
default: {
const codePoint = character.codePointAt(0);
if (codePoint != null && (codePoint < 0x20 || codePoint === 0x7f)) {
// A fixed-width octal escape cannot consume a following hex digit,
// unlike C++'s variable-width \x escape.
literal += '\\' + codePoint.toString(8).padStart(3, '0');
} else {
literal += character;
}
}
}
}

return literal + '"';
}

export function quoteCppString(value: string): string {
return quoteWithCEscapes(value, '\\v');
}

export function quoteJavaString(value: string): string {
// Java has no \v escape, but does support fixed-width octal escapes.
return quoteWithCEscapes(value, '\\013');
}

export function quoteJavascriptString(value: string): string {
const literal = JSON.stringify(value);
if (literal == null) {
throw new Error('Unable to serialize JavaScript string literal');
}

// Keep generated source valid in engines which treat these as line breaks
// inside a quoted string.
return literal.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
}
5 changes: 5 additions & 0 deletions java/com/facebook/yoga/YogaNative.kt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ public object YogaNative {

@JvmStatic public external fun jni_YGNodeStyleSetDisplayJNI(nativePointer: Long, display: Int)

@JvmStatic public external fun jni_YGNodeStyleGetClipPathJNI(nativePointer: Long): String

@JvmStatic
public external fun jni_YGNodeStyleSetClipPathJNI(nativePointer: Long, clipPath: String)

@JvmStatic public external fun jni_YGNodeStyleGetFlexJNI(nativePointer: Long): Float

@JvmStatic public external fun jni_YGNodeStyleSetFlexJNI(nativePointer: Long, flex: Float)
Expand Down
2 changes: 2 additions & 0 deletions java/com/facebook/yoga/YogaNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public abstract class YogaNode : YogaProps {

public abstract var display: YogaDisplay?

abstract override var clipPath: String

abstract override var flex: Float

abstract override var flexGrow: Float
Expand Down
6 changes: 6 additions & 0 deletions java/com/facebook/yoga/YogaNodeJNIBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ public abstract class YogaNodeJNIBase : YogaNode, Cloneable {
)
}

override var clipPath: String
get() = YogaNative.jni_YGNodeStyleGetClipPathJNI(nativePointer)
set(value) {
YogaNative.jni_YGNodeStyleSetClipPathJNI(nativePointer, value)
}

override var flex: Float
get() = YogaNative.jni_YGNodeStyleGetFlexJNI(nativePointer)
set(value) {
Expand Down
7 changes: 7 additions & 0 deletions java/com/facebook/yoga/YogaProps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ public interface YogaProps {

public var boxSizing: YogaBoxSizing

/**
* The serialized CSS `clip-path` value associated with this node.
*
* Yoga preserves this paint-only value for host renderers; it does not affect layout.
*/
public var clipPath: String

/* Read-only properties - getter only, or setter has different type/name */

public val styleDirection: YogaDirection
Expand Down
Loading