CanvasFormattedText Styling Prototype

This change resurrects @sushraja's changes to add styling abilities to
CanvasFormattedText.  Note that this implements an older version of the
API, which is changing rapidly.

Original description:

Add support for styling CanvasFormattedText

This change adds a styleMap on the CanvasFormattedText and
CanvasFormattedTextRun. A subset of CSS properties are supported on the
CanvasFormattedText and a smaller subset on the CanvasFormattedTextRun.

A height parameter is added to fillFormattedText so that vertical
writing modes can be supported. The height/width passed to
fillFormattedText are used as the available space, while the
CSS height/width can be set on the CanvasFormattedText to control
height/width explicitly.

Change-Id: I2af3e2bc667f4d6e57da390fdf0648663a75461e
Bug: 1155764
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3491151
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Reviewed-by: Ian Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: Fernando Serboncini <fserb@chromium.org>
Commit-Queue: Ian Prest <iapres@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#987620}
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 4520eb7..514ed887 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -91,6 +91,7 @@
           "//third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_dictionary.idl",
           "//third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.idl",
           "//third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.idl",
+          "//third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.idl",
           "//third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.idl",
           "//third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.idl",
           "//third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.idl",
diff --git a/third_party/blink/renderer/build/scripts/core/css/css_properties.py b/third_party/blink/renderer/build/scripts/core/css/css_properties.py
index ca94084..afdf0e5 100755
--- a/third_party/blink/renderer/build/scripts/core/css/css_properties.py
+++ b/third_party/blink/renderer/build/scripts/core/css/css_properties.py
@@ -57,6 +57,10 @@
                 ][0]
                 assert subprop['supports_incremental_style'], \
                     '%s must be incrementally applicable when its shorthand %s is' % (subprop_name, name)
+    assert not prop['valid_for_canvas_formatted_text'] or prop['is_longhand'], \
+        'Only longhands can be valid_for_canvas_formatted_text [%s]' % name
+    assert not prop['valid_for_canvas_formatted_text_run'] or prop['is_longhand'], \
+        'Only longhands can be valid_for_canvas_formatted_text_run [%s]' % name
 
 
 def validate_alias(alias):
diff --git a/third_party/blink/renderer/build/scripts/core/css/properties/templates/css_properties.h.tmpl b/third_party/blink/renderer/build/scripts/core/css/properties/templates/css_properties.h.tmpl
index 982e6257..15033a9 100644
--- a/third_party/blink/renderer/build/scripts/core/css/properties/templates/css_properties.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/css/properties/templates/css_properties.h.tmpl
@@ -47,6 +47,8 @@
   (property.valid_for_first_line and 'kValidForFirstLine' or ''),
   (property.valid_for_cue and 'kValidForCue' or ''),
   (property.valid_for_marker and 'kValidForMarker' or ''),
+  (property.valid_for_canvas_formatted_text and 'kValidForCanvasFormattedText' or ''),
+  (property.valid_for_canvas_formatted_text_run and 'kValidForCanvasFormattedTextRun' or ''),
   (is_surrogate and 'kSurrogate' or ''),
   (property.font and 'kAffectsFont' or ''),
   (property.is_background and 'kBackground' or ''),
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 05604b8..5709944 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -582,7 +582,27 @@
     idempotent: {
       default: true,
       valid_type: "bool",
-    }
+    },
+
+    // - valid_for_canvas_formatted_text: true
+    //
+    // Whether the property can be used to style the top-level container
+    // (similar to display:block, but supporting fewer properties), in the
+    // experimental API to bring multiline text rendering to HTML canvas.
+    // https://github.com/WICG/canvas-formatted-text/
+    valid_for_canvas_formatted_text: {
+      default: false,
+      valid_type: "bool",
+    },
+
+    // - valid_for_canvas_formatted_text_run: true
+    //
+    // Whether the property can be used to style individual text runs, in the
+    // experimental API to bring multiline text rendering to HTML canvas.
+    valid_for_canvas_formatted_text_run: {
+      default: false,
+      valid_type: "bool",
+    },
   },
 
   // Members in the data objects should appear in the same order as in the
@@ -817,6 +837,8 @@
       valid_for_cue: true,
       valid_for_marker: true,
       valid_for_highlight: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
       is_highlight_colors: true,
     },
     {
@@ -833,6 +855,8 @@
       style_builder_custom_functions: ["value"],
       priority: "High",
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-family",
@@ -850,6 +874,8 @@
       valid_for_cue: true,
       valid_for_marker: true,
       tree_scoped_value: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-kerning",
@@ -864,6 +890,8 @@
       valid_for_first_letter: true,
       valid_for_first_line: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-optical-sizing",
@@ -910,6 +938,8 @@
       valid_for_first_line: true,
       valid_for_cue: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-size-adjust",
@@ -946,6 +976,8 @@
       valid_for_first_line: true,
       valid_for_cue: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-style",
@@ -962,6 +994,8 @@
       valid_for_first_line: true,
       valid_for_cue: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-variant-ligatures",
@@ -982,6 +1016,8 @@
       valid_for_first_letter: true,
       valid_for_first_line: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-variant-caps",
@@ -999,6 +1035,8 @@
       valid_for_first_letter: true,
       valid_for_first_line: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-variant-east-asian",
@@ -1016,6 +1054,8 @@
       valid_for_first_letter: true,
       valid_for_first_line: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-variant-numeric",
@@ -1034,6 +1074,8 @@
       valid_for_first_letter: true,
       valid_for_first_line: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-weight",
@@ -1051,6 +1093,8 @@
       valid_for_first_line: true,
       valid_for_cue: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-synthesis-weight",
@@ -1112,6 +1156,8 @@
       valid_for_first_line: true,
       valid_for_marker: true,
       computable: false,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "font-variation-settings",
@@ -1129,6 +1175,8 @@
       valid_for_cue: true,
       valid_for_marker: true,
       computable: false,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "-webkit-font-smoothing",
@@ -1187,6 +1235,7 @@
       style_builder_custom_functions: ["initial", "inherit", "value"],
       priority: "High",
       computable: false,
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "-webkit-text-orientation",
@@ -1209,6 +1258,7 @@
       type_name: "WritingMode",
       style_builder_custom_functions: ["initial", "inherit", "value"],
       priority: "High",
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "-webkit-writing-mode",
@@ -2681,6 +2731,7 @@
         resolver: "vertical",
       },
       supports_incremental_style: true,
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "hyphens",
@@ -4281,6 +4332,7 @@
       default_value: "start",
       getter: "GetTextAlign",
       style_builder_custom_functions: ["value"],
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "text-align-last",
@@ -4314,6 +4366,7 @@
       name_for_methods: "TextCombine",
       valid_for_marker: true,
       computable: false,
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "text-decoration-color",
@@ -4334,6 +4387,8 @@
       valid_for_cue: true,
       valid_for_highlight: true,
       supports_incremental_style: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-decoration-line",
