Skip to content

Commit

Permalink
feat: support render svg in <img/>
Browse files Browse the repository at this point in the history
  • Loading branch information
XGHeaven committed Oct 22, 2023
1 parent afc34b8 commit 4d2b276
Show file tree
Hide file tree
Showing 32 changed files with 1,515 additions and 624 deletions.
54 changes: 54 additions & 0 deletions bridge/core/html/parser/html_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,45 @@ GumboOutput* parse(const std::string& html, bool isHTMLFragment = false) {
return htmlTree;
}

void transToSVG(GumboNode* node) {
if (node->type == GUMBO_NODE_ELEMENT) {
auto element = &node->v.element;
gumbo_tag_from_original_text(&element->original_tag);
const GumboVector* children = &element->children;
for (int i = 0; i < children->length; ++i) {
auto* child = (GumboNode*)children->data[i];
transToSVG(child);
}
}
}

GumboOutput* parseSVG(const char* buffer, size_t length) {
GumboOptions options = kGumboDefaultOptions;
options.fragment_namespace = GumboNamespaceEnum::GUMBO_NAMESPACE_SVG;
GumboOutput* svgTree = gumbo_parse_with_options(&options, buffer, length);

return svgTree;
}

GumboNode* findSVGRoot(GumboNode* node) {
if (node->type == GUMBO_NODE_ELEMENT) {
auto element = &node->v.element;
if (element->tag == GUMBO_TAG_SVG && element->tag_namespace == GUMBO_NAMESPACE_SVG) {
return node;
}
auto children = &element->children;
GumboNode* ret = nullptr;
for (int i = 0; i < children->length; i++) {
ret = findSVGRoot((GumboNode*)children->data[i]);
if (ret != nullptr) {
return ret;
}
}
}

return nullptr;
}

