🐛 Fix text shadows and blur and refactor text rendering
@@ -23,6 +23,7 @@ export default defineConfig({
|
|||||||
expect: {
|
expect: {
|
||||||
timeout: process.env.CI ? 20000 : 5000,
|
timeout: process.env.CI ? 20000 : 5000,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: "html",
|
reporter: "html",
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
@@ -62,7 +63,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
testDir: "./playwright/ui/render-wasm-specs",
|
testDir: "./playwright/ui/render-wasm-specs",
|
||||||
snapshotPathTemplate: "{testDir}/{testFilePath}-snapshots/{arg}.png",
|
snapshotPathTemplate: "{testDir}/{testFilePath}-snapshots/{arg}.png",
|
||||||
|
timeout: 2 * 60 * 1000,
|
||||||
expect: {
|
expect: {
|
||||||
|
timeout: process.env.CI ? 20000 : 10000,
|
||||||
toHaveScreenshot: {
|
toHaveScreenshot: {
|
||||||
maxDiffPixelRatio: 0.001,
|
maxDiffPixelRatio: 0.001,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,905 @@
|
|||||||
|
{
|
||||||
|
"~:features": {
|
||||||
|
"~#set": [
|
||||||
|
"fdata/path-data",
|
||||||
|
"plugins/runtime",
|
||||||
|
"design-tokens/v1",
|
||||||
|
"variants/v1",
|
||||||
|
"layout/grid",
|
||||||
|
"styles/v2",
|
||||||
|
"fdata/objects-map",
|
||||||
|
"render-wasm/v1",
|
||||||
|
"components/v2",
|
||||||
|
"fdata/shape-data-type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"~:team-id": "~u6bd7c17d-4f59-815e-8006-5c1f6882469a",
|
||||||
|
"~:permissions": {
|
||||||
|
"~:type": "~:membership",
|
||||||
|
"~:is-owner": true,
|
||||||
|
"~:is-admin": true,
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:can-read": true,
|
||||||
|
"~:is-logged": true
|
||||||
|
},
|
||||||
|
"~:has-media-trimmed": false,
|
||||||
|
"~:comment-thread-seqn": 0,
|
||||||
|
"~:name": "New File 2",
|
||||||
|
"~:revn": 401,
|
||||||
|
"~:modified-at": "~m1757076417573",
|
||||||
|
"~:vern": 0,
|
||||||
|
"~:id": "~u15b74473-2908-8094-8006-bc90c3982c73",
|
||||||
|
"~:is-shared": false,
|
||||||
|
"~:migrations": {
|
||||||
|
"~#ordered-set": [
|
||||||
|
"legacy-2",
|
||||||
|
"legacy-3",
|
||||||
|
"legacy-5",
|
||||||
|
"legacy-6",
|
||||||
|
"legacy-7",
|
||||||
|
"legacy-8",
|
||||||
|
"legacy-9",
|
||||||
|
"legacy-10",
|
||||||
|
"legacy-11",
|
||||||
|
"legacy-12",
|
||||||
|
"legacy-13",
|
||||||
|
"legacy-14",
|
||||||
|
"legacy-16",
|
||||||
|
"legacy-17",
|
||||||
|
"legacy-18",
|
||||||
|
"legacy-19",
|
||||||
|
"legacy-25",
|
||||||
|
"legacy-26",
|
||||||
|
"legacy-27",
|
||||||
|
"legacy-28",
|
||||||
|
"legacy-29",
|
||||||
|
"legacy-31",
|
||||||
|
"legacy-32",
|
||||||
|
"legacy-33",
|
||||||
|
"legacy-34",
|
||||||
|
"legacy-36",
|
||||||
|
"legacy-37",
|
||||||
|
"legacy-38",
|
||||||
|
"legacy-39",
|
||||||
|
"legacy-40",
|
||||||
|
"legacy-41",
|
||||||
|
"legacy-42",
|
||||||
|
"legacy-43",
|
||||||
|
"legacy-44",
|
||||||
|
"legacy-45",
|
||||||
|
"legacy-46",
|
||||||
|
"legacy-47",
|
||||||
|
"legacy-48",
|
||||||
|
"legacy-49",
|
||||||
|
"legacy-50",
|
||||||
|
"legacy-51",
|
||||||
|
"legacy-52",
|
||||||
|
"legacy-53",
|
||||||
|
"legacy-54",
|
||||||
|
"legacy-55",
|
||||||
|
"legacy-56",
|
||||||
|
"legacy-57",
|
||||||
|
"legacy-59",
|
||||||
|
"legacy-62",
|
||||||
|
"legacy-65",
|
||||||
|
"legacy-66",
|
||||||
|
"legacy-67",
|
||||||
|
"0001-remove-tokens-from-groups",
|
||||||
|
"0002-normalize-bool-content-v2",
|
||||||
|
"0002-clean-shape-interactions",
|
||||||
|
"0003-fix-root-shape",
|
||||||
|
"0003-convert-path-content-v2",
|
||||||
|
"0004-clean-shadow-color",
|
||||||
|
"0005-deprecate-image-type",
|
||||||
|
"0006-fix-old-texts-fills",
|
||||||
|
"0007-clear-invalid-strokes-and-fills-v2",
|
||||||
|
"0008-fix-library-colors-v4",
|
||||||
|
"0009-clean-library-colors",
|
||||||
|
"0009-add-partial-text-touched-flags",
|
||||||
|
"0010-fix-swap-slots-pointing-non-existent-shapes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"~:version": 67,
|
||||||
|
"~:project-id": "~u6bd7c17d-4f59-815e-8006-5c1f68846e43",
|
||||||
|
"~:created-at": "~m1756728830560",
|
||||||
|
"~:data": {
|
||||||
|
"~:pages": [
|
||||||
|
"~u15b74473-2908-8094-8006-bc90c3982c74"
|
||||||
|
],
|
||||||
|
"~:pages-index": {
|
||||||
|
"~u15b74473-2908-8094-8006-bc90c3982c74": {
|
||||||
|
"~:objects": {
|
||||||
|
"~u00000000-0000-0000-0000-000000000000": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 0,
|
||||||
|
"~:hide-fill-on-export": false,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:name": "Root Frame",
|
||||||
|
"~:width": 0.01,
|
||||||
|
"~:type": "~:frame",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0,
|
||||||
|
"~:y": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0.01,
|
||||||
|
"~:y": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0.01,
|
||||||
|
"~:y": 0.01
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0,
|
||||||
|
"~:y": 0.01
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:r2": 0,
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:r3": 0,
|
||||||
|
"~:r1": 0,
|
||||||
|
"~:id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [],
|
||||||
|
"~:x": 0,
|
||||||
|
"~:proportion": 1,
|
||||||
|
"~:r4": 0,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 0,
|
||||||
|
"~:y": 0,
|
||||||
|
"~:width": 0.01,
|
||||||
|
"~:height": 0.01,
|
||||||
|
"~:x1": 0,
|
||||||
|
"~:y1": 0,
|
||||||
|
"~:x2": 0.01,
|
||||||
|
"~:y2": 0.01
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#FFFFFF",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 0.01,
|
||||||
|
"~:flip-y": null,
|
||||||
|
"~:shapes": [
|
||||||
|
"~u88db2850-996a-804f-8006-c063323d68a4",
|
||||||
|
"~uf94516f3-2d43-80b3-8006-c1b5c96d7dae",
|
||||||
|
"~uf94516f3-2d43-80b3-8006-c1b60a252bcf"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~u88db2850-996a-804f-8006-c063323d68a4": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": -865.0000046417117,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:last-resize-direction": "~:horizontal",
|
||||||
|
"~:grow-type": "~:auto-height",
|
||||||
|
"~:content": {
|
||||||
|
"~:type": "root",
|
||||||
|
"~:key": "1ygxrlda8tl",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:type": "paragraph-set",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "1.2",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:key": "23svy7uenp6",
|
||||||
|
"~:font-size": "48",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [],
|
||||||
|
"~:font-family": "sourcesanspro",
|
||||||
|
"~:text": "shadows with multiple strokes and no fill"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:text-align": "center",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:key": "e92bgu67k4",
|
||||||
|
"~:font-size": "0",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:text-direction": "ltr",
|
||||||
|
"~:type": "paragraph",
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [],
|
||||||
|
"~:font-family": "sourcesanspro"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:vertical-align": "top"
|
||||||
|
},
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Text",
|
||||||
|
"~:width": 506.1299901710943,
|
||||||
|
"~:type": "~:text",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": -251.00000411998855,
|
||||||
|
"~:y": -865.0000046417117
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 255.12998605110573,
|
||||||
|
"~:y": -865.0000046417117
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 255.12998605110573,
|
||||||
|
"~:y": -749.0000040708983
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": -251.00000411998855,
|
||||||
|
"~:y": -749.0000040708983
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:layout-item-h-sizing": "~:fix",
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~u88db2850-996a-804f-8006-c063323d68a4",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:position-data": [
|
||||||
|
{
|
||||||
|
"~#rect": {
|
||||||
|
"~:y": -804.7000015899539,
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-size": "48px",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:y1": -2.6999969482421875,
|
||||||
|
"~:width": 464.2166748046875,
|
||||||
|
"~:text-decoration": "rgb(0, 0, 0)",
|
||||||
|
"~:letter-spacing": "normal",
|
||||||
|
"~:x": -230.05000717174636,
|
||||||
|
"~:x1": 20.949996948242188,
|
||||||
|
"~:y2": 60.30000305175781,
|
||||||
|
"~:fills": [],
|
||||||
|
"~:x2": 485.1666717529297,
|
||||||
|
"~:direction": "ltr",
|
||||||
|
"~:font-family": "\"sourcesanspro\"",
|
||||||
|
"~:height": 63,
|
||||||
|
"~:text": "shadows with multiple "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#rect": {
|
||||||
|
"~:y": -747.1000107452273,
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-size": "48px",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:y1": 54.899993896484375,
|
||||||
|
"~:width": 354.3333435058594,
|
||||||
|
"~:text-decoration": "rgb(0, 0, 0)",
|
||||||
|
"~:letter-spacing": "normal",
|
||||||
|
"~:x": -175.10001022350417,
|
||||||
|
"~:x1": 75.89999389648438,
|
||||||
|
"~:y2": 117.89999389648438,
|
||||||
|
"~:fills": [],
|
||||||
|
"~:x2": 430.23333740234375,
|
||||||
|
"~:direction": "ltr",
|
||||||
|
"~:font-family": "\"sourcesanspro\"",
|
||||||
|
"~:height": 63,
|
||||||
|
"~:text": "strokes and no fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [
|
||||||
|
{
|
||||||
|
"~:stroke-style": "~:solid",
|
||||||
|
"~:stroke-alignment": "~:inner",
|
||||||
|
"~:stroke-width": 1,
|
||||||
|
"~:stroke-color": "#00ff11",
|
||||||
|
"~:stroke-opacity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:stroke-style": "~:solid",
|
||||||
|
"~:stroke-alignment": "~:outer",
|
||||||
|
"~:stroke-width": 1,
|
||||||
|
"~:stroke-color": "#ff00b1",
|
||||||
|
"~:stroke-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x": -251.00000411998855,
|
||||||
|
"~:shadow": [
|
||||||
|
{
|
||||||
|
"~:color": {
|
||||||
|
"~:color": "#7750e1",
|
||||||
|
"~:opacity": 0.4722222222222222
|
||||||
|
},
|
||||||
|
"~:spread": 0,
|
||||||
|
"~:offset-y": 10,
|
||||||
|
"~:style": "~:drop-shadow",
|
||||||
|
"~:blur": 0,
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~uf94516f3-2d43-80b3-8006-c1b5b7323de5",
|
||||||
|
"~:offset-x": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": {
|
||||||
|
"~:color": "#559fe1",
|
||||||
|
"~:opacity": 0.7333333333333333
|
||||||
|
},
|
||||||
|
"~:spread": 0,
|
||||||
|
"~:offset-y": -10,
|
||||||
|
"~:style": "~:drop-shadow",
|
||||||
|
"~:blur": 0,
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~u427eca67-5b7f-80e6-8006-c0a7398ff4b4",
|
||||||
|
"~:offset-x": -10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": -251.00000411998855,
|
||||||
|
"~:y": -865.0000046417117,
|
||||||
|
"~:width": 506.1299901710943,
|
||||||
|
"~:height": 116.00000057081343,
|
||||||
|
"~:x1": -251.00000411998855,
|
||||||
|
"~:y1": -865.0000046417117,
|
||||||
|
"~:x2": 255.12998605110573,
|
||||||
|
"~:y2": -749.0000040708983
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 116.00000057081343,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~uf94516f3-2d43-80b3-8006-c1b5c96d7dae": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": -723.9999884292483,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:last-resize-direction": "~:horizontal",
|
||||||
|
"~:grow-type": "~:auto-height",
|
||||||
|
"~:content": {
|
||||||
|
"~:type": "root",
|
||||||
|
"~:key": "1ygxrlda8tl",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:type": "paragraph-set",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "1.2",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "1.2",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:key": "23svy7uenp6",
|
||||||
|
"~:font-size": "48",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#214ccd",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:font-family": "sourcesanspro",
|
||||||
|
"~:text": "shadows with multiple strokes and solid fill"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:text-align": "center",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:key": "e92bgu67k4",
|
||||||
|
"~:font-size": "48",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:text-direction": "ltr",
|
||||||
|
"~:type": "paragraph",
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#214ccd",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:font-family": "sourcesanspro"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:vertical-align": "top"
|
||||||
|
},
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Text",
|
||||||
|
"~:width": 609.1300277709961,
|
||||||
|
"~:type": "~:text",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": -303.0000086630885,
|
||||||
|
"~:y": -723.9999884292483
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 306.1300191079076,
|
||||||
|
"~:y": -723.9999884292483
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 306.1300191079076,
|
||||||
|
"~:y": -607.9999878584349
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": -303.0000086630885,
|
||||||
|
"~:y": -607.9999878584349
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:layout-item-h-sizing": "~:fix",
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~uf94516f3-2d43-80b3-8006-c1b5c96d7dae",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:position-data": [
|
||||||
|
{
|
||||||
|
"~#rect": {
|
||||||
|
"~:y": -663.6999853774905,
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-size": "48px",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:y1": -2.6999969482421875,
|
||||||
|
"~:width": 464.2166748046875,
|
||||||
|
"~:text-decoration": "rgb(33, 76, 205)",
|
||||||
|
"~:letter-spacing": "normal",
|
||||||
|
"~:x": -230.55001171484633,
|
||||||
|
"~:x1": 72.44999694824219,
|
||||||
|
"~:y2": 60.30000305175781,
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#214ccd",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x2": 536.6666717529297,
|
||||||
|
"~:direction": "ltr",
|
||||||
|
"~:font-family": "\"sourcesanspro\"",
|
||||||
|
"~:height": 63,
|
||||||
|
"~:text": "shadows with multiple "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#rect": {
|
||||||
|
"~:y": -606.099994532764,
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-size": "48px",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:y1": 54.899993896484375,
|
||||||
|
"~:width": 398.8500061035156,
|
||||||
|
"~:text-decoration": "rgb(33, 76, 205)",
|
||||||
|
"~:letter-spacing": "normal",
|
||||||
|
"~:x": -197.8666773642604,
|
||||||
|
"~:x1": 105.13333129882812,
|
||||||
|
"~:y2": 117.89999389648438,
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#214ccd",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x2": 503.98333740234375,
|
||||||
|
"~:direction": "ltr",
|
||||||
|
"~:font-family": "\"sourcesanspro\"",
|
||||||
|
"~:height": 63,
|
||||||
|
"~:text": "strokes and solid fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [
|
||||||
|
{
|
||||||
|
"~:stroke-style": "~:solid",
|
||||||
|
"~:stroke-alignment": "~:inner",
|
||||||
|
"~:stroke-width": 1,
|
||||||
|
"~:stroke-color": "#00ff11",
|
||||||
|
"~:stroke-opacity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:stroke-style": "~:solid",
|
||||||
|
"~:stroke-alignment": "~:outer",
|
||||||
|
"~:stroke-width": 3,
|
||||||
|
"~:stroke-color": "#ff00b1",
|
||||||
|
"~:stroke-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x": -303.0000086630885,
|
||||||
|
"~:shadow": [
|
||||||
|
{
|
||||||
|
"~:color": {
|
||||||
|
"~:color": "#7750e1",
|
||||||
|
"~:opacity": 0.4722222222222222
|
||||||
|
},
|
||||||
|
"~:spread": 0,
|
||||||
|
"~:offset-y": 4,
|
||||||
|
"~:style": "~:drop-shadow",
|
||||||
|
"~:blur": 0,
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~uf94516f3-2d43-80b3-8006-c1b5b7323de5",
|
||||||
|
"~:offset-x": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": {
|
||||||
|
"~:color": "#559fe1",
|
||||||
|
"~:opacity": 0.7333333333333333
|
||||||
|
},
|
||||||
|
"~:spread": 0,
|
||||||
|
"~:offset-y": -4,
|
||||||
|
"~:style": "~:drop-shadow",
|
||||||
|
"~:blur": 0,
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~u427eca67-5b7f-80e6-8006-c0a7398ff4b4",
|
||||||
|
"~:offset-x": -4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": -303.0000086630885,
|
||||||
|
"~:y": -723.9999884292483,
|
||||||
|
"~:width": 609.1300277709961,
|
||||||
|
"~:height": 116.00000057081343,
|
||||||
|
"~:x1": -303.0000086630885,
|
||||||
|
"~:y1": -723.9999884292483,
|
||||||
|
"~:x2": 306.1300191079076,
|
||||||
|
"~:y2": -607.9999878584349
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 116.00000057081343,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~uf94516f3-2d43-80b3-8006-c1b60a252bcf": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": -581.9999718032777,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:fixed",
|
||||||
|
"~:content": {
|
||||||
|
"~:type": "root",
|
||||||
|
"~:key": "1ygxrlda8tl",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:type": "paragraph-set",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "1.2",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:key": "23svy7uenp6",
|
||||||
|
"~:font-size": "48",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#ff7700",
|
||||||
|
"~:fill-opacity": 0.38333333333333336
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:font-family": "sourcesanspro",
|
||||||
|
"~:text": "shadows with multiple strokes and transparent fill"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:text-align": "center",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:key": "e92bgu67k4",
|
||||||
|
"~:font-size": "0",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:text-direction": "ltr",
|
||||||
|
"~:type": "paragraph",
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#ff7700",
|
||||||
|
"~:fill-opacity": 0.38333333333333336
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:font-family": "sourcesanspro"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:vertical-align": "top"
|
||||||
|
},
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Text",
|
||||||
|
"~:width": 753.1299834251404,
|
||||||
|
"~:type": "~:text",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": -374.99998553834683,
|
||||||
|
"~:y": -581.9999718032777
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 378.1299978867936,
|
||||||
|
"~:y": -581.9999718032777
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 378.1299978867936,
|
||||||
|
"~:y": -523.999971517871
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": -374.99998553834683,
|
||||||
|
"~:y": -523.999971517871
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:layout-item-h-sizing": "~:fix",
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1,
|
||||||
|
"~:b": 0,
|
||||||
|
"~:c": 0,
|
||||||
|
"~:d": 1,
|
||||||
|
"~:e": 0,
|
||||||
|
"~:f": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~uf94516f3-2d43-80b3-8006-c1b60a252bcf",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:position-data": [
|
||||||
|
{
|
||||||
|
"~#rect": {
|
||||||
|
"~:y": -521.6999687515199,
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-size": "48px",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:y1": -2.6999969482421875,
|
||||||
|
"~:width": 706.3333740234375,
|
||||||
|
"~:text-decoration": "rgba(255, 119, 0, 0.383)",
|
||||||
|
"~:letter-spacing": "normal",
|
||||||
|
"~:x": -351.5999916418624,
|
||||||
|
"~:x1": 23.399993896484375,
|
||||||
|
"~:y2": 60.30000305175781,
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#ff7700",
|
||||||
|
"~:fill-opacity": 0.38333333333333336
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x2": 729.7333679199219,
|
||||||
|
"~:direction": "ltr",
|
||||||
|
"~:font-family": "\"sourcesanspro\"",
|
||||||
|
"~:height": 63,
|
||||||
|
"~:text": "shadows with multiple strokes and "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#rect": {
|
||||||
|
"~:y": -464.09997790679336,
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-size": "48px",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:y1": 54.899993896484375,
|
||||||
|
"~:width": 295.48333740234375,
|
||||||
|
"~:text-decoration": "rgba(255, 119, 0, 0.383)",
|
||||||
|
"~:letter-spacing": "normal",
|
||||||
|
"~:x": -146.18331988893272,
|
||||||
|
"~:x1": 228.81666564941406,
|
||||||
|
"~:y2": 117.89999389648438,
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#ff7700",
|
||||||
|
"~:fill-opacity": 0.38333333333333336
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x2": 524.3000030517578,
|
||||||
|
"~:direction": "ltr",
|
||||||
|
"~:font-family": "\"sourcesanspro\"",
|
||||||
|
"~:height": 63,
|
||||||
|
"~:text": "transparent fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [
|
||||||
|
{
|
||||||
|
"~:stroke-style": "~:solid",
|
||||||
|
"~:stroke-alignment": "~:outer",
|
||||||
|
"~:stroke-width": 3,
|
||||||
|
"~:stroke-color": "#ff00b0",
|
||||||
|
"~:stroke-opacity": 0.37222222222222223
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x": -374.9999855383468,
|
||||||
|
"~:shadow": [
|
||||||
|
{
|
||||||
|
"~:color": {
|
||||||
|
"~:color": "#00fb08",
|
||||||
|
"~:opacity": 1
|
||||||
|
},
|
||||||
|
"~:spread": 0,
|
||||||
|
"~:offset-y": 4,
|
||||||
|
"~:style": "~:drop-shadow",
|
||||||
|
"~:blur": 0,
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~uf94516f3-2d43-80b3-8006-c1b5b7323de5",
|
||||||
|
"~:offset-x": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": {
|
||||||
|
"~:color": "#559fe1",
|
||||||
|
"~:opacity": 0.7333333333333333
|
||||||
|
},
|
||||||
|
"~:spread": 0,
|
||||||
|
"~:offset-y": -4,
|
||||||
|
"~:style": "~:drop-shadow",
|
||||||
|
"~:blur": 0,
|
||||||
|
"~:hidden": false,
|
||||||
|
"~:id": "~u427eca67-5b7f-80e6-8006-c0a7398ff4b4",
|
||||||
|
"~:offset-x": -4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": -374.9999855383468,
|
||||||
|
"~:y": -581.9999718032777,
|
||||||
|
"~:width": 753.1299834251404,
|
||||||
|
"~:height": 58.00000028540671,
|
||||||
|
"~:x1": -374.9999855383468,
|
||||||
|
"~:y1": -581.9999718032777,
|
||||||
|
"~:x2": 378.1299978867936,
|
||||||
|
"~:y2": -523.999971517871
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 58.00000028540671,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~u15b74473-2908-8094-8006-bc90c3982c74",
|
||||||
|
"~:name": "Page 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~u15b74473-2908-8094-8006-bc90c3982c73",
|
||||||
|
"~:options": {
|
||||||
|
"~:components-v2": true,
|
||||||
|
"~:base-font-size": "16px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -227,6 +227,71 @@ test("Renders a file with multiple emoji", async ({ page }) => {
|
|||||||
await expect(workspace.canvas).toHaveScreenshot();
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Renders a file with multiple text shadows, strokes, and blur combinations", async ({ page }) => {
|
||||||
|
const workspace = new WasmWorkspacePage(page);
|
||||||
|
await workspace.setupEmptyFile();
|
||||||
|
|
||||||
|
await workspace.mockGetFile(
|
||||||
|
"render-wasm/get-file-text-shadows-and-blurs.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
await workspace.goToWorkspace({
|
||||||
|
id: "15b74473-2908-8094-8006-bdb4fbd2c6a3",
|
||||||
|
pageId: "15b74473-2908-8094-8006-bdb4fbd2c6a4",
|
||||||
|
});
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders a file with different text leaves decoration", async ({ page }) => {
|
||||||
|
const workspace = new WasmWorkspacePage(page);
|
||||||
|
await workspace.setupEmptyFile();
|
||||||
|
await workspace.mockGetFile(
|
||||||
|
"render-wasm/get-file-text-leaves-decoration.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
await workspace.goToWorkspace({
|
||||||
|
id: "15b74473-2908-8094-8006-bdb4fbd2c6a3",
|
||||||
|
pageId: "15b74473-2908-8094-8006-bdb4fbd2c6a4",
|
||||||
|
});
|
||||||
|
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders a file with different text shadows combinations", async ({ page }) => {
|
||||||
|
const workspace = new WasmWorkspacePage(page);
|
||||||
|
await workspace.setupEmptyFile();
|
||||||
|
await workspace.mockGetFile(
|
||||||
|
"render-wasm/get-file-text-shadows-combination.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
await workspace.goToWorkspace({
|
||||||
|
id: "15b74473-2908-8094-8006-bdb4fbd2c6a3",
|
||||||
|
pageId: "15b74473-2908-8094-8006-bc90c3982c74",
|
||||||
|
});
|
||||||
|
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("Renders a file with text in frames and different strokes, shadows, and blurs", async ({ page }) => {
|
||||||
|
const workspace = new WasmWorkspacePage(page);
|
||||||
|
await workspace.setupEmptyFile();
|
||||||
|
await workspace.mockGetFile(
|
||||||
|
"render-wasm/get-file-frame-clipping-shadows-and-texts.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
await workspace.goToWorkspace({
|
||||||
|
id: "44471494-966a-8178-8006-c5bd93f0fe72",
|
||||||
|
pageId: "44471494-966a-8178-8006-c5bd93f0fe73",
|
||||||
|
});
|
||||||
|
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
});
|
||||||
|
|
||||||
test("Renders a file with texts with different alignments", async ({
|
test("Renders a file with texts with different alignments", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 240 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 430 KiB |
|
Before Width: | Height: | Size: 524 KiB After Width: | Height: | Size: 523 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 126 KiB |
@@ -436,6 +436,9 @@ impl RenderState {
|
|||||||
|
|
||||||
let paint = skia::Paint::default();
|
let paint = skia::Paint::default();
|
||||||
|
|
||||||
|
self.surfaces
|
||||||
|
.draw_into(SurfaceId::TextDropShadows, SurfaceId::Current, Some(&paint));
|
||||||
|
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.draw_into(SurfaceId::Fills, SurfaceId::Current, Some(&paint));
|
.draw_into(SurfaceId::Fills, SurfaceId::Current, Some(&paint));
|
||||||
|
|
||||||
@@ -457,8 +460,10 @@ impl RenderState {
|
|||||||
.draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint));
|
.draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint));
|
||||||
}
|
}
|
||||||
|
|
||||||
let surface_ids =
|
let surface_ids = SurfaceId::Strokes as u32
|
||||||
SurfaceId::Strokes as u32 | SurfaceId::Fills as u32 | SurfaceId::InnerShadows as u32;
|
| SurfaceId::Fills as u32
|
||||||
|
| SurfaceId::InnerShadows as u32
|
||||||
|
| SurfaceId::TextDropShadows as u32;
|
||||||
|
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas().clear(skia::Color::TRANSPARENT);
|
s.canvas().clear(skia::Color::TRANSPARENT);
|
||||||
@@ -485,8 +490,10 @@ impl RenderState {
|
|||||||
fills_surface_id: SurfaceId,
|
fills_surface_id: SurfaceId,
|
||||||
strokes_surface_id: SurfaceId,
|
strokes_surface_id: SurfaceId,
|
||||||
innershadows_surface_id: SurfaceId,
|
innershadows_surface_id: SurfaceId,
|
||||||
|
text_drop_shadows_surface_id: SurfaceId,
|
||||||
apply_to_current_surface: bool,
|
apply_to_current_surface: bool,
|
||||||
offset: Option<(f32, f32)>,
|
offset: Option<(f32, f32)>,
|
||||||
|
parent_shadows: Option<Vec<skia_safe::Paint>>,
|
||||||
) {
|
) {
|
||||||
let shape = if let Some(scale_content) = scale_content {
|
let shape = if let Some(scale_content) = scale_content {
|
||||||
&shape.scale_content(*scale_content)
|
&shape.scale_content(*scale_content)
|
||||||
@@ -494,8 +501,10 @@ impl RenderState {
|
|||||||
shape
|
shape
|
||||||
};
|
};
|
||||||
|
|
||||||
let surface_ids =
|
let surface_ids = fills_surface_id as u32
|
||||||
fills_surface_id as u32 | strokes_surface_id as u32 | innershadows_surface_id as u32;
|
| strokes_surface_id as u32
|
||||||
|
| innershadows_surface_id as u32
|
||||||
|
| text_drop_shadows_surface_id as u32;
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas().save();
|
s.canvas().save();
|
||||||
});
|
});
|
||||||
@@ -604,72 +613,139 @@ impl RenderState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let text_content = text_content.new_bounds(shape.selrect());
|
let text_content = text_content.new_bounds(shape.selrect());
|
||||||
|
let drop_shadows = shape.drop_shadow_paints();
|
||||||
let inner_shadows = shape.inner_shadow_paints();
|
let inner_shadows = shape.inner_shadow_paints();
|
||||||
let blur_filter = shape.image_filter(1.);
|
let blur_filter = shape.image_filter(1.);
|
||||||
let blur_mask = shape.mask_filter(1.);
|
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(
|
|
||||||
&text_content,
|
|
||||||
blur_filter.as_ref(),
|
|
||||||
blur_mask.as_ref(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let count_inner_strokes = shape.count_visible_inner_strokes();
|
let count_inner_strokes = shape.count_visible_inner_strokes();
|
||||||
text::render(self, &shape, &mut paragraphs, Some(fills_surface_id));
|
let mut paragraphs = paragraph_builder_group_from_text(&text_content, None);
|
||||||
for stroke in shape.visible_strokes().rev() {
|
let mut paragraphs_with_shadows =
|
||||||
let mut stroke_paragraphs = stroke_paragraph_builder_group_from_text(
|
paragraph_builder_group_from_text(&text_content, Some(true));
|
||||||
&text_content,
|
let mut stroke_paragraphs_list = shape
|
||||||
stroke,
|
.visible_strokes()
|
||||||
&shape.selrect(),
|
.map(|stroke| {
|
||||||
blur_filter.as_ref(),
|
stroke_paragraph_builder_group_from_text(
|
||||||
blur_mask.as_ref(),
|
&text_content,
|
||||||
None,
|
stroke,
|
||||||
count_inner_strokes,
|
&shape.selrect(),
|
||||||
);
|
count_inner_strokes,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
strokes::render(
|
let mut stroke_paragraphs_with_shadows_list = shape
|
||||||
self,
|
.visible_strokes()
|
||||||
&shape,
|
.map(|stroke| {
|
||||||
stroke,
|
stroke_paragraph_builder_group_from_text(
|
||||||
Some(strokes_surface_id),
|
&text_content,
|
||||||
None,
|
stroke,
|
||||||
Some(&mut stroke_paragraphs),
|
&shape.selrect(),
|
||||||
antialias,
|
count_inner_strokes,
|
||||||
);
|
Some(true),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for inner_shadow in &inner_shadows {
|
if let Some(parent_shadows) = parent_shadows {
|
||||||
let mut stroke_paragraphs_with_inner_shadows =
|
if !shape.has_visible_strokes() {
|
||||||
stroke_paragraph_builder_group_from_text(
|
for shadow in &parent_shadows {
|
||||||
&text_content,
|
text::render(
|
||||||
stroke,
|
Some(self),
|
||||||
&shape.selrect(),
|
None,
|
||||||
|
&shape,
|
||||||
|
&mut paragraphs_with_shadows,
|
||||||
|
text_drop_shadows_surface_id.into(),
|
||||||
|
Some(shadow),
|
||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
blur_mask.as_ref(),
|
|
||||||
Some(inner_shadow),
|
|
||||||
count_inner_strokes,
|
|
||||||
);
|
);
|
||||||
shadows::render_text_inner_shadows(
|
}
|
||||||
|
} else {
|
||||||
|
shadows::render_text_shadows(
|
||||||
self,
|
self,
|
||||||
&shape,
|
&shape,
|
||||||
&mut stroke_paragraphs_with_inner_shadows,
|
&mut paragraphs_with_shadows,
|
||||||
innershadows_surface_id,
|
&mut stroke_paragraphs_with_shadows_list,
|
||||||
|
text_drop_shadows_surface_id.into(),
|
||||||
|
&parent_shadows,
|
||||||
|
&blur_filter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// 1. Text drop shadows
|
||||||
|
if !shape.has_visible_strokes() {
|
||||||
|
for shadow in &drop_shadows {
|
||||||
|
text::render(
|
||||||
|
Some(self),
|
||||||
|
None,
|
||||||
|
&shape,
|
||||||
|
&mut paragraphs_with_shadows,
|
||||||
|
text_drop_shadows_surface_id.into(),
|
||||||
|
Some(shadow),
|
||||||
|
blur_filter.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for inner_shadow in &inner_shadows {
|
// 2. Text fills
|
||||||
let mut paragraphs_with_inner_shadows = paragraph_builder_group_from_text(
|
text::render(
|
||||||
&text_content,
|
Some(self),
|
||||||
|
None,
|
||||||
|
&shape,
|
||||||
|
&mut paragraphs,
|
||||||
|
Some(fills_surface_id),
|
||||||
|
None,
|
||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
blur_mask.as_ref(),
|
|
||||||
Some(inner_shadow),
|
|
||||||
);
|
);
|
||||||
shadows::render_text_inner_shadows(
|
|
||||||
|
// 3. Stroke drop shadows
|
||||||
|
shadows::render_text_shadows(
|
||||||
self,
|
self,
|
||||||
&shape,
|
&shape,
|
||||||
&mut paragraphs_with_inner_shadows,
|
&mut paragraphs_with_shadows,
|
||||||
innershadows_surface_id,
|
&mut stroke_paragraphs_with_shadows_list,
|
||||||
|
text_drop_shadows_surface_id.into(),
|
||||||
|
&drop_shadows,
|
||||||
|
&blur_filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 4. Stroke fills
|
||||||
|
for stroke_paragraphs in stroke_paragraphs_list.iter_mut() {
|
||||||
|
text::render(
|
||||||
|
Some(self),
|
||||||
|
None,
|
||||||
|
&shape,
|
||||||
|
stroke_paragraphs,
|
||||||
|
Some(strokes_surface_id),
|
||||||
|
None,
|
||||||
|
blur_filter.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Stroke inner shadows
|
||||||
|
shadows::render_text_shadows(
|
||||||
|
self,
|
||||||
|
&shape,
|
||||||
|
&mut paragraphs_with_shadows,
|
||||||
|
&mut stroke_paragraphs_with_shadows_list,
|
||||||
|
Some(innershadows_surface_id),
|
||||||
|
&inner_shadows,
|
||||||
|
&blur_filter,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Fill Inner shadows
|
||||||
|
if !shape.has_visible_strokes() {
|
||||||
|
for shadow in &inner_shadows {
|
||||||
|
text::render(
|
||||||
|
Some(self),
|
||||||
|
None,
|
||||||
|
&shape,
|
||||||
|
&mut paragraphs_with_shadows,
|
||||||
|
Some(innershadows_surface_id),
|
||||||
|
Some(shadow),
|
||||||
|
blur_filter.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -718,7 +794,6 @@ impl RenderState {
|
|||||||
stroke,
|
stroke,
|
||||||
Some(strokes_surface_id),
|
Some(strokes_surface_id),
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
antialias,
|
antialias,
|
||||||
);
|
);
|
||||||
shadows::render_stroke_inner_shadows(
|
shadows::render_stroke_inner_shadows(
|
||||||
@@ -815,8 +890,10 @@ impl RenderState {
|
|||||||
performance::begin_measure!("start_render_loop");
|
performance::begin_measure!("start_render_loop");
|
||||||
|
|
||||||
self.reset_canvas();
|
self.reset_canvas();
|
||||||
let surface_ids =
|
let surface_ids = SurfaceId::Strokes as u32
|
||||||
SurfaceId::Strokes as u32 | SurfaceId::Fills as u32 | SurfaceId::InnerShadows as u32;
|
| SurfaceId::Fills as u32
|
||||||
|
| SurfaceId::InnerShadows as u32
|
||||||
|
| SurfaceId::TextDropShadows as u32;
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas().scale((scale, scale));
|
s.canvas().scale((scale, scale));
|
||||||
});
|
});
|
||||||
@@ -997,8 +1074,10 @@ impl RenderState {
|
|||||||
SurfaceId::Fills,
|
SurfaceId::Fills,
|
||||||
SurfaceId::Strokes,
|
SurfaceId::Strokes,
|
||||||
SurfaceId::InnerShadows,
|
SurfaceId::InnerShadows,
|
||||||
|
SurfaceId::TextDropShadows,
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1098,7 +1177,6 @@ impl RenderState {
|
|||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::DropShadows)
|
.canvas(SurfaceId::DropShadows)
|
||||||
.save_layer(&layer_rec);
|
.save_layer(&layer_rec);
|
||||||
|
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::DropShadows)
|
.canvas(SurfaceId::DropShadows)
|
||||||
.scale((scale, scale));
|
.scale((scale, scale));
|
||||||
@@ -1116,8 +1194,10 @@ impl RenderState {
|
|||||||
SurfaceId::DropShadows,
|
SurfaceId::DropShadows,
|
||||||
SurfaceId::DropShadows,
|
SurfaceId::DropShadows,
|
||||||
SurfaceId::DropShadows,
|
SurfaceId::DropShadows,
|
||||||
|
SurfaceId::DropShadows,
|
||||||
false,
|
false,
|
||||||
Some((shadow.offset.0, shadow.offset.1)),
|
Some((shadow.offset.0, shadow.offset.1)),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
@@ -1133,6 +1213,7 @@ impl RenderState {
|
|||||||
) -> Result<(bool, bool), String> {
|
) -> Result<(bool, bool), String> {
|
||||||
let mut iteration = 0;
|
let mut iteration = 0;
|
||||||
let mut is_empty = true;
|
let mut is_empty = true;
|
||||||
|
|
||||||
while let Some(node_render_state) = self.pending_nodes.pop() {
|
while let Some(node_render_state) = self.pending_nodes.pop() {
|
||||||
let NodeRenderState {
|
let NodeRenderState {
|
||||||
id: node_id,
|
id: node_id,
|
||||||
@@ -1199,80 +1280,128 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.render_shape_enter(element, mask);
|
self.render_shape_enter(element, mask);
|
||||||
|
|
||||||
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
||||||
let scale = self.get_scale();
|
let scale: f32 = self.get_scale();
|
||||||
let translation = self
|
let translation = self
|
||||||
.surfaces
|
.surfaces
|
||||||
.get_render_context_translation(self.render_area, scale);
|
.get_render_context_translation(self.render_area, scale);
|
||||||
|
|
||||||
// Shadow rendering technique: Two-pass approach for proper opacity handling
|
// For text shapes, render drop shadow using text rendering logic
|
||||||
//
|
if !matches!(element.shape_type, Type::Text(_)) {
|
||||||
// The shadow rendering uses a two-pass technique to ensure that overlapping
|
// Shadow rendering technique: Two-pass approach for proper opacity handling
|
||||||
// shadow areas maintain correct opacity without unwanted darkening:
|
//
|
||||||
//
|
// The shadow rendering uses a two-pass technique to ensure that overlapping
|
||||||
// 1. First pass: Render shadow shape in pure black (alpha channel preserved)
|
// shadow areas maintain correct opacity without unwanted darkening:
|
||||||
// - This creates the shadow silhouette with proper alpha gradients
|
//
|
||||||
// - The black color acts as a mask for the final shadow color
|
// 1. First pass: Render shadow shape in pure black (alpha channel preserved)
|
||||||
//
|
// - This creates the shadow silhouette with proper alpha gradients
|
||||||
// 2. Second pass: Apply actual shadow color using SrcIn blend mode
|
// - The black color acts as a mask for the final shadow color
|
||||||
// - SrcIn preserves the alpha channel from the black shadow
|
//
|
||||||
// - Only the color channels are replaced, maintaining transparency
|
// 2. Second pass: Apply actual shadow color using SrcIn blend mode
|
||||||
// - This prevents overlapping shadows from accumulating opacity
|
// - SrcIn preserves the alpha channel from the black shadow
|
||||||
//
|
// - Only the color channels are replaced, maintaining transparency
|
||||||
// This approach is essential for complex shapes with transparency where
|
// - This prevents overlapping shadows from accumulating opacity
|
||||||
// multiple shadow areas might overlap, ensuring visual consistency.
|
//
|
||||||
for shadow in element.drop_shadows().rev().filter(|s| !s.hidden()) {
|
// This approach is essential for complex shapes with transparency where
|
||||||
let paint = skia::Paint::default();
|
// multiple shadow areas might overlap, ensuring visual consistency.
|
||||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
for shadow in element.drop_shadows_visible() {
|
||||||
self.surfaces
|
let paint = skia::Paint::default();
|
||||||
.canvas(SurfaceId::DropShadows)
|
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||||
.save_layer(&layer_rec);
|
|
||||||
|
|
||||||
// First pass: Render shadow in black to establish alpha mask
|
self.surfaces
|
||||||
self.render_drop_black_shadow(
|
.canvas(SurfaceId::DropShadows)
|
||||||
tree,
|
.save_layer(&layer_rec);
|
||||||
modifiers,
|
|
||||||
structure,
|
|
||||||
element,
|
|
||||||
shadow,
|
|
||||||
scale_content.get(&element.id),
|
|
||||||
clip_bounds,
|
|
||||||
scale,
|
|
||||||
translation,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Nested shapes shadowing - apply black shadow to child shapes too
|
// First pass: Render shadow in black to establish alpha mask
|
||||||
for shadow_shape_id in element.children.iter() {
|
|
||||||
let shadow_shape = tree.get(shadow_shape_id).unwrap();
|
|
||||||
let clip_bounds = node_render_state.get_shadow_clip_bounds(
|
|
||||||
element,
|
|
||||||
modifiers.get(&element.id),
|
|
||||||
shadow,
|
|
||||||
);
|
|
||||||
self.render_drop_black_shadow(
|
self.render_drop_black_shadow(
|
||||||
tree,
|
tree,
|
||||||
modifiers,
|
modifiers,
|
||||||
structure,
|
structure,
|
||||||
shadow_shape,
|
element,
|
||||||
shadow,
|
shadow,
|
||||||
scale_content.get(&element.id),
|
scale_content.get(&element.id),
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
scale,
|
scale,
|
||||||
translation,
|
translation,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Nested shapes shadowing - apply black shadow to child shapes too
|
||||||
|
for shadow_shape_id in element.children.iter() {
|
||||||
|
let shadow_shape = tree.get(shadow_shape_id).unwrap();
|
||||||
|
let clip_bounds = node_render_state.get_shadow_clip_bounds(
|
||||||
|
element,
|
||||||
|
modifiers.get(&element.id),
|
||||||
|
shadow,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
|
||||||
|
self.render_drop_black_shadow(
|
||||||
|
tree,
|
||||||
|
modifiers,
|
||||||
|
structure,
|
||||||
|
shadow_shape,
|
||||||
|
shadow,
|
||||||
|
scale_content.get(&element.id),
|
||||||
|
clip_bounds,
|
||||||
|
scale,
|
||||||
|
translation,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let paint = skia::Paint::default();
|
||||||
|
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||||
|
|
||||||
|
self.surfaces
|
||||||
|
.canvas(SurfaceId::DropShadows)
|
||||||
|
.save_layer(&layer_rec);
|
||||||
|
self.surfaces
|
||||||
|
.canvas(SurfaceId::DropShadows)
|
||||||
|
.scale((scale, scale));
|
||||||
|
self.surfaces
|
||||||
|
.canvas(SurfaceId::DropShadows)
|
||||||
|
.translate(translation);
|
||||||
|
|
||||||
|
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
|
||||||
|
// transformed_shadow.to_mut().offset = (0., 0.);
|
||||||
|
transformed_shadow.to_mut().color = skia::Color::BLACK;
|
||||||
|
transformed_shadow.to_mut().blur = transformed_shadow.blur * scale;
|
||||||
|
|
||||||
|
let mut new_shadow_paint = skia::Paint::default();
|
||||||
|
new_shadow_paint
|
||||||
|
.set_image_filter(transformed_shadow.get_drop_shadow_filter());
|
||||||
|
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
||||||
|
|
||||||
|
self.render_shape(
|
||||||
|
tree,
|
||||||
|
modifiers,
|
||||||
|
structure,
|
||||||
|
shadow_shape,
|
||||||
|
scale_content.get(&element.id),
|
||||||
|
clip_bounds,
|
||||||
|
SurfaceId::DropShadows,
|
||||||
|
SurfaceId::DropShadows,
|
||||||
|
SurfaceId::DropShadows,
|
||||||
|
SurfaceId::DropShadows,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
Some(vec![new_shadow_paint.clone()]),
|
||||||
|
);
|
||||||
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: Apply actual shadow color using SrcIn blend mode
|
||||||
|
// This preserves the alpha channel from the black shadow while
|
||||||
|
// replacing only the color channels, preventing opacity accumulation
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_color(shadow.color);
|
||||||
|
paint.set_blend_mode(skia::BlendMode::SrcIn);
|
||||||
|
self.surfaces
|
||||||
|
.canvas(SurfaceId::DropShadows)
|
||||||
|
.draw_paint(&paint);
|
||||||
|
|
||||||
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: Apply actual shadow color using SrcIn blend mode
|
|
||||||
// This preserves the alpha channel from the black shadow while
|
|
||||||
// replacing only the color channels, preventing opacity accumulation
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_color(shadow.color);
|
|
||||||
paint.set_blend_mode(skia::BlendMode::SrcIn);
|
|
||||||
self.surfaces
|
|
||||||
.canvas(SurfaceId::DropShadows)
|
|
||||||
.draw_paint(&paint);
|
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.surfaces
|
self.surfaces
|
||||||
@@ -1292,8 +1421,10 @@ impl RenderState {
|
|||||||
SurfaceId::Fills,
|
SurfaceId::Fills,
|
||||||
SurfaceId::Strokes,
|
SurfaceId::Strokes,
|
||||||
SurfaceId::InnerShadows,
|
SurfaceId::InnerShadows,
|
||||||
|
SurfaceId::TextDropShadows,
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.surfaces
|
self.surfaces
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use super::{RenderState, SurfaceId};
|
use super::{RenderState, SurfaceId};
|
||||||
use crate::render::strokes;
|
use crate::render::strokes;
|
||||||
use crate::render::text::{self};
|
|
||||||
use crate::shapes::{Shadow, Shape, Stroke, Type};
|
use crate::shapes::{Shadow, Shape, Stroke, Type};
|
||||||
use skia_safe::textlayout::ParagraphBuilder;
|
use skia_safe::{canvas::SaveLayerRec, Paint, Path};
|
||||||
use skia_safe::{Paint, Path};
|
|
||||||
|
use crate::render::text;
|
||||||
|
use crate::textlayout::ParagraphBuilderGroup;
|
||||||
|
|
||||||
// Fill Shadows
|
// Fill Shadows
|
||||||
pub fn render_fill_inner_shadows(
|
pub fn render_fill_inner_shadows(
|
||||||
@@ -46,7 +47,6 @@ pub fn render_stroke_inner_shadows(
|
|||||||
stroke,
|
stroke,
|
||||||
Some(surface_id),
|
Some(surface_id),
|
||||||
filter.as_ref(),
|
filter.as_ref(),
|
||||||
None,
|
|
||||||
antialias,
|
antialias,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -76,15 +76,6 @@ pub fn render_text_path_stroke_drop_shadows(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_text_inner_shadows(
|
|
||||||
render_state: &mut RenderState,
|
|
||||||
shape: &Shape,
|
|
||||||
paragraphs: &mut [Vec<ParagraphBuilder>],
|
|
||||||
surface_id: SurfaceId,
|
|
||||||
) {
|
|
||||||
text::render(render_state, shape, paragraphs, Some(surface_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render text paths (unused)
|
// Render text paths (unused)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn render_text_path_stroke_inner_shadows(
|
pub fn render_text_path_stroke_inner_shadows(
|
||||||
@@ -129,3 +120,50 @@ fn render_shadow_paint(
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_text_shadows(
|
||||||
|
render_state: &mut RenderState,
|
||||||
|
shape: &Shape,
|
||||||
|
paragraphs: &mut [ParagraphBuilderGroup],
|
||||||
|
stroke_paragraphs_group: &mut [Vec<ParagraphBuilderGroup>],
|
||||||
|
surface_id: Option<SurfaceId>,
|
||||||
|
shadows: &[Paint],
|
||||||
|
blur_filter: &Option<skia_safe::ImageFilter>,
|
||||||
|
) {
|
||||||
|
if stroke_paragraphs_group.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canvas = render_state
|
||||||
|
.surfaces
|
||||||
|
.canvas(surface_id.unwrap_or(SurfaceId::TextDropShadows));
|
||||||
|
|
||||||
|
for shadow in shadows {
|
||||||
|
let shadow_layer = SaveLayerRec::default().paint(shadow);
|
||||||
|
canvas.save_layer(&shadow_layer);
|
||||||
|
|
||||||
|
text::render(
|
||||||
|
None,
|
||||||
|
Some(canvas),
|
||||||
|
shape,
|
||||||
|
paragraphs,
|
||||||
|
surface_id,
|
||||||
|
None,
|
||||||
|
blur_filter.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for stroke_paragraphs in stroke_paragraphs_group.iter_mut() {
|
||||||
|
text::render(
|
||||||
|
None,
|
||||||
|
Some(canvas),
|
||||||
|
shape,
|
||||||
|
stroke_paragraphs,
|
||||||
|
surface_id,
|
||||||
|
None,
|
||||||
|
blur_filter.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ use std::collections::HashMap;
|
|||||||
use crate::math::{Matrix, Point, Rect};
|
use crate::math::{Matrix, Point, Rect};
|
||||||
|
|
||||||
use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type};
|
use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type};
|
||||||
use skia_safe::{self as skia, textlayout::ParagraphBuilder, ImageFilter, RRect};
|
use skia_safe::{self as skia, ImageFilter, RRect};
|
||||||
|
|
||||||
use super::{RenderState, SurfaceId};
|
use super::{RenderState, SurfaceId};
|
||||||
use crate::render::filters::compose_filters;
|
use crate::render::filters::compose_filters;
|
||||||
use crate::render::text::{self};
|
|
||||||
use crate::render::{get_dest_rect, get_source_rect};
|
use crate::render::{get_dest_rect, get_source_rect};
|
||||||
|
|
||||||
// FIXME: See if we can simplify these arguments
|
// FIXME: See if we can simplify these arguments
|
||||||
@@ -519,7 +518,6 @@ pub fn render(
|
|||||||
stroke: &Stroke,
|
stroke: &Stroke,
|
||||||
surface_id: Option<SurfaceId>,
|
surface_id: Option<SurfaceId>,
|
||||||
shadow: Option<&ImageFilter>,
|
shadow: Option<&ImageFilter>,
|
||||||
paragraphs: Option<&mut Vec<Vec<ParagraphBuilder>>>,
|
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
) {
|
) {
|
||||||
let scale = render_state.get_scale();
|
let scale = render_state.get_scale();
|
||||||
@@ -564,14 +562,7 @@ pub fn render(
|
|||||||
shape.image_filter(1.).as_ref(),
|
shape.image_filter(1.).as_ref(),
|
||||||
antialias,
|
antialias,
|
||||||
),
|
),
|
||||||
Type::Text(_) => {
|
Type::Text(_) => {}
|
||||||
text::render(
|
|
||||||
render_state,
|
|
||||||
shape,
|
|
||||||
paragraphs.expect("Text shapes should have paragraphs"),
|
|
||||||
surface_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
||||||
if let Some(path) = shape_type.path() {
|
if let Some(path) = shape_type.path() {
|
||||||
draw_stroke_on_path(
|
draw_stroke_on_path(
|
||||||
|
|||||||
@@ -17,15 +17,16 @@ const TILE_SIZE_MULTIPLIER: i32 = 2;
|
|||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum SurfaceId {
|
pub enum SurfaceId {
|
||||||
Target = 0b0_0000_0001,
|
Target = 0b00_0000_0001,
|
||||||
Cache = 0b0_0000_0010,
|
Cache = 0b00_0000_0010,
|
||||||
Current = 0b0_0000_0100,
|
Current = 0b00_0000_0100,
|
||||||
Fills = 0b0_0000_1000,
|
Fills = 0b00_0000_1000,
|
||||||
Strokes = 0b0_0001_0000,
|
Strokes = 0b00_0001_0000,
|
||||||
DropShadows = 0b0_0010_0000,
|
DropShadows = 0b00_0010_0000,
|
||||||
InnerShadows = 0b0_0100_0000,
|
InnerShadows = 0b00_0100_0000,
|
||||||
UI = 0b0_1000_0000,
|
TextDropShadows = 0b00_1000_0000,
|
||||||
Debug = 0b1_0000_0000,
|
UI = 0b01_0000_0000,
|
||||||
|
Debug = 0b10_0000_0001,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Surfaces {
|
pub struct Surfaces {
|
||||||
@@ -42,6 +43,8 @@ pub struct Surfaces {
|
|||||||
drop_shadows: skia::Surface,
|
drop_shadows: skia::Surface,
|
||||||
// used for rendering over shadows.
|
// used for rendering over shadows.
|
||||||
inner_shadows: skia::Surface,
|
inner_shadows: skia::Surface,
|
||||||
|
// used for rendering text drop shadows
|
||||||
|
text_drop_shadows: skia::Surface,
|
||||||
// used for displaying auxiliary workspace elements
|
// used for displaying auxiliary workspace elements
|
||||||
ui: skia::Surface,
|
ui: skia::Surface,
|
||||||
// for drawing debug info.
|
// for drawing debug info.
|
||||||
@@ -73,6 +76,8 @@ impl Surfaces {
|
|||||||
gpu_state.create_surface_with_isize("drop_shadows".to_string(), extra_tile_dims);
|
gpu_state.create_surface_with_isize("drop_shadows".to_string(), extra_tile_dims);
|
||||||
let inner_shadows =
|
let inner_shadows =
|
||||||
gpu_state.create_surface_with_isize("inner_shadows".to_string(), extra_tile_dims);
|
gpu_state.create_surface_with_isize("inner_shadows".to_string(), extra_tile_dims);
|
||||||
|
let text_drop_shadows =
|
||||||
|
gpu_state.create_surface_with_isize("text_drop_shadows".to_string(), extra_tile_dims);
|
||||||
let shape_fills =
|
let shape_fills =
|
||||||
gpu_state.create_surface_with_isize("shape_fills".to_string(), extra_tile_dims);
|
gpu_state.create_surface_with_isize("shape_fills".to_string(), extra_tile_dims);
|
||||||
let shape_strokes =
|
let shape_strokes =
|
||||||
@@ -88,6 +93,7 @@ impl Surfaces {
|
|||||||
current,
|
current,
|
||||||
drop_shadows,
|
drop_shadows,
|
||||||
inner_shadows,
|
inner_shadows,
|
||||||
|
text_drop_shadows,
|
||||||
shape_fills,
|
shape_fills,
|
||||||
shape_strokes,
|
shape_strokes,
|
||||||
ui,
|
ui,
|
||||||
@@ -166,6 +172,9 @@ impl Surfaces {
|
|||||||
if ids & SurfaceId::InnerShadows as u32 != 0 {
|
if ids & SurfaceId::InnerShadows as u32 != 0 {
|
||||||
f(self.get_mut(SurfaceId::InnerShadows));
|
f(self.get_mut(SurfaceId::InnerShadows));
|
||||||
}
|
}
|
||||||
|
if ids & SurfaceId::TextDropShadows as u32 != 0 {
|
||||||
|
f(self.get_mut(SurfaceId::TextDropShadows));
|
||||||
|
}
|
||||||
if ids & SurfaceId::DropShadows as u32 != 0 {
|
if ids & SurfaceId::DropShadows as u32 != 0 {
|
||||||
f(self.get_mut(SurfaceId::DropShadows));
|
f(self.get_mut(SurfaceId::DropShadows));
|
||||||
}
|
}
|
||||||
@@ -189,11 +198,15 @@ impl Surfaces {
|
|||||||
pub fn update_render_context(&mut self, render_area: skia::Rect, scale: f32) {
|
pub fn update_render_context(&mut self, render_area: skia::Rect, scale: f32) {
|
||||||
let translation = self.get_render_context_translation(render_area, scale);
|
let translation = self.get_render_context_translation(render_area, scale);
|
||||||
self.apply_mut(
|
self.apply_mut(
|
||||||
SurfaceId::Fills as u32 | SurfaceId::Strokes as u32 | SurfaceId::InnerShadows as u32,
|
SurfaceId::Fills as u32
|
||||||
|
| SurfaceId::Strokes as u32
|
||||||
|
| SurfaceId::InnerShadows as u32
|
||||||
|
| SurfaceId::TextDropShadows as u32,
|
||||||
|s| {
|
|s| {
|
||||||
s.canvas().restore();
|
let canvas = s.canvas();
|
||||||
s.canvas().save();
|
canvas.reset_matrix();
|
||||||
s.canvas().translate(translation);
|
canvas.scale((scale, scale));
|
||||||
|
canvas.translate(translation);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -206,6 +219,7 @@ impl Surfaces {
|
|||||||
SurfaceId::Current => &mut self.current,
|
SurfaceId::Current => &mut self.current,
|
||||||
SurfaceId::DropShadows => &mut self.drop_shadows,
|
SurfaceId::DropShadows => &mut self.drop_shadows,
|
||||||
SurfaceId::InnerShadows => &mut self.inner_shadows,
|
SurfaceId::InnerShadows => &mut self.inner_shadows,
|
||||||
|
SurfaceId::TextDropShadows => &mut self.text_drop_shadows,
|
||||||
SurfaceId::Fills => &mut self.shape_fills,
|
SurfaceId::Fills => &mut self.shape_fills,
|
||||||
SurfaceId::Strokes => &mut self.shape_strokes,
|
SurfaceId::Strokes => &mut self.shape_strokes,
|
||||||
SurfaceId::Debug => &mut self.debug,
|
SurfaceId::Debug => &mut self.debug,
|
||||||
@@ -257,13 +271,15 @@ impl Surfaces {
|
|||||||
pub fn reset(&mut self, color: skia::Color) {
|
pub fn reset(&mut self, color: skia::Color) {
|
||||||
self.canvas(SurfaceId::Fills).restore_to_count(1);
|
self.canvas(SurfaceId::Fills).restore_to_count(1);
|
||||||
self.canvas(SurfaceId::InnerShadows).restore_to_count(1);
|
self.canvas(SurfaceId::InnerShadows).restore_to_count(1);
|
||||||
|
self.canvas(SurfaceId::TextDropShadows).restore_to_count(1);
|
||||||
self.canvas(SurfaceId::Strokes).restore_to_count(1);
|
self.canvas(SurfaceId::Strokes).restore_to_count(1);
|
||||||
self.canvas(SurfaceId::Current).restore_to_count(1);
|
self.canvas(SurfaceId::Current).restore_to_count(1);
|
||||||
self.apply_mut(
|
self.apply_mut(
|
||||||
SurfaceId::Fills as u32
|
SurfaceId::Fills as u32
|
||||||
| SurfaceId::Strokes as u32
|
| SurfaceId::Strokes as u32
|
||||||
| SurfaceId::Current as u32
|
| SurfaceId::Current as u32
|
||||||
| SurfaceId::InnerShadows as u32,
|
| SurfaceId::InnerShadows as u32
|
||||||
|
| SurfaceId::TextDropShadows as u32,
|
||||||
|s| {
|
|s| {
|
||||||
s.canvas().clear(color).reset_matrix();
|
s.canvas().clear(color).reset_matrix();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,22 +1,55 @@
|
|||||||
use super::{RenderState, Shape, SurfaceId};
|
use super::{RenderState, Shape, SurfaceId};
|
||||||
use crate::shapes::VerticalAlign;
|
use crate::shapes::VerticalAlign;
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
canvas::SaveLayerRec, textlayout::LineMetrics, textlayout::Paragraph,
|
canvas::SaveLayerRec,
|
||||||
textlayout::ParagraphBuilder, textlayout::RectHeightStyle, textlayout::RectWidthStyle,
|
textlayout::{
|
||||||
textlayout::StyleMetrics, textlayout::TextDecoration, textlayout::TextStyle, Canvas, Paint,
|
LineMetrics, Paragraph, ParagraphBuilder, RectHeightStyle, RectWidthStyle, StyleMetrics,
|
||||||
Path,
|
TextDecoration, TextStyle,
|
||||||
|
},
|
||||||
|
Canvas, ImageFilter, Paint, Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(
|
||||||
render_state: &mut RenderState,
|
render_state: Option<&mut RenderState>,
|
||||||
|
canvas: Option<&Canvas>,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
paragraphs: &mut [Vec<ParagraphBuilder>],
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
surface_id: Option<SurfaceId>,
|
surface_id: Option<SurfaceId>,
|
||||||
|
shadow: Option<&Paint>,
|
||||||
|
blur: Option<&ImageFilter>,
|
||||||
) {
|
) {
|
||||||
let canvas = render_state
|
let render_canvas = if let Some(rs) = render_state {
|
||||||
.surfaces
|
rs.surfaces.canvas(surface_id.unwrap_or(SurfaceId::Fills))
|
||||||
.canvas(surface_id.unwrap_or(SurfaceId::Fills));
|
} else if let Some(c) = canvas {
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(blur_filter) = blur {
|
||||||
|
let mut blur_paint = Paint::default();
|
||||||
|
blur_paint.set_image_filter(blur_filter.clone());
|
||||||
|
let blur_layer = SaveLayerRec::default().paint(&blur_paint);
|
||||||
|
render_canvas.save_layer(&blur_layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(shadow_paint) = shadow {
|
||||||
|
let layer_rec = SaveLayerRec::default().paint(shadow_paint);
|
||||||
|
render_canvas.save_layer(&layer_rec);
|
||||||
|
draw_text(render_canvas, shape, paragraphs);
|
||||||
|
render_canvas.restore();
|
||||||
|
} else {
|
||||||
|
draw_text(render_canvas, shape, paragraphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if blur.is_some() {
|
||||||
|
render_canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
render_canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_text(canvas: &Canvas, shape: &Shape, paragraphs: &mut [Vec<ParagraphBuilder>]) {
|
||||||
// Width
|
// Width
|
||||||
let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type {
|
let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type {
|
||||||
text_content.width()
|
text_content.width()
|
||||||
@@ -63,8 +96,6 @@ pub fn render(
|
|||||||
global_offset_y = group_offset_y;
|
global_offset_y = group_offset_y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_text_decorations(
|
fn draw_text_decorations(
|
||||||
@@ -286,11 +317,11 @@ pub fn render_as_path(
|
|||||||
// let text_content = text_content.new_bounds(shape.selrect());
|
// let text_content = text_content.new_bounds(shape.selrect());
|
||||||
// let paths = text_content.get_paths(antialias);
|
// let paths = text_content.get_paths(antialias);
|
||||||
|
|
||||||
// shadows::render_text_drop_shadows(self, &shape, &paths, antialias);
|
// shadows::render_text_shadows(self, &shape, &paths, antialias);
|
||||||
// text::render(self, &paths, None, None);
|
// text::render(self, &paths, None, None);
|
||||||
|
|
||||||
// for stroke in shape.visible_strokes().rev() {
|
// for stroke in shape.visible_strokes().rev() {
|
||||||
// shadows::render_text_path_stroke_drop_shadows(
|
// shadows::render_text_path_stroke_shadows(
|
||||||
// self, &shape, &paths, stroke, antialias,
|
// self, &shape, &paths, stroke, antialias,
|
||||||
// );
|
// );
|
||||||
// strokes::render_text_paths(self, &shape, stroke, &paths, None, None, antialias);
|
// strokes::render_text_paths(self, &shape, stroke, &paths, None, None, antialias);
|
||||||
|
|||||||
@@ -573,6 +573,12 @@ impl Shape {
|
|||||||
.filter(|stroke| stroke.width > MIN_STROKE_WIDTH)
|
.filter(|stroke| stroke.width > MIN_STROKE_WIDTH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_visible_strokes(&self) -> bool {
|
||||||
|
self.strokes
|
||||||
|
.iter()
|
||||||
|
.any(|stroke| stroke.width > MIN_STROKE_WIDTH)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_stroke(&mut self, s: Stroke) {
|
pub fn add_stroke(&mut self, s: Stroke) {
|
||||||
self.invalidate_extrect();
|
self.invalidate_extrect();
|
||||||
self.strokes.push(s)
|
self.strokes.push(s)
|
||||||
@@ -984,6 +990,7 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn mask_filter(&self, scale: f32) -> Option<skia::MaskFilter> {
|
pub fn mask_filter(&self, scale: f32) -> Option<skia::MaskFilter> {
|
||||||
if !self.blur.hidden {
|
if !self.blur.hidden {
|
||||||
match self.blur.blur_type {
|
match self.blur.blur_type {
|
||||||
@@ -1022,6 +1029,12 @@ impl Shape {
|
|||||||
.filter(|shadow| shadow.style() == ShadowStyle::Drop)
|
.filter(|shadow| shadow.style() == ShadowStyle::Drop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn drop_shadows_visible(&self) -> impl DoubleEndedIterator<Item = &Shadow> {
|
||||||
|
self.shadows
|
||||||
|
.iter()
|
||||||
|
.filter(|shadow| shadow.style() == ShadowStyle::Drop && !shadow.hidden())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner_shadows(&self) -> impl DoubleEndedIterator<Item = &Shadow> {
|
pub fn inner_shadows(&self) -> impl DoubleEndedIterator<Item = &Shadow> {
|
||||||
self.shadows
|
self.shadows
|
||||||
.iter()
|
.iter()
|
||||||
@@ -1162,11 +1175,6 @@ impl Shape {
|
|||||||
!self.fills.is_empty()
|
!self.fills.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn has_visible_inner_strokes(&self) -> bool {
|
|
||||||
self.visible_strokes().any(|s| s.kind == StrokeKind::Inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn count_visible_inner_strokes(&self) -> usize {
|
pub fn count_visible_inner_strokes(&self) -> usize {
|
||||||
self.visible_strokes()
|
self.visible_strokes()
|
||||||
.filter(|s| s.kind == StrokeKind::Inner)
|
.filter(|s| s.kind == StrokeKind::Inner)
|
||||||
@@ -1210,6 +1218,20 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn drop_shadow_paints(&self) -> Vec<skia_safe::Paint> {
|
||||||
|
let drop_shadows: Vec<&crate::shapes::shadows::Shadow> =
|
||||||
|
self.drop_shadows().filter(|s| !s.hidden()).collect();
|
||||||
|
drop_shadows
|
||||||
|
.into_iter()
|
||||||
|
.map(|shadow| {
|
||||||
|
let mut paint = skia_safe::Paint::default();
|
||||||
|
let filter = shadow.get_drop_shadow_filter();
|
||||||
|
paint.set_image_filter(filter);
|
||||||
|
paint
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner_shadow_paints(&self) -> Vec<skia_safe::Paint> {
|
pub fn inner_shadow_paints(&self) -> Vec<skia_safe::Paint> {
|
||||||
let inner_shadows: Vec<&crate::shapes::shadows::Shadow> =
|
let inner_shadows: Vec<&crate::shapes::shadows::Shadow> =
|
||||||
self.inner_shadows().filter(|s| !s.hidden()).collect();
|
self.inner_shadows().filter(|s| !s.hidden()).collect();
|
||||||
|
|||||||
@@ -255,7 +255,12 @@ pub fn merge_fills(fills: &[Fill], bounding_box: Rect) -> skia::Paint {
|
|||||||
fills_paint
|
fills_paint
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_paint_fill(paint: &mut Paint, fill: &Fill, bounding_box: &Rect) {
|
pub fn set_paint_fill(paint: &mut Paint, fill: &Fill, bounding_box: &Rect, remove_alpha: bool) {
|
||||||
|
if remove_alpha {
|
||||||
|
paint.set_color(skia::Color::BLACK);
|
||||||
|
paint.set_alpha(255);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let shader = get_fill_shader(fill, bounding_box);
|
let shader = get_fill_shader(fill, bounding_box);
|
||||||
if let Some(shader) = shader {
|
if let Some(shader) = shader {
|
||||||
paint.set_shader(shader);
|
paint.set_shader(shader);
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ fn propagate_transform(
|
|||||||
match content.grow_type() {
|
match content.grow_type() {
|
||||||
GrowType::AutoHeight => {
|
GrowType::AutoHeight => {
|
||||||
let paragraph_width = shape_bounds_after.width();
|
let paragraph_width = shape_bounds_after.width();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(content, None, None, None);
|
let mut paragraphs = paragraph_builder_group_from_text(content, None);
|
||||||
let height = auto_height(&mut paragraphs, paragraph_width);
|
let height = auto_height(&mut paragraphs, paragraph_width);
|
||||||
let resize_transform = math::resize_matrix(
|
let resize_transform = math::resize_matrix(
|
||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
@@ -213,7 +213,7 @@ fn propagate_transform(
|
|||||||
}
|
}
|
||||||
GrowType::AutoWidth => {
|
GrowType::AutoWidth => {
|
||||||
let paragraph_width = content.width();
|
let paragraph_width = content.width();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(content, None, None, None);
|
let mut paragraphs = paragraph_builder_group_from_text(content, None);
|
||||||
let height = auto_height(&mut paragraphs, paragraph_width);
|
let height = auto_height(&mut paragraphs, paragraph_width);
|
||||||
let resize_transform = math::resize_matrix(
|
let resize_transform = math::resize_matrix(
|
||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
|
|||||||
@@ -264,4 +264,11 @@ impl Stroke {
|
|||||||
|
|
||||||
paint
|
paint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_transparent(&self) -> bool {
|
||||||
|
match &self.fill {
|
||||||
|
Fill::Solid(SolidColor(color)) => color.a() == 0,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ use crate::{
|
|||||||
textlayout::paragraph_builder_group_from_text,
|
textlayout::paragraph_builder_group_from_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
use skia_safe::{self as skia, paint::Paint, textlayout::ParagraphStyle, ImageFilter, MaskFilter};
|
use skia_safe::{
|
||||||
|
self as skia,
|
||||||
|
paint::{self, Paint},
|
||||||
|
textlayout::ParagraphStyle,
|
||||||
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use super::FontFamily;
|
use super::FontFamily;
|
||||||
@@ -86,7 +90,7 @@ impl TextContent {
|
|||||||
|
|
||||||
pub fn width(&self) -> f32 {
|
pub fn width(&self) -> f32 {
|
||||||
if self.grow_type() == GrowType::AutoWidth {
|
if self.grow_type() == GrowType::AutoWidth {
|
||||||
let temp_paragraphs = paragraph_builder_group_from_text(self, None, None, None);
|
let temp_paragraphs = paragraph_builder_group_from_text(self, None);
|
||||||
let mut temp_paragraphs = temp_paragraphs;
|
let mut temp_paragraphs = temp_paragraphs;
|
||||||
auto_width(&mut temp_paragraphs, f32::MAX).ceil()
|
auto_width(&mut temp_paragraphs, f32::MAX).ceil()
|
||||||
} else {
|
} else {
|
||||||
@@ -104,7 +108,7 @@ impl TextContent {
|
|||||||
|
|
||||||
pub fn visual_bounds(&self) -> (f32, f32) {
|
pub fn visual_bounds(&self) -> (f32, f32) {
|
||||||
let paragraph_width = self.width();
|
let paragraph_width = self.width();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(self, None, None, None);
|
let mut paragraphs = paragraph_builder_group_from_text(self, None);
|
||||||
let paragraph_height = auto_height(&mut paragraphs, paragraph_width);
|
let paragraph_height = auto_height(&mut paragraphs, paragraph_width);
|
||||||
(paragraph_width, paragraph_height)
|
(paragraph_width, paragraph_height)
|
||||||
}
|
}
|
||||||
@@ -308,26 +312,20 @@ impl TextLeaf {
|
|||||||
&self,
|
&self,
|
||||||
content_bounds: &Rect,
|
content_bounds: &Rect,
|
||||||
fallback_fonts: &HashSet<String>,
|
fallback_fonts: &HashSet<String>,
|
||||||
_blur: Option<&ImageFilter>,
|
remove_alpha: bool,
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
shadow: Option<&Paint>,
|
|
||||||
) -> skia::textlayout::TextStyle {
|
) -> skia::textlayout::TextStyle {
|
||||||
let mut style = skia::textlayout::TextStyle::default();
|
let mut style = skia::textlayout::TextStyle::default();
|
||||||
|
|
||||||
if shadow.is_some() {
|
let mut paint = paint::Paint::default();
|
||||||
let paint = shadow.unwrap().clone();
|
|
||||||
style.set_foreground_paint(&paint);
|
if remove_alpha {
|
||||||
|
paint.set_color(skia::Color::BLACK);
|
||||||
|
paint.set_alpha(255);
|
||||||
} else {
|
} else {
|
||||||
let paint = merge_fills(&self.fills, *content_bounds);
|
paint = merge_fills(&self.fills, *content_bounds);
|
||||||
style.set_foreground_paint(&paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(blur_mask) = blur_mask {
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_mask_filter(blur_mask.clone());
|
|
||||||
style.set_foreground_paint(&paint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
style.set_foreground_paint(&paint);
|
||||||
style.set_font_size(self.font_size);
|
style.set_font_size(self.font_size);
|
||||||
style.set_letter_spacing(self.letter_spacing);
|
style.set_letter_spacing(self.letter_spacing);
|
||||||
style.set_half_leading(false);
|
style.set_half_leading(false);
|
||||||
@@ -359,12 +357,20 @@ impl TextLeaf {
|
|||||||
&self,
|
&self,
|
||||||
stroke_paint: &Paint,
|
stroke_paint: &Paint,
|
||||||
fallback_fonts: &HashSet<String>,
|
fallback_fonts: &HashSet<String>,
|
||||||
blur: Option<&ImageFilter>,
|
remove_alpha: bool,
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
shadow: Option<&Paint>,
|
|
||||||
) -> skia::textlayout::TextStyle {
|
) -> skia::textlayout::TextStyle {
|
||||||
let mut style = self.to_style(&Rect::default(), fallback_fonts, blur, blur_mask, shadow);
|
let mut style = self.to_style(&Rect::default(), fallback_fonts, remove_alpha);
|
||||||
style.set_foreground_paint(stroke_paint);
|
if remove_alpha {
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(stroke_paint.style());
|
||||||
|
paint.set_stroke_width(stroke_paint.stroke_width());
|
||||||
|
paint.set_color(skia::Color::BLACK);
|
||||||
|
paint.set_alpha(255);
|
||||||
|
style.set_foreground_paint(&paint);
|
||||||
|
} else {
|
||||||
|
style.set_foreground_paint(stroke_paint);
|
||||||
|
}
|
||||||
|
|
||||||
style.set_font_size(self.font_size);
|
style.set_font_size(self.font_size);
|
||||||
style.set_letter_spacing(self.letter_spacing);
|
style.set_letter_spacing(self.letter_spacing);
|
||||||
style.set_decoration_type(match self.text_decoration {
|
style.set_decoration_type(match self.text_decoration {
|
||||||
@@ -406,9 +412,6 @@ impl TextLeaf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_transparent(&self) -> bool {
|
pub fn is_transparent(&self) -> bool {
|
||||||
if self.fills.is_empty() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
self.fills.iter().all(|fill| match fill {
|
self.fills.iter().all(|fill| match fill {
|
||||||
shapes::Fill::Solid(shapes::SolidColor(color)) => color.a() == 0,
|
shapes::Fill::Solid(shapes::SolidColor(color)) => color.a() == 0,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ impl TextPaths {
|
|||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
|
|
||||||
let mut offset_y = self.bounds.y();
|
let mut offset_y = self.bounds.y();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(&self.0, None, None, None);
|
let mut paragraphs = paragraph_builder_group_from_text(&self.0, None);
|
||||||
|
|
||||||
for paragraphs in paragraphs.iter_mut() {
|
for paragraphs in paragraphs.iter_mut() {
|
||||||
for paragraph_builder in paragraphs.iter_mut() {
|
for paragraph_builder in paragraphs.iter_mut() {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use skia_safe::{self as skia, textlayout::ParagraphBuilder, ImageFilter, MaskFilter, Paint, Rect};
|
use skia_safe::{self as skia, textlayout::ParagraphBuilder, Paint, Rect};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render::filters::compose_filters,
|
|
||||||
shapes::{merge_fills, set_paint_fill, Stroke, StrokeKind, TextContent},
|
shapes::{merge_fills, set_paint_fill, Stroke, StrokeKind, TextContent},
|
||||||
utils::{get_fallback_fonts, get_font_collection},
|
utils::{get_fallback_fonts, get_font_collection},
|
||||||
};
|
};
|
||||||
@@ -51,13 +50,11 @@ pub fn build_paragraphs_with_width(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParagraphBuilderGroup = Vec<ParagraphBuilder>;
|
pub type ParagraphBuilderGroup = Vec<ParagraphBuilder>;
|
||||||
|
|
||||||
pub fn paragraph_builder_group_from_text(
|
pub fn paragraph_builder_group_from_text(
|
||||||
text_content: &TextContent,
|
text_content: &TextContent,
|
||||||
blur: Option<&ImageFilter>,
|
use_shadow: Option<bool>,
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
shadow: Option<&Paint>,
|
|
||||||
) -> Vec<ParagraphBuilderGroup> {
|
) -> Vec<ParagraphBuilderGroup> {
|
||||||
let fonts = get_font_collection();
|
let fonts = get_font_collection();
|
||||||
let fallback_fonts = get_fallback_fonts();
|
let fallback_fonts = get_fallback_fonts();
|
||||||
@@ -67,13 +64,8 @@ pub fn paragraph_builder_group_from_text(
|
|||||||
let paragraph_style = paragraph.paragraph_to_style();
|
let paragraph_style = paragraph.paragraph_to_style();
|
||||||
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
||||||
for leaf in paragraph.children() {
|
for leaf in paragraph.children() {
|
||||||
let text_style = leaf.to_style(
|
let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent();
|
||||||
&text_content.bounds(),
|
let text_style = leaf.to_style(&text_content.bounds(), fallback_fonts, remove_alpha);
|
||||||
fallback_fonts,
|
|
||||||
blur,
|
|
||||||
blur_mask,
|
|
||||||
shadow,
|
|
||||||
);
|
|
||||||
let text = leaf.apply_text_transform();
|
let text = leaf.apply_text_transform();
|
||||||
builder.push_style(&text_style);
|
builder.push_style(&text_style);
|
||||||
builder.add_text(&text);
|
builder.add_text(&text);
|
||||||
@@ -88,43 +80,27 @@ pub fn stroke_paragraph_builder_group_from_text(
|
|||||||
text_content: &TextContent,
|
text_content: &TextContent,
|
||||||
stroke: &Stroke,
|
stroke: &Stroke,
|
||||||
bounds: &Rect,
|
bounds: &Rect,
|
||||||
blur: Option<&ImageFilter>,
|
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
shadow: Option<&Paint>,
|
|
||||||
count_inner_strokes: usize,
|
count_inner_strokes: usize,
|
||||||
|
use_shadow: Option<bool>,
|
||||||
) -> Vec<ParagraphBuilderGroup> {
|
) -> Vec<ParagraphBuilderGroup> {
|
||||||
let fallback_fonts = get_fallback_fonts();
|
let fallback_fonts = get_fallback_fonts();
|
||||||
let fonts = get_font_collection();
|
let fonts = get_font_collection();
|
||||||
let mut paragraph_group = Vec::new();
|
let mut paragraph_group = Vec::new();
|
||||||
|
let remove_stroke_alpha = use_shadow.unwrap_or(false) && !stroke.is_transparent();
|
||||||
|
|
||||||
for paragraph in text_content.paragraphs() {
|
for paragraph in text_content.paragraphs() {
|
||||||
let mut stroke_paragraphs_map: std::collections::HashMap<usize, ParagraphBuilder> =
|
let mut stroke_paragraphs_map: std::collections::HashMap<usize, ParagraphBuilder> =
|
||||||
std::collections::HashMap::new();
|
std::collections::HashMap::new();
|
||||||
|
|
||||||
for leaf in paragraph.children().iter() {
|
for leaf in paragraph.children().iter() {
|
||||||
let mut text_paint = merge_fills(leaf.fills(), *bounds);
|
let text_paint: skia_safe::Handle<_> = merge_fills(leaf.fills(), *bounds);
|
||||||
if let Some(blur_mask) = blur_mask {
|
let stroke_paints = get_text_stroke_paints(
|
||||||
text_paint.set_mask_filter(blur_mask.clone());
|
stroke,
|
||||||
}
|
bounds,
|
||||||
|
&text_paint,
|
||||||
let stroke_paints = if shadow.is_some() {
|
count_inner_strokes,
|
||||||
get_text_stroke_paints_with_shadows(
|
remove_stroke_alpha,
|
||||||
stroke,
|
);
|
||||||
blur,
|
|
||||||
blur_mask,
|
|
||||||
shadow,
|
|
||||||
leaf.is_transparent(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
get_text_stroke_paints(
|
|
||||||
stroke,
|
|
||||||
bounds,
|
|
||||||
&text_paint,
|
|
||||||
blur,
|
|
||||||
blur_mask,
|
|
||||||
count_inner_strokes,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let text: String = leaf.apply_text_transform();
|
let text: String = leaf.apply_text_transform();
|
||||||
|
|
||||||
@@ -134,8 +110,9 @@ pub fn stroke_paragraph_builder_group_from_text(
|
|||||||
ParagraphBuilder::new(¶graph_style, fonts)
|
ParagraphBuilder::new(¶graph_style, fonts)
|
||||||
});
|
});
|
||||||
let stroke_paint = stroke_paint.clone();
|
let stroke_paint = stroke_paint.clone();
|
||||||
|
let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent();
|
||||||
let stroke_style =
|
let stroke_style =
|
||||||
leaf.to_stroke_style(&stroke_paint, fallback_fonts, blur, blur_mask, None);
|
leaf.to_stroke_style(&stroke_paint, fallback_fonts, remove_alpha);
|
||||||
builder.push_style(&stroke_style);
|
builder.push_style(&stroke_style);
|
||||||
builder.add_text(&text);
|
builder.add_text(&text);
|
||||||
}
|
}
|
||||||
@@ -158,101 +135,12 @@ fn get_built_paragraphs(
|
|||||||
build_paragraphs_with_width(paragraphs, width)
|
build_paragraphs_with_width(paragraphs, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_text_stroke_paints_with_shadows(
|
|
||||||
stroke: &Stroke,
|
|
||||||
blur: Option<&ImageFilter>,
|
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
shadow: Option<&Paint>,
|
|
||||||
is_transparent: bool,
|
|
||||||
) -> Vec<Paint> {
|
|
||||||
let mut paints = Vec::new();
|
|
||||||
|
|
||||||
match stroke.kind {
|
|
||||||
StrokeKind::Inner => {
|
|
||||||
let mut paint = Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
|
|
||||||
if let Some(blur) = blur {
|
|
||||||
paint.set_image_filter(blur.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(shadow) = shadow {
|
|
||||||
paint.set_image_filter(shadow.image_filter());
|
|
||||||
}
|
|
||||||
|
|
||||||
paints.push(paint.clone());
|
|
||||||
|
|
||||||
if is_transparent {
|
|
||||||
let image_filter = skia_safe::image_filters::erode(
|
|
||||||
(stroke.width, stroke.width),
|
|
||||||
paint.image_filter(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
paint.set_image_filter(image_filter);
|
|
||||||
paint.set_blend_mode(skia::BlendMode::DstOut);
|
|
||||||
paints.push(paint.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StrokeKind::Center => {
|
|
||||||
let mut paint = skia_safe::Paint::default();
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
paint.set_stroke_width(stroke.width);
|
|
||||||
|
|
||||||
if let Some(blur) = blur {
|
|
||||||
paint.set_image_filter(blur.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(shadow) = shadow {
|
|
||||||
paint.set_image_filter(shadow.image_filter());
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_transparent {
|
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
|
||||||
} else {
|
|
||||||
paint.set_style(skia::PaintStyle::StrokeAndFill);
|
|
||||||
}
|
|
||||||
|
|
||||||
paints.push(paint);
|
|
||||||
}
|
|
||||||
StrokeKind::Outer => {
|
|
||||||
let mut paint = skia_safe::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::StrokeAndFill);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
paint.set_stroke_width(stroke.width * 2.0);
|
|
||||||
|
|
||||||
if let Some(blur_mask) = blur_mask {
|
|
||||||
paint.set_mask_filter(blur_mask.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(shadow) = shadow {
|
|
||||||
paint.set_image_filter(shadow.image_filter());
|
|
||||||
}
|
|
||||||
|
|
||||||
paints.push(paint.clone());
|
|
||||||
|
|
||||||
if is_transparent {
|
|
||||||
let image_filter = skia_safe::image_filters::erode(
|
|
||||||
(stroke.width, stroke.width),
|
|
||||||
paint.image_filter(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
paint.set_image_filter(image_filter);
|
|
||||||
paint.set_blend_mode(skia::BlendMode::DstOut);
|
|
||||||
paints.push(paint.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paints
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_text_stroke_paints(
|
fn get_text_stroke_paints(
|
||||||
stroke: &Stroke,
|
stroke: &Stroke,
|
||||||
bounds: &Rect,
|
bounds: &Rect,
|
||||||
text_paint: &Paint,
|
text_paint: &Paint,
|
||||||
blur: Option<&ImageFilter>,
|
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
count_inner_strokes: usize,
|
count_inner_strokes: usize,
|
||||||
|
remove_stroke_alpha: bool,
|
||||||
) -> Vec<Paint> {
|
) -> Vec<Paint> {
|
||||||
let mut paints = Vec::new();
|
let mut paints = Vec::new();
|
||||||
|
|
||||||
@@ -269,34 +157,37 @@ fn get_text_stroke_paints(
|
|||||||
let mut paint = text_paint.clone();
|
let mut paint = text_paint.clone();
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
paint.set_style(skia::PaintStyle::Fill);
|
||||||
paint.set_anti_alias(true);
|
paint.set_anti_alias(true);
|
||||||
if let Some(blur) = blur {
|
|
||||||
paint.set_image_filter(blur.clone());
|
|
||||||
}
|
|
||||||
paints.push(paint);
|
paints.push(paint);
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
paint.set_blend_mode(skia::BlendMode::SrcIn);
|
paint.set_blend_mode(skia::BlendMode::SrcIn);
|
||||||
paint.set_anti_alias(true);
|
paint.set_anti_alias(true);
|
||||||
paint.set_stroke_width(stroke.width * 2.0);
|
paint.set_stroke_width(stroke.width * 2.0);
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds);
|
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
||||||
if let Some(blur) = blur {
|
|
||||||
paint.set_image_filter(blur.clone());
|
|
||||||
}
|
|
||||||
paints.push(paint);
|
paints.push(paint);
|
||||||
} else {
|
} else {
|
||||||
let mut paint = text_paint.clone();
|
let mut paint = skia::Paint::default();
|
||||||
|
if remove_stroke_alpha {
|
||||||
|
paint.set_color(skia::Color::BLACK);
|
||||||
|
paint.set_alpha(255);
|
||||||
|
} else {
|
||||||
|
paint = text_paint.clone();
|
||||||
|
set_paint_fill(&mut paint, &stroke.fill, bounds, false);
|
||||||
|
}
|
||||||
|
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
paint.set_style(skia::PaintStyle::Fill);
|
||||||
paint.set_anti_alias(false);
|
paint.set_anti_alias(false);
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds);
|
|
||||||
paints.push(paint);
|
paints.push(paint);
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
let image_filter =
|
let image_filter =
|
||||||
skia_safe::image_filters::erode((stroke.width, stroke.width), None, None);
|
skia_safe::image_filters::erode((stroke.width, stroke.width), None, None);
|
||||||
|
|
||||||
let filter = compose_filters(blur, image_filter.as_ref());
|
paint.set_image_filter(image_filter);
|
||||||
paint.set_image_filter(filter);
|
|
||||||
paint.set_anti_alias(false);
|
paint.set_anti_alias(false);
|
||||||
|
paint.set_color(skia::Color::BLACK);
|
||||||
|
paint.set_alpha(255);
|
||||||
paint.set_blend_mode(skia::BlendMode::DstOut);
|
paint.set_blend_mode(skia::BlendMode::DstOut);
|
||||||
paints.push(paint);
|
paints.push(paint);
|
||||||
}
|
}
|
||||||
@@ -306,12 +197,7 @@ fn get_text_stroke_paints(
|
|||||||
paint.set_style(skia::PaintStyle::Stroke);
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
paint.set_anti_alias(true);
|
paint.set_anti_alias(true);
|
||||||
paint.set_stroke_width(stroke.width);
|
paint.set_stroke_width(stroke.width);
|
||||||
|
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds);
|
|
||||||
if let Some(blur) = blur {
|
|
||||||
paint.set_image_filter(blur.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
paints.push(paint);
|
paints.push(paint);
|
||||||
}
|
}
|
||||||
StrokeKind::Outer => {
|
StrokeKind::Outer => {
|
||||||
@@ -320,10 +206,7 @@ fn get_text_stroke_paints(
|
|||||||
paint.set_blend_mode(skia::BlendMode::DstOver);
|
paint.set_blend_mode(skia::BlendMode::DstOver);
|
||||||
paint.set_anti_alias(true);
|
paint.set_anti_alias(true);
|
||||||
paint.set_stroke_width(stroke.width * 2.0);
|
paint.set_stroke_width(stroke.width * 2.0);
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds);
|
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
||||||
if let Some(blur_mask) = blur_mask {
|
|
||||||
paint.set_mask_filter(blur_mask.clone());
|
|
||||||
}
|
|
||||||
paints.push(paint);
|
paints.push(paint);
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 {
|
|||||||
if let Type::Text(content) = &shape.shape_type {
|
if let Type::Text(content) = &shape.shape_type {
|
||||||
// 1. Reset Paragraphs
|
// 1. Reset Paragraphs
|
||||||
let paragraph_width = content.width();
|
let paragraph_width = content.width();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(content, None, None, None);
|
let mut paragraphs = paragraph_builder_group_from_text(content, None);
|
||||||
let built_paragraphs = build_paragraphs_with_width(&mut paragraphs, paragraph_width);
|
let built_paragraphs = build_paragraphs_with_width(&mut paragraphs, paragraph_width);
|
||||||
|
|
||||||
// 2. Max Width Calculation
|
// 2. Max Width Calculation
|
||||||
@@ -58,14 +58,12 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 {
|
|||||||
// 3. Width and Height Calculation
|
// 3. Width and Height Calculation
|
||||||
match content.grow_type() {
|
match content.grow_type() {
|
||||||
GrowType::AutoHeight => {
|
GrowType::AutoHeight => {
|
||||||
let mut paragraph_height =
|
let mut paragraph_height = paragraph_builder_group_from_text(content, None);
|
||||||
paragraph_builder_group_from_text(content, None, None, None);
|
|
||||||
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
||||||
}
|
}
|
||||||
GrowType::AutoWidth => {
|
GrowType::AutoWidth => {
|
||||||
width = paragraph_width;
|
width = paragraph_width;
|
||||||
let mut paragraph_height =
|
let mut paragraph_height = paragraph_builder_group_from_text(content, None);
|
||||||
paragraph_builder_group_from_text(content, None, None, None);
|
|
||||||
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
||||||
}
|
}
|
||||||
GrowType::Fixed => {}
|
GrowType::Fixed => {}
|
||||||
|
|||||||