@@ -4349,6 +4404,8 @@
       valid_for_first_line: true,
       valid_for_cue: true,
       valid_for_highlight: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-decoration-skip-ink",
@@ -4364,6 +4421,8 @@
       valid_for_cue: true,
       valid_for_marker: true,
       valid_for_highlight: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-decoration-style",
@@ -4377,6 +4436,8 @@
       valid_for_first_line: true,
       valid_for_cue: true,
       valid_for_highlight: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-decoration-thickness",
@@ -4395,6 +4456,8 @@
       valid_for_first_line: true,
       valid_for_highlight: true,
       computable: false,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-indent",
@@ -4422,6 +4485,7 @@
       typedom_types: ["Keyword"],
       valid_for_first_letter: true,
       valid_for_first_line: true,
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "text-overflow",
@@ -4431,6 +4495,7 @@
       keywords: ["clip", "ellipsis"],
       typedom_types: ["Keyword"],
       default_value: "clip",
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "text-shadow",
@@ -4451,6 +4516,8 @@
       valid_for_cue: true,
       valid_for_marker: true,
       valid_for_highlight: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-size-adjust",
@@ -4479,6 +4546,8 @@
       valid_for_first_letter: true,
       valid_for_first_line: true,
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-underline-offset",
@@ -4495,6 +4564,8 @@
       valid_for_first_letter: true,
       valid_for_first_line: true,
       computable: false,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "text-underline-position",
@@ -4511,6 +4582,8 @@
       typedom_types: ["Keyword"],
       valid_for_first_letter: true,
       valid_for_first_line: true,
+      valid_for_canvas_formatted_text: true,
+      valid_for_canvas_formatted_text_run: true,
     },
     {
       name: "top",
@@ -4979,6 +5052,7 @@
       keywords: ["auto", "loose", "normal", "strict", "after-white-space", "anywhere"],
       default_value: "auto",
       type_name: "LineBreak",
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "line-break",
@@ -4988,6 +5062,7 @@
       keywords: ["auto", "loose", "normal", "strict", "anywhere"],
       typedom_types: ["Keyword"],
       valid_for_marker: true,
+      valid_for_canvas_formatted_text: true,
     },
     // An Apple extension.
     {
@@ -5370,6 +5445,7 @@
         resolver: "horizontal",
       },
       supports_incremental_style: true,