void HTMLParser::traverseHTML(Node* root_node, GumboNode* node) {
auto* context = root_node->GetExecutingContext();
JSContext* ctx = root_node->GetExecutingContext()->ctx();
Expand Down Expand Up @@ -136,6 +175,21 @@ bool HTMLParser::parseHTMLFragment(const char* code, size_t codeLength, Node* ro
return parseHTML(html, rootNode, true);
}

GumboOutput* HTMLParser::parseSVGResult(const char* code, size_t codeLength) {
std::string svg = std::string(code, codeLength);
auto result = parseSVG(code, codeLength);
auto root = findSVGRoot(result->root);
if (root != nullptr) {
transToSVG(root);
}

return result;
}

void HTMLParser::freeSVGResult(GumboOutput* svgTree) {
gumbo_destroy_output(&kGumboDefaultOptions, svgTree);
}

void HTMLParser::parseProperty(Element* element, GumboElement* gumboElement) {
auto* context = element->GetExecutingContext();
JSContext* ctx = context->ctx();
Expand Down
3 changes: 3 additions & 0 deletions bridge/core/html/parser/html_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class HTMLParser {
static bool parseHTML(const std::string& html, Node* rootNode);
static bool parseHTMLFragment(const char* code, size_t codeLength, Node* rootNode);

static GumboOutput* parseSVGResult(const char* code, size_t codeLength);
static void freeSVGResult(GumboOutput* svgTree);

private:
ExecutingContext* context_;
static void traverseHTML(Node* root, GumboNode* node);
Expand Down
6 changes: 4 additions & 2 deletions bridge/include/webf_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ typedef struct NativeValue NativeValue;
typedef struct NativeScreen NativeScreen;
typedef struct NativeByteCode NativeByteCode;

struct WebFInfo;

struct WebFInfo {
const char* app_name{nullptr};
const char* app_version{nullptr};
Expand Down Expand Up @@ -54,6 +52,10 @@ int8_t evaluateQuickjsByteCode(void* page, uint8_t* bytes, int32_t byteLen);
WEBF_EXPORT_C
void parseHTML(void* page, const char* code, int32_t length);
WEBF_EXPORT_C
void* parseSVGResult(const char* code, int32_t length);
WEBF_EXPORT_C
void freeSVGResult(void* svgTree);
WEBF_EXPORT_C
NativeValue* invokeModuleEvent(void* page,
SharedNativeString* module,
const char* eventType,
Expand Down
10 changes: 10 additions & 0 deletions bridge/webf_bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "bindings/qjs/native_string_utils.h"
#include "core/dart_isolate_context.h"
#include "core/html/parser/html_parser.h"
#include "core/page.h"
#include "foundation/logging.h"
#include "foundation/ui_command_buffer.h"
Expand Down Expand Up @@ -89,6 +90,15 @@ void parseHTML(void* page_, const char* code, int32_t length) {
page->parseHTML(code, length);
}

void* parseSVGResult(const char* code, int32_t length) {
auto* result = webf::HTMLParser::parseSVGResult(code, length);
return result;
}

void freeSVGResult(void* svgTree) {
webf::HTMLParser::freeSVGResult(reinterpret_cast<GumboOutput*>(svgTree));
}

NativeValue* invokeModuleEvent(void* page_,
SharedNativeString* module_name,
const char* eventType,
Expand Down
1 change: 1 addition & 0 deletions integration_tests/assets/js-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added integration_tests/assets/non-svg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions integration_tests/specs/dom/events/appear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,34 @@ describe('Appear Event', () => {
done();
});
});

it('should trigger appear when changing RenderBox type', async () => {
const div = document.createElement('div');
div.style.width = '300px';
div.style.height = '300px';
div.style.backgroundColor = 'red';
div.style.top = '0';

document.body.appendChild(div);

let resolve = () => {}
let promise = new Promise<void>((res) => resolve = res)
div.addEventListener('appear', function onAppear() {
resolve();
promise = new Promise<void>((res) => resolve = res)
});

await promise

div.style.top = '-600px';
// this style will change RenderFlexLayout to RenderRepaintBoundaryFlexLayout
div.style.position = 'absolute';
div.style.display = 'flex';

await sleep(0.5)

div.style.top = '0'; // appear

await promise
})
});
95 changes: 95 additions & 0 deletions integration_tests/specs/svg/rendering/within-img.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
function createCaseForErrorUrl(url) {
return (done) => {
const img = document.createElement('img');
img.src = url;
img.style.width = '100px';
img.style.height = '100px';
img.onload = () => {
done.fail('Unreachable');
}
img.onerror = () => {
done();
}
document.body.appendChild(img);
}
}

function createCaseForSize(width?: string, height?: string) {
return (done) => {
const img = document.createElement('img');
img.src = 'assets/js-icon.svg'
img.style.border = '1px solid green';
if (width) img.style.width = width;
if (height) img.style.height = height;
img.onload = () => {
snapshot().then(done, done.fail);
}
img.onerror = () => {
done.fail('Should now throw error');
}
document.body.appendChild(img);
}
}

function createCaseForToggleImg(srcs: string[], hasError?: boolean) {
return (done) => {
let i = 0;
function next() {
if (i == srcs.length) {
// snapshot last image
snapshot().then(done, done.fail);
return;
}
img.src = srcs[i++];
}
const img = document.createElement('img');
img.style.width = img.style.height = '100px';
img.onload = async () => {
setTimeout(() => next(), 100);
}
img.onerror = () => {
if (hasError) {
setTimeout(() => next(), 100);
} else {
done.fail('Unreachable');
}
}
document.body.appendChild(img);

next();
}
}

describe('SVG rendering within img', () => {
it('should works with svg', (done) => {
const img = document.createElement('img');
img.src = 'assets/js-icon.svg';
img.style.width = '100px';
img.style.height = '100px';
img.onload = async () => {
await snapshot();
done();
}
document.body.appendChild(img);
});

it('should throw error when decode svg file failed', createCaseForErrorUrl('assets/non-svg.svg'));

it('should throw error when resource is not found', createCaseForErrorUrl('assets/svg-icon-not-found.svg'));

it('should show large size', createCaseForSize('300px', '300px'));
it('should auto calc height when has a fixed width', createCaseForSize('100px'));
it('should auto calc width when has a fixed height', createCaseForSize(undefined, '100px'));
it('should use natural size when height and width is not fixed', createCaseForSize());

it('should correct render when switch img src', createCaseForToggleImg([
'assets/100x100-green.png',
'assets/js-icon.svg',
]));

it('should render svg after a failed load', createCaseForToggleImg([
'assets/100x100-green.png',
'assets/non-svg.svg', // decode error
'assets/js-icon.svg' // correct image
], true))
});
Binary file added webf/example/assets/10frames-1s.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion webf/example/assets/bundle.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</div>
<div class="icons">
<svg class="icon" viewBox="0 0 1024 1024"><path d="M64 512c0 195.2 124.8 361.6 300.8 422.4 22.4 6.4 19.2-9.6 19.2-22.4v-76.8c-134.4 16-140.8-73.6-150.4-89.6-19.2-32-60.8-38.4-48-54.4 32-16 64 3.2 99.2 57.6 25.6 38.4 76.8 32 105.6 25.6 6.4-22.4 19.2-44.8 35.2-60.8-144-22.4-201.6-108.8-201.6-211.2 0-48 16-96 48-131.2-22.4-60.8 0-115.2 3.2-121.6 57.6-6.4 118.4 41.6 124.8 44.8 32-9.6 70.4-12.8 112-12.8 41.6 0 80 6.4 112 12.8 12.8-9.6 67.2-48 121.6-44.8 3.2 6.4 25.6 57.6 6.4 118.4 32 38.4 48 83.2 48 131.2 0 102.4-57.6 188.8-201.6 214.4 22.4 22.4 38.4 54.4 38.4 92.8v112c0 9.6 0 19.2 16 19.2C832 876.8 960 710.4 960 512c0-246.4-201.6-448-448-448S64 265.6 64 512z" fill="#040000" p-id="3824"></path></svg>
<svg class="icon" viewBox="0 0 1024 1024"><path d="M63.5 63.5h897v897h-897v-897zM887 746.6c-6.5-40.9-33.2-75.3-112.2-107.4-27.5-12.9-58.1-21.9-67.2-42.6-3.4-12.3-3.9-19.1-1.7-26.4 5.6-24.1 34.2-31.4 56.6-24.7 14.6 4.5 28 15.7 36.5 33.6 38.6-25.3 38.6-25.3 65.6-42-10.1-15.7-15.1-22.5-21.9-29.2-23.5-26.3-54.9-39.8-105.9-38.6l-26.4 3.3c-25.3 6.2-49.3 19.6-63.9 37.6-42.6 48.3-30.3 132.3 21.3 167.1 51 38.1 125.6 46.5 135.2 82.4 9 43.7-32.5 57.7-73.5 52.7-30.3-6.7-47.1-21.9-65.6-49.9l-68.4 39.3c7.8 17.9 16.8 25.8 30.3 41.4 65 65.6 227.6 62.3 256.8-37.5 1.1-3.4 9-26.4 2.8-61.7l1.6 2.6zM551.3 475.8h-84c0 72.4-0.3 144.4-0.3 217 0 46 2.4 88.3-5.2 101.3-12.3 25.8-44.1 22.5-58.5 17.9-14.8-7.3-22.3-17.4-31-32-2.4-3.9-4.1-7.3-4.7-7.3l-68.2 42c11.4 23.5 28 43.8 49.5 56.7 32 19.1 74.9 25.2 119.9 15.1 29.3-8.4 54.5-25.8 67.7-52.7 19.1-34.8 15-77.4 14.8-125.1 0.4-76.8 0-153.6 0-230.9v-2z" fill="#F5DD1E" p-id="4254"></path></svg>
<img class="icon" src="./js-icon.svg" />
<svg class="icon" viewBox="0 0 1024 1024"><path d="M89.088 59.392l62.464 803.84c1.024 12.288 9.216 22.528 20.48 25.6L502.784 993.28c6.144 2.048 12.288 2.048 18.432 0l330.752-104.448c11.264-4.096 19.456-14.336 20.48-25.6l62.464-803.84c1.024-17.408-12.288-31.744-29.696-31.744H118.784c-17.408 0-31.744 14.336-29.696 31.744z" fill="#FC490B" p-id="5421"></path><path d="M774.144 309.248h-409.6l12.288 113.664h388.096l-25.6 325.632-227.328 71.68-227.328-71.68-13.312-169.984h118.784v82.944l124.928 33.792 123.904-33.792 10.24-132.096H267.264L241.664 204.8h540.672z" fill="#FFFFFF" p-id="5422"></path></svg>
</div>
<script>
Expand Down
1 change: 1 addition & 0 deletions webf/example/assets/js-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4d2b276

Please sign in to comment.