| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
| 2 | // Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | // |
| 4 | // SPDX-License-Identifier: MIT |
| 5 | |
| 6 | #ifdef DEBUG |
| 7 | |
| 8 | #include <stdarg.h> |
| 9 | |
| 10 | #include <yoga/YGEnums.h> |
| 11 | |
| 12 | #include "YGNodePrint.h" |
| 13 | #include "YGNode.h" |
| 14 | #include "Yoga-internal.h" |
| 15 | #include "Utils.h" |
| 16 | |
| 17 | QT_YOGA_NAMESPACE_BEGIN |
| 18 | |
| 19 | namespace facebook { |
| 20 | namespace yoga { |
| 21 | typedef std::string string; |
| 22 | |
| 23 | static void indent(string& base, uint32_t level) { |
| 24 | for (uint32_t i = 0; i < level; ++i) { |
| 25 | base.append(" " ); |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | static bool areFourValuesEqual(const YGStyle::Edges& four) { |
| 30 | return YGValueEqual(four[0], four[1]) && YGValueEqual(four[0], four[2]) && |
| 31 | YGValueEqual(four[0], four[3]); |
| 32 | } |
| 33 | |
| 34 | static void appendFormattedString(string& str, const char* fmt, ...) { |
| 35 | va_list args; |
| 36 | va_start(args, fmt); |
| 37 | va_list argsCopy; |
| 38 | va_copy(argsCopy, args); |
| 39 | std::vector<char> buf(1 + vsnprintf(NULL, 0, fmt, args)); |
| 40 | va_end(args); |
| 41 | vsnprintf(buf.data(), buf.size(), fmt, argsCopy); |
| 42 | va_end(argsCopy); |
| 43 | string result = string(buf.begin(), buf.end() - 1); |
| 44 | str.append(result); |
| 45 | } |
| 46 | |
| 47 | static void appendFloatOptionalIfDefined( |
| 48 | string& base, |
| 49 | const string key, |
| 50 | const YGFloatOptional num) { |
| 51 | if (!num.isUndefined()) { |
| 52 | appendFormattedString(base, "%s: %g; " , key.c_str(), num.unwrap()); |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | static void appendNumberIfNotUndefined( |
| 57 | string& base, |
| 58 | const string key, |
| 59 | const YGValue number) { |
| 60 | if (number.unit != YGUnitUndefined) { |
| 61 | if (number.unit == YGUnitAuto) { |
| 62 | base.append(key + ": auto; " ); |
| 63 | } else { |
| 64 | string unit = number.unit == YGUnitPoint ? "px" : "%%" ; |
| 65 | appendFormattedString( |
| 66 | base, "%s: %g%s; " , key.c_str(), number.value, unit.c_str()); |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | static void appendNumberIfNotAuto( |
| 72 | string& base, |
| 73 | const string& key, |
| 74 | const YGValue number) { |
| 75 | if (number.unit != YGUnitAuto) { |
| 76 | appendNumberIfNotUndefined(base, key, number); |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | static void appendNumberIfNotZero( |
| 81 | string& base, |
| 82 | const string& str, |
| 83 | const YGValue number) { |
| 84 | if (number.unit == YGUnitAuto) { |
| 85 | base.append(str + ": auto; " ); |
| 86 | } else if (!YGFloatsEqual(number.value, 0)) { |
| 87 | appendNumberIfNotUndefined(base, str, number); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | static void appendEdges( |
| 92 | string& base, |
| 93 | const string& key, |
| 94 | const YGStyle::Edges& edges) { |
| 95 | if (areFourValuesEqual(edges)) { |
| 96 | auto edgeValue = YGNode::computeEdgeValueForColumn( |
| 97 | edges, YGEdgeLeft, detail::CompactValue::ofZero()); |
| 98 | appendNumberIfNotZero(base, key, edgeValue); |
| 99 | } else { |
| 100 | for (int edge = YGEdgeLeft; edge != YGEdgeAll; ++edge) { |
| 101 | string str = key + "-" + YGEdgeToString(static_cast<YGEdge>(edge)); |
| 102 | appendNumberIfNotZero(base, str, edges[edge]); |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | static void appendEdgeIfNotUndefined( |
| 108 | string& base, |
| 109 | const string& str, |
| 110 | const YGStyle::Edges& edges, |
| 111 | const YGEdge edge) { |
| 112 | // TODO: this doesn't take RTL / YGEdgeStart / YGEdgeEnd into account |
| 113 | auto value = (edge == YGEdgeLeft || edge == YGEdgeRight) |
| 114 | ? YGNode::computeEdgeValueForRow( |
| 115 | edges, edge, edge, detail::CompactValue::ofUndefined()) |
| 116 | : YGNode::computeEdgeValueForColumn( |
| 117 | edges, edge, detail::CompactValue::ofUndefined()); |
| 118 | appendNumberIfNotUndefined(base, str, value); |
| 119 | } |
| 120 | |
| 121 | void YGNodeToString( |
| 122 | std::string& str, |
| 123 | YGNodeRef node, |
| 124 | YGPrintOptions options, |
| 125 | uint32_t level) { |
| 126 | indent(str, level); |
| 127 | appendFormattedString(str, "<div " ); |
| 128 | |
| 129 | if (options & YGPrintOptionsLayout) { |
| 130 | appendFormattedString(str, "layout=\"" ); |
| 131 | appendFormattedString( |
| 132 | str, "width: %g; " , node->getLayout().dimensions[YGDimensionWidth]); |
| 133 | appendFormattedString( |
| 134 | str, "height: %g; " , node->getLayout().dimensions[YGDimensionHeight]); |
| 135 | appendFormattedString( |
| 136 | str, "top: %g; " , node->getLayout().position[YGEdgeTop]); |
| 137 | appendFormattedString( |
| 138 | str, "left: %g;" , node->getLayout().position[YGEdgeLeft]); |
| 139 | appendFormattedString(str, "\" " ); |
| 140 | } |
| 141 | |
| 142 | if (options & YGPrintOptionsStyle) { |
| 143 | appendFormattedString(str, "style=\"" ); |
| 144 | const auto& style = node->getStyle(); |
| 145 | if (style.flexDirection() != YGNode().getStyle().flexDirection()) { |
| 146 | appendFormattedString( |
| 147 | str, |
| 148 | "flex-direction: %s; " , |
| 149 | YGFlexDirectionToString(style.flexDirection())); |
| 150 | } |
| 151 | if (style.justifyContent() != YGNode().getStyle().justifyContent()) { |
| 152 | appendFormattedString( |
| 153 | str, |
| 154 | "justify-content: %s; " , |
| 155 | YGJustifyToString(style.justifyContent())); |
| 156 | } |
| 157 | if (style.alignItems() != YGNode().getStyle().alignItems()) { |
| 158 | appendFormattedString( |
| 159 | str, "align-items: %s; " , YGAlignToString(style.alignItems())); |
| 160 | } |
| 161 | if (style.alignContent() != YGNode().getStyle().alignContent()) { |
| 162 | appendFormattedString( |
| 163 | str, "align-content: %s; " , YGAlignToString(style.alignContent())); |
| 164 | } |
| 165 | if (style.alignSelf() != YGNode().getStyle().alignSelf()) { |
| 166 | appendFormattedString( |
| 167 | str, "align-self: %s; " , YGAlignToString(style.alignSelf())); |
| 168 | } |
| 169 | appendFloatOptionalIfDefined(str, "flex-grow" , style.flexGrow()); |
| 170 | appendFloatOptionalIfDefined(str, "flex-shrink" , style.flexShrink()); |
| 171 | appendNumberIfNotAuto(str, "flex-basis" , style.flexBasis()); |
| 172 | appendFloatOptionalIfDefined(str, "flex" , style.flex()); |
| 173 | |
| 174 | if (style.flexWrap() != YGNode().getStyle().flexWrap()) { |
| 175 | appendFormattedString( |
| 176 | str, "flex-wrap: %s; " , YGWrapToString(style.flexWrap())); |
| 177 | } |
| 178 | |
| 179 | if (style.overflow() != YGNode().getStyle().overflow()) { |
| 180 | appendFormattedString( |
| 181 | str, "overflow: %s; " , YGOverflowToString(style.overflow())); |
| 182 | } |
| 183 | |
| 184 | if (style.display() != YGNode().getStyle().display()) { |
| 185 | appendFormattedString( |
| 186 | str, "display: %s; " , YGDisplayToString(style.display())); |
| 187 | } |
| 188 | appendEdges(str, "margin" , style.margin()); |
| 189 | appendEdges(str, "padding" , style.padding()); |
| 190 | appendEdges(str, "border" , style.border()); |
| 191 | |
| 192 | if (YGNode::computeColumnGap( |
| 193 | style.gap(), detail::CompactValue::ofUndefined()) != |
| 194 | YGNode::computeColumnGap( |
| 195 | YGNode().getStyle().gap(), detail::CompactValue::ofUndefined())) { |
| 196 | appendNumberIfNotUndefined( |
| 197 | str, "column-gap" , style.gap()[YGGutterColumn]); |
| 198 | } |
| 199 | if (YGNode::computeRowGap( |
| 200 | style.gap(), detail::CompactValue::ofUndefined()) != |
| 201 | YGNode::computeRowGap( |
| 202 | YGNode().getStyle().gap(), detail::CompactValue::ofUndefined())) { |
| 203 | appendNumberIfNotUndefined(str, "row-gap" , style.gap()[YGGutterRow]); |
| 204 | } |
| 205 | |
| 206 | appendNumberIfNotAuto(str, "width" , style.dimensions()[YGDimensionWidth]); |
| 207 | appendNumberIfNotAuto(str, "height" , style.dimensions()[YGDimensionHeight]); |
| 208 | appendNumberIfNotAuto( |
| 209 | str, "max-width" , style.maxDimensions()[YGDimensionWidth]); |
| 210 | appendNumberIfNotAuto( |
| 211 | str, "max-height" , style.maxDimensions()[YGDimensionHeight]); |
| 212 | appendNumberIfNotAuto( |
| 213 | str, "min-width" , style.minDimensions()[YGDimensionWidth]); |
| 214 | appendNumberIfNotAuto( |
| 215 | str, "min-height" , style.minDimensions()[YGDimensionHeight]); |
| 216 | |
| 217 | if (style.positionType() != YGNode().getStyle().positionType()) { |
| 218 | appendFormattedString( |
| 219 | str, "position: %s; " , YGPositionTypeToString(style.positionType())); |
| 220 | } |
| 221 | |
| 222 | appendEdgeIfNotUndefined(str, "left" , style.position(), YGEdgeLeft); |
| 223 | appendEdgeIfNotUndefined(str, "right" , style.position(), YGEdgeRight); |
| 224 | appendEdgeIfNotUndefined(str, "top" , style.position(), YGEdgeTop); |
| 225 | appendEdgeIfNotUndefined(str, "bottom" , style.position(), YGEdgeBottom); |
| 226 | appendFormattedString(str, "\" " ); |
| 227 | |
| 228 | if (node->hasMeasureFunc()) { |
| 229 | appendFormattedString(str, "has-custom-measure=\"true\"" ); |
| 230 | } |
| 231 | } |
| 232 | appendFormattedString(str, ">" ); |
| 233 | |
| 234 | const uint32_t childCount = static_cast<uint32_t>(node->getChildren().size()); |
| 235 | if (options & YGPrintOptionsChildren && childCount > 0) { |
| 236 | for (uint32_t i = 0; i < childCount; i++) { |
| 237 | appendFormattedString(str, "\n" ); |
| 238 | YGNodeToString(str, YGNodeGetChild(node, i), options, level + 1); |
| 239 | } |
| 240 | appendFormattedString(str, "\n" ); |
| 241 | indent(str, level); |
| 242 | } |
| 243 | appendFormattedString(str, "</div>" ); |
| 244 | } |
| 245 | } // namespace yoga |
| 246 | } // namespace facebook |
| 247 | |
| 248 | QT_YOGA_NAMESPACE_END |
| 249 | |
| 250 | #endif |
| 251 | |