+      valid_for_canvas_formatted_text: true,
     },
     {
       name: "will-change",
diff --git a/third_party/blink/renderer/core/css/properties/css_property.h b/third_party/blink/renderer/core/css/properties/css_property.h
index 660bd3e..656266e4 100644
--- a/third_party/blink/renderer/core/css/properties/css_property.h
+++ b/third_party/blink/renderer/core/css/properties/css_property.h
@@ -64,6 +64,12 @@
   bool IsValidForCue() const { return flags_ & kValidForCue; }
   bool IsValidForMarker() const { return flags_ & kValidForMarker; }
   bool IsValidForHighlight() const { return flags_ & kValidForHighlight; }
+  bool IsValidForCanvasFormattedText() const {
+    return flags_ & kValidForCanvasFormattedText;
+  }
+  bool IsValidForCanvasFormattedTextRun() const {
+    return flags_ & kValidForCanvasFormattedTextRun;
+  }
   bool IsSurrogate() const { return flags_ & kSurrogate; }
   bool AffectsFont() const { return flags_ & kAffectsFont; }
   bool IsBackground() const { return flags_ & kBackground; }
@@ -170,6 +176,11 @@
     kSupportsIncrementalStyle = 1 << 23,
     // See idempotent in css_properties.json5.
     kIdempotent = 1 << 24,
+    // Set if the css property can apply to the experiemental canvas
+    // formatted text API to render multiline text in canvas.
+    // https://github.com/WICG/canvas-formatted-text
+    kValidForCanvasFormattedText = 1 << 25,
+    kValidForCanvasFormattedTextRun = 1 << 26,
   };
 
   constexpr CSSProperty(CSSPropertyID property_id,
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 1d9d8eb6..97aeb07 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -2070,6 +2070,7 @@
   visitor->Trace(selector_filter_);
   visitor->Trace(document_);
   visitor->Trace(tracker_);
+  visitor->Trace(canvas_formatted_text_element_);
 }
 
 bool StyleResolver::IsForcedColorsModeEnabled() const {
@@ -2357,4 +2358,68 @@
 #undef PROPAGATE_VALUE
 #undef PROPAGATE_FROM
 
+scoped_refptr<const ComputedStyle> StyleResolver::StyleForCanvasFormattedText(
+    bool is_text_run,
+    const FontDescription& default_font,
+    const CSSPropertyValueSet* css_property_value_set) {
+  return StyleForCanvasFormattedText(is_text_run, &default_font,
+                                     /*parent_style*/ nullptr,
+                                     css_property_value_set);
+}
+
+scoped_refptr<const ComputedStyle> StyleResolver::StyleForCanvasFormattedText(
+    bool is_text_run,
+    const ComputedStyle& parent_style,
+    const CSSPropertyValueSet* css_property_value_set) {
+  return StyleForCanvasFormattedText(is_text_run, /*default_font*/ nullptr,
+                                     &parent_style, css_property_value_set);
+}
+
+scoped_refptr<const ComputedStyle> StyleResolver::StyleForCanvasFormattedText(
+    bool is_text_run,
+    const FontDescription* default_font,
+    const ComputedStyle* parent_style,
+    const CSSPropertyValueSet* css_property_value_set) {
+  DCHECK_NE(!!parent_style, !!default_font)
+      << "only one of `default_font` or `parent_style` should be specified";
+
+  // Set up our initial style properties based on either the `default_font` or
+  // `parent_style`.
+  scoped_refptr<ComputedStyle> style = CreateComputedStyle();
+  if (default_font)
+    style->SetFontDescription(*default_font);
+  else  // parent_style
+    style->InheritFrom(*parent_style);
+  style->SetDisplay(is_text_run ? EDisplay::kInline : EDisplay::kBlock);
+
+  // Apply any properties in the `css_property_value_set`.
+  if (css_property_value_set) {
+    // Use a dummy/disconnected element when resolving the styles so that we
+    // don't inherit anything from existing elements.
+    StyleResolverState state(
+        GetDocument(), EnsureElementForCanvasFormattedText(),
+        StyleRecalcContext{},
+        StyleRequest{parent_style ? parent_style : &InitialStyle()});
+    state.SetStyle(style);
+
+    // Use StyleCascade to apply inheritance in the correct order.
+    STACK_UNINITIALIZED StyleCascade cascade(state);
+    cascade.MutableMatchResult().AddMatchedProperties(
+        css_property_value_set,
+        AddMatchedPropertiesOptions::Builder().SetIsInlineStyle(true).Build());
+    cascade.Apply();
+
+    StyleAdjuster::AdjustComputedStyle(state, nullptr);
+  }
+
+  return style;
+}
+
+Element& StyleResolver::EnsureElementForCanvasFormattedText() {
+  if (!canvas_formatted_text_element_)
+    canvas_formatted_text_element_ =
+        MakeGarbageCollected<Element>(html_names::kSpanTag, &GetDocument());
+  return *canvas_formatted_text_element_;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.h b/third_party/blink/renderer/core/css/resolver/style_resolver.h
index 8a89bab..8e0670c 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.h
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.h
@@ -96,6 +96,14 @@
       const AtomicString& page_name);
   scoped_refptr<const ComputedStyle> StyleForText(Text*);
   scoped_refptr<ComputedStyle> StyleForViewport();
+  scoped_refptr<const ComputedStyle> StyleForCanvasFormattedText(
+      bool is_text_run,
+      const ComputedStyle& parent_style,
+      const CSSPropertyValueSet* css_property_value_set);
+  scoped_refptr<const ComputedStyle> StyleForCanvasFormattedText(
+      bool is_text_run,
+      const FontDescription& default_font,
+      const CSSPropertyValueSet* css_property_value_set);
 
   // Propagate computed values from the root or body element to the viewport
   // when specified to do so.
@@ -306,12 +314,23 @@
 
   Member<StyleRuleUsageTracker> tracker_;
 
+  // This is a dummy/disconnected element that we use for CanvasFormattedText
+  // style computations; see `EnsureElementForCanvasFormattedText`.
+  Member<Element> canvas_formatted_text_element_;
+
   bool print_media_type_ = false;
   bool was_viewport_resized_ = false;
 
   FRIEND_TEST_ALL_PREFIXES(ComputedStyleTest, ApplyInternalLightDarkColor);
   friend class StyleResolverTest;
   FRIEND_TEST_ALL_PREFIXES(StyleResolverTest, TreeScopedReferences);
+
+  Element& EnsureElementForCanvasFormattedText();
+  scoped_refptr<const ComputedStyle> StyleForCanvasFormattedText(
+      bool is_text_run,
+      const FontDescription* default_font,
+      const ComputedStyle* parent_style,
+      const CSSPropertyValueSet* css_property_value_set);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_property_serializer.h b/third_party/blink/renderer/core/css/style_property_serializer.h
index f494e6e..36c6f67 100644
--- a/third_party/blink/renderer/core/css/style_property_serializer.h
+++ b/third_party/blink/renderer/core/css/style_property_serializer.h
@@ -34,7 +34,7 @@
 class CSSPropertyValueSet;
 class StylePropertyShorthand;
 
-class StylePropertySerializer {
+class CORE_EXPORT StylePropertySerializer {
   STACK_ALLOCATED();
 
  public:
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index 7093dbc..8955a54a 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -212,7 +212,7 @@
   return text;
 }
 
-LayoutText* LayoutText::CreateAnonymous(
+LayoutText* LayoutText::CreateAnonymousForFormattedText(
     Document& doc,
     scoped_refptr<const ComputedStyle> style,
     scoped_refptr<StringImpl> text,
@@ -220,7 +220,7 @@
   LayoutText* layout_text =
       LayoutObjectFactory::CreateText(nullptr, std::move(text), legacy);
   layout_text->SetDocumentForAnonymous(&doc);
-  layout_text->SetStyle(std::move(style));
+  layout_text->SetStyleInternal(std::move(style));
   return layout_text;
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_text.h b/third_party/blink/renderer/core/layout/layout_text.h
index 7a3fca2..0f4ee0a 100644
--- a/third_party/blink/renderer/core/layout/layout_text.h
+++ b/third_party/blink/renderer/core/layout/layout_text.h
@@ -90,10 +90,11 @@
                                           scoped_refptr<const ComputedStyle>,
                                           LegacyLayout);
 
-  static LayoutText* CreateAnonymous(Document&,
-                                     scoped_refptr<const ComputedStyle>,
-                                     scoped_refptr<StringImpl>,
-                                     LegacyLayout legacy);
+  static LayoutText* CreateAnonymousForFormattedText(
+      Document&,
+      scoped_refptr<const ComputedStyle>,
+      scoped_refptr<StringImpl>,
+      LegacyLayout legacy);
 
   const char* GetName() const override {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/modules/canvas/BUILD.gn b/third_party/blink/renderer/modules/canvas/BUILD.gn
index ca6c394c..d66aaafe 100644
--- a/third_party/blink/renderer/modules/canvas/BUILD.gn
+++ b/third_party/blink/renderer/modules/canvas/BUILD.gn
@@ -17,6 +17,8 @@
     "canvas2d/canvas_formatted_text.h",
     "canvas2d/canvas_formatted_text_run.cc",
     "canvas2d/canvas_formatted_text_run.h",
+    "canvas2d/canvas_formatted_text_style.cc",
+    "canvas2d/canvas_formatted_text_style.h",
     "canvas2d/canvas_gradient.cc",
     "canvas2d/canvas_gradient.h",
     "canvas2d/canvas_image_source_util.cc",
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc
index b4469b5..bb48b39 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.cc
@@ -24,6 +24,7 @@
   visitor->Trace(text_runs_);
   visitor->Trace(block_);
   ScriptWrappable::Trace(visitor);
+  CanvasFormattedTextStyle::Trace(visitor);
 }
 
 CanvasFormattedText* CanvasFormattedText::Create(
@@ -38,7 +39,8 @@
   return canvas_formatted_text;
 }
 
-CanvasFormattedText::CanvasFormattedText(ExecutionContext* execution_context) {
+CanvasFormattedText::CanvasFormattedText(ExecutionContext* execution_context)
+    : CanvasFormattedTextStyle(/* is_text_run */ false) {
   // Refrain from extending the use of document, apart from creating layout
   // block flow. In the future we should handle execution_context's from worker
   // threads that do not have a document.
@@ -63,15 +65,23 @@
     block_->Destroy();
 }
 
-LayoutBlockFlow* CanvasFormattedText::GetLayoutBlock(
+void CanvasFormattedText::SetNeedsStyleRecalc() {
+  needs_style_recalc_ = true;
+}
+
+void CanvasFormattedText::UpdateComputedStylesIfNeeded(
     Document& document,
     const FontDescription& defaultFont) {
-  scoped_refptr<ComputedStyle> style =
-      document.GetStyleResolver().CreateComputedStyle();
-  style->SetDisplay(EDisplay::kBlock);
-  style->SetFontDescription(defaultFont);
-  block_->SetStyle(style);
-  return block_;
+  if (needs_style_recalc_ || current_default_font_ != defaultFont) {
+    auto style = document.GetStyleResolver().StyleForCanvasFormattedText(
+        /*is_text_run*/ false, defaultFont, GetCssPropertySet());
+    block_->SetStyle(style, LayoutObject::ApplyStyleChanges::kNo);
+    block_->SetHorizontalWritingMode(style->IsHorizontalWritingMode());
+    for (auto& text_run : text_runs_)
+      text_run->UpdateStyle(document, /*parent_style*/ *style);
+    needs_style_recalc_ = false;
+    current_default_font_ = defaultFont;
+  }
 }
 
 CanvasFormattedTextRun* CanvasFormattedText::appendRun(
@@ -81,6 +91,8 @@
     return nullptr;
   text_runs_.push_back(run);
   block_->AddChild(run->GetLayoutObject());
+  run->SetParent(this);
+  SetNeedsStyleRecalc();
   return run;
 }
 
@@ -91,10 +103,13 @@
   if (!CheckRunsIndexBound(index, &exception_state) ||
       !CheckRunIsNotParented(run, &exception_state))
     return nullptr;
+  run->SetParent(this);
   block_->AddChild(run->GetLayoutObject(),
                    text_runs_[index]->GetLayoutObject());
+  text_runs_[index]->SetParent(nullptr);
   block_->RemoveChild(text_runs_[index]->GetLayoutObject());
   text_runs_[index] = run;
+  SetNeedsStyleRecalc();
   return text_runs_[index];
 }
 
@@ -111,6 +126,8 @@
   block_->AddChild(run->GetLayoutObject(),
                    text_runs_[index]->GetLayoutObject());
   text_runs_.insert(index, run);
+  run->SetParent(this);
+  SetNeedsStyleRecalc();
   return text_runs_[index];
 }
 
@@ -130,6 +147,7 @@
   }
 
   for (wtf_size_t i = index; i < index + length; i++) {
+    text_runs_[i]->SetParent(nullptr);
     block_->RemoveChild(text_runs_[i]->GetLayoutObject());
   }
   block_->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
@@ -144,26 +162,26 @@
     double x,
     double y,
     double wrap_width,
+    double wrap_height,
     gfx::RectF& bounds) {
-  LayoutBlockFlow* block = GetLayoutBlock(document, font);
-  NGBlockNode block_node(block);
-  NGInlineNode node(block);
+  UpdateComputedStylesIfNeeded(document, font);
+  NGBlockNode block_node(block_);
 
-  // TODO(sushraja) Once we add support for writing mode on the canvas formatted
-  // text, fix this to be not hardcoded horizontal top to bottom.
   NGConstraintSpaceBuilder builder(
       WritingMode::kHorizontalTb,
-      {WritingMode::kHorizontalTb, TextDirection::kLtr},
+      {block_->StyleRef().GetWritingMode(), block_->StyleRef().Direction()},
       /* is_new_fc */ true);
-  LayoutUnit available_logical_width(wrap_width);
-  LogicalSize available_size = {available_logical_width, kIndefiniteSize};
+  LayoutUnit available_logical_width(std::max(wrap_width, 0.0));
+  LayoutUnit available_logical_height(std::max(wrap_height, 0.0));
+  LogicalSize available_size = {available_logical_width,
+                                available_logical_height};
   builder.SetAvailableSize(available_size);
   NGConstraintSpace space = builder.ToConstraintSpace();
   const NGLayoutResult* block_results = block_node.Layout(space, nullptr);
   const auto& fragment =
       To<NGPhysicalBoxFragment>(block_results->PhysicalFragment());
-  block->RecalcFragmentsVisualOverflow();
-  bounds = gfx::RectF(block->PhysicalVisualOverflowRect());
+  block_->RecalcFragmentsVisualOverflow();
+  bounds = gfx::RectF{block_->PhysicalVisualOverflowRect()};
   auto* paint_record_builder = MakeGarbageCollected<PaintRecordBuilder>();
   PaintInfo paint_info(paint_record_builder->Context(), CullRect::Infinite(),
                        PaintPhase::kForeground);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h
index 41acc43..6ca067d 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h
@@ -25,7 +25,9 @@
 class FontDescription;
 class LayoutBlockFlow;
 
-class MODULES_EXPORT CanvasFormattedText final : public ScriptWrappable {
+class MODULES_EXPORT CanvasFormattedText final
+    : public ScriptWrappable,
+      public CanvasFormattedTextStyle {
   DEFINE_WRAPPERTYPEINFO();
   USING_PRE_FINALIZER(CanvasFormattedText, Dispose);
 
@@ -108,21 +110,27 @@
                  unsigned length,
                  ExceptionState& exception_state);
 
-  LayoutBlockFlow* GetLayoutBlock(Document& document,
-                                  const FontDescription& defaultFont);
-
   sk_sp<PaintRecord> PaintFormattedText(Document& document,
                                         const FontDescription& font,
                                         double x,
                                         double y,
                                         double wrap_width,
+                                        double wrap_height,
                                         gfx::RectF& bounds);
 
   void Dispose();
 
+  void SetNeedsStyleRecalc() override;
+
+ private:
+  void UpdateComputedStylesIfNeeded(Document& document,
+                                    const FontDescription& defaultFont);
+
  private:
   HeapVector<Member<CanvasFormattedTextRun>> text_runs_;
   Member<LayoutBlockFlow> block_;
+  FontDescription current_default_font_;
+  bool needs_style_recalc_ = true;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.idl b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.idl
index 0e3b846..a53d44d3 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.idl
@@ -21,4 +21,6 @@
   [RaisesException] void deleteRun(unsigned long index, unsigned long length); 
 
   readonly attribute unsigned long length;
-};
\ No newline at end of file
+};
+
+CanvasFormattedText includes CanvasFormattedTextStyle;
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.cc
index c3b8bc1..8cd79b4 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.cc
@@ -3,15 +3,15 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.h"
-
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
+#include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text.h"
 
 namespace blink {
 
 CanvasFormattedTextRun::CanvasFormattedTextRun(
     ExecutionContext* execution_context,
     const String text)
-    : text_(text) {
+    : CanvasFormattedTextStyle(/* is_text_run */ true), text_(text) {
   // Refrain from extending the use of document, apart from creating layout
   // text. In the future we should handle execution_context's from worker
   // threads that do not have a document.
@@ -19,11 +19,18 @@
   scoped_refptr<ComputedStyle> style =
       document->GetStyleResolver().CreateComputedStyle();
   style->SetDisplay(EDisplay::kInline);
-  layout_text_ = LayoutText::CreateAnonymous(*document, std::move(style),
-                                             text.Impl(), LegacyLayout::kAuto);
+  layout_text_ = LayoutText::CreateAnonymousForFormattedText(
+      *document, std::move(style), text.Impl(), LegacyLayout::kAuto);
   layout_text_->SetIsLayoutNGObjectForCanvasFormattedText(true);
 }
 
+void CanvasFormattedTextRun::UpdateStyle(Document& document,
+                                         const ComputedStyle& parent_style) {
+  auto style = document.GetStyleResolver().StyleForCanvasFormattedText(
+      /*is_text_run*/ true, parent_style, GetCssPropertySet());
+  layout_text_->SetStyle(style, LayoutObject::ApplyStyleChanges::kNo);
+}
+
 void CanvasFormattedTextRun::Dispose() {
   AllowDestroyingLayoutObjectInFinalizerScope scope;
   if (layout_text_)
@@ -32,7 +39,14 @@
 
 void CanvasFormattedTextRun::Trace(Visitor* visitor) const {
   visitor->Trace(layout_text_);
+  visitor->Trace(parent_);
   ScriptWrappable::Trace(visitor);
+  CanvasFormattedTextStyle::Trace(visitor);
+}
+
+void CanvasFormattedTextRun::SetNeedsStyleRecalc() {
+  if (parent_ != nullptr)
+    parent_->SetNeedsStyleRecalc();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.h
index ea294f87..20abb958 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/layout/layout_text.h"
+#include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/prefinalizer.h"
@@ -14,7 +15,11 @@
 
 namespace blink {
 
-class MODULES_EXPORT CanvasFormattedTextRun final : public ScriptWrappable {
+class CanvasFormattedText;
+
+class MODULES_EXPORT CanvasFormattedTextRun final
+    : public ScriptWrappable,
+      public CanvasFormattedTextStyle {
   DEFINE_WRAPPERTYPEINFO();
   USING_PRE_FINALIZER(CanvasFormattedTextRun, Dispose);
 
@@ -35,14 +40,25 @@
   unsigned length() const { return text_.length(); }
 
   LayoutText* GetLayoutObject() { return layout_text_; }
+  void UpdateStyle(Document& document, const ComputedStyle& parent_style);
+
+  void SetParent(CanvasFormattedText* canvas_formatted_text) {
+    parent_ = canvas_formatted_text;
+  }
 
   void Trace(Visitor* visitor) const override;
 
   void Dispose();
 
+  // Style dirtiness is tracked only at the level of a canvas formatted text
+  // and all run styles are recomputed when a canvas formatted text has its
+  // style recomputed. This can be improved by adding additional granularity
+  // of dirtiness tracking.
+  void SetNeedsStyleRecalc() override;
+
  private:
   String text_;
-
+  WeakMember<CanvasFormattedText> parent_;
   Member<LayoutText> layout_text_;
 };
 
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.idl b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.idl
index 7e1ea4f..d8d1932 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_run.idl
@@ -9,4 +9,6 @@
   [CallWith = ExecutionContext] constructor(DOMString text);
 
   attribute DOMString text;
-};
\ No newline at end of file
+};
+
+CanvasFormattedTextRun includes CanvasFormattedTextStyle;
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.cc
new file mode 100644
index 0000000..5c982100
--- /dev/null
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.cc
@@ -0,0 +1,125 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.h"
+#include "third_party/blink/renderer/core/css/css_font_selector.h"
+#include "third_party/blink/renderer/core/css/css_identifier_value.h"
+#include "third_party/blink/renderer/core/css/css_value_list.h"
+#include "third_party/blink/renderer/core/css/properties/longhand.h"
+#include "third_party/blink/renderer/core/css/resolver/font_style_resolver.h"
+#include "third_party/blink/renderer/core/css/resolver/style_adjuster.h"
+#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
+#include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
+#include "third_party/blink/renderer/core/css/style_engine.h"
+#include "third_party/blink/renderer/core/css/style_property_serializer.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
+
+namespace blink {
+
+StylePropertyMap* CanvasFormattedTextStyle::styleMap() {
+  if (!style_map_) {
+    style_map_ = MakeGarbageCollected<CanvasFormattedTextStylePropertyMap>(
+        is_text_run_, this);
+  }
+  return style_map_.Get();
+}
+
+const CSSPropertyValueSet* CanvasFormattedTextStyle::GetCssPropertySet() const {
+  return style_map_ ? style_map_->GetCssPropertySet() : nullptr;
+}
+
+void CanvasFormattedTextStyle::Trace(Visitor* visitor) const {
+  visitor->Trace(style_map_);
+}
+
+CanvasFormattedTextStylePropertyMap::CanvasFormattedTextStylePropertyMap(
+    bool is_text_run,
+    CanvasFormattedTextStyle* canvas_formatted_text_style)
+    : is_text_run_(is_text_run) {
+  css_property_value_set_ =
+      MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLStandardMode);
+  canvas_formatted_text_style_ = canvas_formatted_text_style;
+}
+
+const CSSValue* CanvasFormattedTextStylePropertyMap::GetProperty(
+    CSSPropertyID property_id) const {
+  return css_property_value_set_->GetPropertyCSSValue(property_id);
+}
+
+const CSSValue* CanvasFormattedTextStylePropertyMap::GetCustomProperty(
+    const AtomicString& property_name) const {
+  // Custom properties or CSS variables are not supported for
+  // CanvasFormattedText at this point.
+  NOTREACHED();
+  return nullptr;
+}
+
+void CanvasFormattedTextStylePropertyMap::ForEachProperty(
+    const IterationCallback& callback) {
+  for (unsigned i = 0; i < css_property_value_set_->PropertyCount(); i++) {
+    const auto& property_reference = css_property_value_set_->PropertyAt(i);
+    callback(property_reference.Name(), property_reference.Value());
+  }
+}
+
+void CanvasFormattedTextStylePropertyMap::SetProperty(
+    CSSPropertyID unresolved_property,
+    const CSSValue& value) {
+  const CSSProperty& prop = CSSProperty::Get(unresolved_property);
+  if ((prop.IsValidForCanvasFormattedText() && !is_text_run_) ||
+      (prop.IsValidForCanvasFormattedTextRun() && is_text_run_)) {
+    css_property_value_set_->SetProperty(unresolved_property, value);
+    if (canvas_formatted_text_style_)
+      canvas_formatted_text_style_->SetNeedsStyleRecalc();
+  }
+}
+
+bool CanvasFormattedTextStylePropertyMap::SetShorthandProperty(
+    CSSPropertyID unresolved_property,
+    const String& string,
+    SecureContextMode secure_context) {
+  MutableCSSPropertyValueSet::SetResult result =
+      css_property_value_set_->SetProperty(unresolved_property, string, false,
+                                           secure_context);
+  if (canvas_formatted_text_style_ &&
+      result != MutableCSSPropertyValueSet::kParseError)
+    canvas_formatted_text_style_->SetNeedsStyleRecalc();
+  return result != MutableCSSPropertyValueSet::kParseError;
+}
+
+void CanvasFormattedTextStylePropertyMap::SetCustomProperty(const AtomicString&,
+                                                            const CSSValue&) {
+  // Custom properties are not supported on CanvasFormattedText
+  NOTREACHED();
+}
+
+void CanvasFormattedTextStylePropertyMap::RemoveProperty(
+    CSSPropertyID property_id) {
+  bool did_change = css_property_value_set_->RemoveProperty(property_id);
+  if (did_change) {
+    canvas_formatted_text_style_->SetNeedsStyleRecalc();
+  }
+}
+
+void CanvasFormattedTextStylePropertyMap::RemoveCustomProperty(
+    const AtomicString&) {
+  // Custom properties are not supported on CanvasFormattedText
+  NOTREACHED();
+}
+
+void CanvasFormattedTextStylePropertyMap::RemoveAllProperties() {
+  css_property_value_set_->Clear();
+  canvas_formatted_text_style_->SetNeedsStyleRecalc();
+}
+
+String CanvasFormattedTextStylePropertyMap::SerializationForShorthand(
+    const CSSProperty& property) const {
+  DCHECK(property.IsShorthand());
+  return StylePropertySerializer(*css_property_value_set_)
+      .SerializeShorthand(property.PropertyID());
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.h
new file mode 100644
index 0000000..87df308
--- /dev/null
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.h
@@ -0,0 +1,80 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CANVAS_CANVAS2D_CANVAS_FORMATTED_TEXT_STYLE_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_CANVAS_CANVAS2D_CANVAS_FORMATTED_TEXT_STYLE_H_
+
+#include "third_party/blink/renderer/core/css/css_property_value_set.h"
+#include "third_party/blink/renderer/core/css/cssom/style_property_map.h"
+
+namespace blink {
+
+class CanvasFormattedTextStylePropertyMap;
+class FontDescription;
+
+class CanvasFormattedTextStyle : public GarbageCollectedMixin {
+  DISALLOW_NEW();
+
+ public:
+  explicit CanvasFormattedTextStyle(bool is_text_run)
+      : is_text_run_(is_text_run) {}
+  StylePropertyMap* styleMap();
+
+  virtual void SetNeedsStyleRecalc() = 0;
+  const CSSPropertyValueSet* GetCssPropertySet() const;
+
+  void Trace(Visitor* visitor) const override;
+
+ private:
+  bool is_text_run_;
+  Member<CanvasFormattedTextStylePropertyMap> style_map_;
+};
+
+class CanvasFormattedTextStylePropertyMap final : public StylePropertyMap {
+ public:
+  explicit CanvasFormattedTextStylePropertyMap(bool is_text_run,
+                                               CanvasFormattedTextStyle*);
+  CanvasFormattedTextStylePropertyMap(
+      const CanvasFormattedTextStylePropertyMap&) = delete;
+  CanvasFormattedTextStylePropertyMap& operator=(
+      const CanvasFormattedTextStylePropertyMap&) = delete;
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(css_property_value_set_);
+    visitor->Trace(canvas_formatted_text_style_);
+    StylePropertyMap::Trace(visitor);
+  }
+
+  unsigned int size() const final {
+    return css_property_value_set_->PropertyCount();
+  }
+
+  const CSSPropertyValueSet* GetCssPropertySet() const {
+    return css_property_value_set_;
+  }
+
+ protected:
+  const CSSValue* GetProperty(CSSPropertyID) const override;
+  const CSSValue* GetCustomProperty(const AtomicString&) const override;
+  void ForEachProperty(const IterationCallback&) override;
+  void SetProperty(CSSPropertyID, const CSSValue&) override;
+  bool SetShorthandProperty(CSSPropertyID,
+                            const String&,
+                            SecureContextMode) override;
+  void SetCustomProperty(const AtomicString&, const CSSValue&) override;
+  void RemoveProperty(CSSPropertyID) override;
+  void RemoveCustomProperty(const AtomicString&) override;
+  void RemoveAllProperties() final;
+
+  String SerializationForShorthand(const CSSProperty&) const final;
+
+ private:
+  Member<MutableCSSPropertyValueSet> css_property_value_set_;
+  WeakMember<CanvasFormattedTextStyle> canvas_formatted_text_style_;
+  bool is_text_run_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CANVAS_CANVAS2D_CANVAS_FORMATTED_TEXT_STYLE_H_
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.idl b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.idl
new file mode 100644
index 0000000..8b2f6f55
--- /dev/null
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_formatted_text_style.idl
@@ -0,0 +1,6 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+interface mixin CanvasFormattedTextStyle{
+  [SameObject] readonly attribute StylePropertyMap styleMap;
+};
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index cc3cbb7..8b1bfba3 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -925,7 +925,8 @@
     CanvasFormattedText* formatted_text,
     double x,
     double y,
-    double wrap_width) {
+    double wrap_width,
+    double height) {
   if (!formatted_text)
     return;
   // TODO(crbug.com/1234113): Instrument new canvas APIs.
@@ -937,7 +938,7 @@
   gfx::RectF bounds;
   sk_sp<PaintRecord> recording = formatted_text->PaintFormattedText(
       canvas()->GetDocument(), GetState().GetFontDescription(), x, y,
-      wrap_width, bounds);
+      wrap_width, height, bounds);
   Draw<OverdrawOp::kNone>(
       [recording](cc::PaintCanvas* c,
                   const cc::PaintFlags* flags)  // draw lambda
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
index 6ae2ce4..89d3eb07 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
@@ -139,7 +139,8 @@
   void fillFormattedText(CanvasFormattedText* formatted_text,
                          double x,
                          double y,
-                         double wrap_width);
+                         double wrap_width,
+                         double height = kIndefiniteSize);
 
   void drawFocusIfNeeded(Element*);
   void drawFocusIfNeeded(Path2D*, Element*);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl
index 3e96a86..34e7bba 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl
@@ -123,7 +123,7 @@
     TextMetrics measureText(DOMString text);
 
     // Render entire CanvasFormattedText with line wrapping (one-shot)
-    [RuntimeEnabled=CanvasFormattedText] void fillFormattedText(CanvasFormattedText formattedText, double x, double y, double wrapWidth);
+    [RuntimeEnabled=CanvasFormattedText] void fillFormattedText(CanvasFormattedText formattedText, double x, double y, double wrapWidth, optional double height);
 
     // drawing images
     [RaisesException] void drawImage(CanvasImageSource image, unrestricted double x, unrestricted double y);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index e77c1ad..47b308dc 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -358,6 +358,7 @@
     },
     {
       name: "CanvasFormattedText",
+      depends_on: ["LayoutNG"],
       status: "experimental",
     },
     {
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 1b05c41..ecf43e7 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -1529,6 +1529,8 @@
 # CanvasFormattedText is supported only through layout NG
 crbug.com/1176933 fast/canvas/canvas-formattedtext-1.html [ Skip ]
 crbug.com/1176933 fast/canvas/canvas-formattedtext-2.html [ Skip ]
+crbug.com/1176933 fast/canvas-api/canvas-formattedtext.html [ Skip ]
+crbug.com/1176933 wpt_internal/canvas/canvas-formattedtext-style.html [ Skip ]
 crbug.com/1176933 virtual/gpu/fast/canvas/canvas-formattedtext-1.html [ Skip ]
 crbug.com/1176933 virtual/gpu/fast/canvas/canvas-formattedtext-2.html [ Skip ]
 
@@ -1550,7 +1552,6 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/alt-display-vertical-001-manual.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/tooltip-display-vertical-001-manual.html [ Failure ]
 crbug.com/591099 external/wpt/html/editing/dnd/platform/close-drag-005-manual.html [ Failure ]
-crbug.com/591099 fast/canvas-api/canvas-formattedtext.html [ Crash ]
 crbug.com/591099 http/tests/devtools/elements/highlight/highlight-css-flex-item.js [ Failure ]
 
 # Failures accumulated until 2021-5-11
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 381dd32..be8fde7 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -1048,6 +1048,7 @@
 interface CanvasFormattedText
     attribute @@toStringTag
     getter length
+    getter styleMap
     method @@iterator
     method appendRun
     method constructor
@@ -1057,6 +1058,7 @@
     method setRun
 interface CanvasFormattedTextRun
     attribute @@toStringTag
+    getter styleMap
     getter text
     method constructor
     setter text
diff --git a/third_party/blink/web_tests/wpt_internal/canvas/canvas-formattedtext-style.html b/third_party/blink/web_tests/wpt_internal/canvas/canvas-formattedtext-style.html
new file mode 100644
index 0000000..8123d89
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/canvas/canvas-formattedtext-style.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<link rel="match" href="references/canvas-formattedtext-style-expected.html">
+<!-- Mac/GPU tests will have slightly different underline/strikethrough rendering -->
+<meta name=fuzzy content="maxDifference=255;totalPixels=12">
+<html>
+<head>
+  <style>
+    h3 {
+      margin: 0px;
+    }
+    canvas {
+      border: 1px solid black;
+      display: block;
+      margin-bottom: 2px;
+    }
+  </style>
+</head>
+<body>
+  <script>
+    function makeContext(id) {
+      var canvas = document.getElementById(id);
+      var context = canvas.getContext("2d", { alpha: true });
+      context.fillStyle = "#000000";
+      context.clearRect(0, 0, canvas.width, canvas.height);
+      context.fillStyle = "#FFFFFF";
+      context.fillRect(0, 0, canvas.width, canvas.height);
+      context.font = "20px Arial";
+      return context;
+    }
+    function makeRun(text, prop, value) {
+      text.appendRun(new CanvasFormattedTextRun(" "));
+      var textRun = new CanvasFormattedTextRun(prop + ":" + value);
+      textRun.styleMap.set(prop, value);
+      text.appendRun(textRun);
+    }
+  </script>
+  <h3>Test Font Related Styles</h3>
+  <canvas width=500 height=100 id="target1"></canvas>
+  <script>
+    function testFontRelatedStyles() {
+      var context = makeContext("target1");
+      var text = new CanvasFormattedText();
+      text.appendRun(new CanvasFormattedTextRun('Hello World !'));
+      makeRun(text, 'color', 'blue')
+      makeRun(text, 'text-decoration', 'underline')
+      makeRun(text, 'text-decoration', 'line-through')
+      makeRun(text, 'font-family', 'Times New Roman')
+      makeRun(text, 'font-kerning', 'none')
+      makeRun(text, 'font-kerning', 'normal')
+      makeRun(text, 'font-size', '10px')
+
+      // Test font-size-dependant lengths
+      text.appendRun(new CanvasFormattedTextRun(" "));
+      var textRun = new CanvasFormattedTextRun("1em");
+      textRun.styleMap.set('text-decoration', 'underline');
+      textRun.styleMap.set('text-decoration-thickness', '1em');
+      textRun.styleMap.set('text-underline-offset', '-12px');
+      textRun.styleMap.set('font-size', '25px');
+      text.appendRun(textRun);
+
+      context.fillFormattedText(text, 0, 0, 500);
+    };
+    testFontRelatedStyles();
+  </script>
+  <canvas width=500 height=30 id="target7"></canvas>
+  <script>
+    function testFontRelatedStyles2() {
+      var context = makeContext("target7");
+      var text = new CanvasFormattedText();
+      makeRun(text, 'font-weight', 'bold')
+      context.fillFormattedText(text, 0, 0, 500);
+    };
+    testFontRelatedStyles2();
+  </script>
+  <canvas width=500 height=30 id="target8"></canvas>
+  <script>
+    function testFontRelatedStyles3() {
+      var context = makeContext("target8");
+      var text = new CanvasFormattedText();
+      makeRun(text, 'font-stretch', 'condensed')
+      makeRun(text, 'font-variant-caps', 'small-caps');
+      context.fillFormattedText(text, 0, 0, 500);
+    };
+    testFontRelatedStyles3();
+  </script>
+  <canvas width=500 height=30 id="target9"></canvas>
+  <script>
+    function testFontRelatedStyles3() {
+      var context = makeContext("target9");
+      var text = new CanvasFormattedText();
+      makeRun(text, 'font-style', 'italic')
+      context.fillFormattedText(text, 0, 0, 500);
+    };
+    testFontRelatedStyles3();
+  </script>
+
+  <h3>Test RTL</h3>
+  <canvas width=500 height=30 id="target2"></canvas>
+  <script>
+    {
+      var context = makeContext("target2");
+      var text = new CanvasFormattedText();
+      var textRun = new CanvasFormattedTextRun('Hello World !');
+      text.appendRun(textRun);
+      text.styleMap.set('direction', 'rtl');
+      text.styleMap.set('width', '500px');
+      context.fillFormattedText(text, 0, 0, 500);
+    }
+  </script>
+
+  <h3>Test align</h3>
+  <canvas width=500 height=50 id="target3"></canvas>
+  <script>
+    {
+      var context = makeContext("target3");
+      var text = new CanvasFormattedText();
+      var textRun = new CanvasFormattedTextRun('Hello World ! Hello World ! Hello World ! Hello World ! Hello World !');
+      text.appendRun(textRun);
+      text.styleMap.set('text-align', 'center');
+      text.styleMap.set('width', '500px');
+      context.fillFormattedText(text, 0, 0, 500);
+    }
+  </script>
+
+  <h3>Test Writing-Mode</h3>
+  <canvas width=100 height=70 id="target4" style="border:1px solid black; display:inline-block;"></canvas>
+  <script>
+    {
+      var context = makeContext("target4");
+      var text = new CanvasFormattedText();
+      var textRun = new CanvasFormattedTextRun('ABC DEF GHI JKL MNO PQR');
+      text.appendRun(textRun);
+      text.styleMap.set('width', '100px');
+      text.styleMap.set('writing-mode', 'vertical-lr');
+      context.fillFormattedText(text, 0, 0, 100, 70);
+    }
+  </script>
+  <canvas width=100 height=70 id="target5" style="border:1px solid black; display:inline-block;"></canvas>
+  <script>
+    {
+      var context = makeContext("target5");
+      var text = new CanvasFormattedText();
+      var textRun = new CanvasFormattedTextRun('ABC DEF GHI JKL MNO PQR');
+      text.appendRun(textRun);
+      text.styleMap.set('width', '100px');
+      text.styleMap.set('writing-mode', 'vertical-rl');
+      context.fillFormattedText(text, 0, 0, 100, 70);
+    }
+  </script>
+  <canvas width=100 height=70 id="target6" style="border:1px solid black; display:inline-block;"></canvas>
+  <script>
+    {
+      var context = makeContext("target6");
+      var text = new CanvasFormattedText();
+      var textRun = new CanvasFormattedTextRun('ABC DEF GHI JKL MNO PQR');
+      text.appendRun(textRun);
+      text.styleMap.set('width', '100px');
+      text.styleMap.set('writing-mode', 'vertical-rl');
+      text.styleMap.set('text-orientation', 'upright');
+      context.fillFormattedText(text, 0, 0, 100, 70);
+    }
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/canvas/references/canvas-formattedtext-style-expected.html b/third_party/blink/web_tests/wpt_internal/canvas/references/canvas-formattedtext-style-expected.html
new file mode 100644
index 0000000..d9b49423
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/canvas/references/canvas-formattedtext-style-expected.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    h3 {
+      margin: 0px;
+    }
+    div {
+      width: 500px;
+      height: 100px;
+      border: 1px solid black;
+      font: 20px Arial;
+      vertical-align: top;
+      margin-bottom: 2px;
+    }
+
+    span {
+      vertical-align: top;
+    }
+
+    .ib {
+      width: 100px;
+      height: 70px;
+      display: inline-block;
+      overflow: hidden;
+    }
+  </style>
+</head>
+<body>
+  <h3>Test Font Related Styles</h3>
+  <div id="target1">
+    <span>Hello World !</span>
+  </div>
+  <script>
+    function makeRun(id, prop, value) {
+      document.getElementById(id).innerHTML += " <span style='" + prop + ":"
+        + value + "' >" + prop + ":" + value + "</span>";
+    }
+    function testFontRelatedStyles() {
+      makeRun("target1", 'color', 'blue')
+      makeRun("target1", 'text-decoration', 'underline')
+      makeRun("target1", 'text-decoration', 'line-through')
+      makeRun("target1", 'font-family', 'Times New Roman')
+      makeRun("target1", 'font-kerning', 'none')
+      makeRun("target1", 'font-kerning', 'normal')
+      makeRun("target1", 'font-size', '10px')
+      document.getElementById("target1").innerHTML += " <span style='text-decoration:underline; text-decoration-thickness:1em; text-underline-offset:-12px; font-size:25px;'>1em</span>"
+    };
+    testFontRelatedStyles();
+  </script>
+
+  <div id="target7" style="height:30px;">
+  </div>
+  <script>
+    function testFontRelatedStyles2() {
+      makeRun("target7", 'font-weight', 'bold')
+    };
+    testFontRelatedStyles2();
+  </script>
+
+  <div id="target8" style="height:30px;">
+  </div>
+  <script>
+    function testFontRelatedStyles3() {
+      makeRun("target8", 'font-stretch', 'condensed')
+      makeRun("target8", 'font-variant-caps', 'small-caps');
+    };
+    testFontRelatedStyles3();
+  </script>
+
+  <div id="target9" style="height:30px;">
+  </div>
+  <script>
+    function testFontRelatedStyles4() {
+      makeRun("target9", 'font-style', 'italic')
+    };
+    testFontRelatedStyles4();
+  </script>
+
+  <h3>Test RTL</h3>
+  <DIV style="direction:rtl; height:30px;"><span>Hello World !</span></DIV>
+
+  <h3>Test align</h3>
+  <DIV style="text-align:center; height:50px;">
+    <span>
+      Hello World ! Hello World ! Hello World ! Hello World ! Hello World !
+    </span>
+  </DIV>
+
+  <h3>Test Writing-Mode</h3>
+  <DIV class="ib" style="writing-mode:vertical-lr">
+    ABC DEF GHI JKL MNO PQR
+  </DIV>
+  <DIV class="ib" style="writing-mode:vertical-rl">
+    ABC DEF GHI JKL MNO PQR
+  </DIV>
+  <DIV class="ib" style="writing-mode:vertical-rl; text-orientation: upright;">
+    ABC DEF GHI JKL MNO PQR
+  </DIV>
+</body>
+</html>
\ No newline at end of file