diff --git a/tests/helpers/load-plugin-modules.mjs b/tests/helpers/load-plugin-modules.mjs
index cb3e5bf..9adcaa1 100644
--- a/tests/helpers/load-plugin-modules.mjs
+++ b/tests/helpers/load-plugin-modules.mjs
@@ -78,6 +78,7 @@ export async function bundleSource(entry, outputName, external = [], plugins = [
let textUtilsModulePromise;
let idleScheduleModulePromise;
+let textHaloModulePromise;
const stubObsidianPlugin = {
name: "stub-obsidian",
@@ -161,6 +162,13 @@ export function loadTextUtilsModule() {
return textUtilsModulePromise;
}
+export function loadTextHaloModule() {
+ textHaloModulePromise ??= bundleSource("src/TextHalo.ts", "text-halo.cjs").then(
+ (outfile) => require(outfile)
+ );
+ return textHaloModulePromise;
+}
+
export function loadTextToolbarControllerModule() {
textToolbarControllerModulePromise ??= bundleSource(
"src/powerpoint/textToolbarController.ts",
diff --git a/tests/text-halo.test.mjs b/tests/text-halo.test.mjs
new file mode 100644
index 0000000..e47ad43
--- /dev/null
+++ b/tests/text-halo.test.mjs
@@ -0,0 +1,164 @@
+import assert from "node:assert/strict";
+import { test } from "node:test";
+import { loadTextHaloModule } from "./helpers/load-plugin-modules.mjs";
+
+function parseSvg(svgString) {
+ const parser = new globalThis.DOMParser();
+ return parser.parseFromString(svgString, "image/svg+xml").documentElement;
+}
+
+test("table text halos inherit background color via SVG structure", async () => {
+ const { applyBackgroundAwareTextHalos, DEFAULT_TEXT_HALO_COLOR } = await loadTextHaloModule();
+
+ // A basic table-like SVG where a text element follows a rectangle.
+ const svg = parseSvg(`
+
+ `);
+
+ applyBackgroundAwareTextHalos(svg, "table");
+
+ const texts = svg.getElementsByTagName("text");
+
+ // text "Hello" after "red" rect
+ assert.equal(texts[0].getAttribute("data-native-powerpoint-halo-color"), "rgb(255, 0, 0)");
+ assert.ok(texts[0].getAttribute("style").includes("--native-powerpoint-text-halo: rgb(255, 0, 0)"));
+
+ // text "World" in a group after a "blue" rect
+ assert.equal(texts[1].getAttribute("data-native-powerpoint-halo-color"), "rgb(0, 0, 255)");
+
+ // text "Default" without a preceding rect (inherits default)
+ assert.equal(texts[2].getAttribute("data-native-powerpoint-halo-color"), DEFAULT_TEXT_HALO_COLOR);
+});
+
+test("chart text halos inherit background color of largest rect", async () => {
+ const { applyBackgroundAwareTextHalos, DEFAULT_TEXT_HALO_COLOR } = await loadTextHaloModule();
+
+ const svg = parseSvg(`
+
+ `);
+
+ // #00ff00 (green) is 100x100 = 10000 area. Largest!
+ applyBackgroundAwareTextHalos(svg, "chart");
+
+ const texts = svg.getElementsByTagName("text");
+ assert.equal(texts[0].getAttribute("data-native-powerpoint-halo-color"), "rgb(0, 255, 0)");
+ assert.equal(texts[1].getAttribute("data-native-powerpoint-halo-color"), "rgb(0, 255, 0)");
+});
+
+test("color parsing handles opacity and fill-opacity", async () => {
+ const { applyBackgroundAwareTextHalos } = await loadTextHaloModule();
+
+ // Test compositeAgainstWhite
+ // Red rect, 50% opacity -> composed with white -> rgb(255, 128, 128)
+ const svg = parseSvg(`
+
+ `);
+
+ applyBackgroundAwareTextHalos(svg, "table");
+
+ const texts = svg.getElementsByTagName("text");
+
+ // red 0.5 composed with white:
+ // red = 255 * 0.5 + 255 * 0.5 = 255
+ // green = 0 * 0.5 + 255 * 0.5 = 128
+ // blue = 0 * 0.5 + 255 * 0.5 = 128
+ // rgb(255, 128, 128)
+ assert.equal(texts[0].getAttribute("data-native-powerpoint-halo-color"), "rgb(255, 128, 128)");
+
+ // black 0.25 composed with white:
+ // r, g, b = 0 * 0.25 + 255 * 0.75 = 191
+ // rgb(191, 191, 191)
+ assert.equal(texts[1].getAttribute("data-native-powerpoint-halo-color"), "rgb(191, 191, 191)");
+});
+
+test("color parsing handles transparent, none, and absent colors", async () => {
+ const { applyBackgroundAwareTextHalos, DEFAULT_TEXT_HALO_COLOR } = await loadTextHaloModule();
+
+ const svg = parseSvg(`
+
+ `);
+
+ applyBackgroundAwareTextHalos(svg, "table");
+
+ const texts = svg.getElementsByTagName("text");
+ assert.equal(texts[0].getAttribute("data-native-powerpoint-halo-color"), DEFAULT_TEXT_HALO_COLOR);
+ assert.equal(texts[1].getAttribute("data-native-powerpoint-halo-color"), DEFAULT_TEXT_HALO_COLOR);
+ assert.equal(texts[2].getAttribute("data-native-powerpoint-halo-color"), DEFAULT_TEXT_HALO_COLOR);
+});
+
+test("color parsing handles named colors and functions", async () => {
+ const { applyBackgroundAwareTextHalos } = await loadTextHaloModule();
+
+ const svg = parseSvg(`
+
+ `);
+
+ applyBackgroundAwareTextHalos(svg, "table");
+
+ const texts = svg.getElementsByTagName("text");
+
+ // white
+ assert.equal(texts[0].getAttribute("data-native-powerpoint-halo-color"), "rgb(255, 255, 255)");
+
+ // black
+ assert.equal(texts[1].getAttribute("data-native-powerpoint-halo-color"), "rgb(0, 0, 0)");
+
+ // rgb(0, 100, 200)
+ assert.equal(texts[2].getAttribute("data-native-powerpoint-halo-color"), "rgb(0, 100, 200)");
+
+ // rgba(0, 100, 200, 0.5) against white
+ // red: 0*0.5 + 255*0.5 = 128
+ // green: 100*0.5 + 255*0.5 = 178
+ // blue: 200*0.5 + 255*0.5 = 228
+ assert.equal(texts[3].getAttribute("data-native-powerpoint-halo-color"), "rgb(128, 178, 228)");
+});
diff --git a/vendor/docx-editor-runtime/provenance.json b/vendor/docx-editor-runtime/provenance.json
index 1218476..04364fb 100644
--- a/vendor/docx-editor-runtime/provenance.json
+++ b/vendor/docx-editor-runtime/provenance.json
@@ -9,20 +9,18 @@
"react": "1.9.0",
"i18n": "1.9.0"
},
- "fileCount": 375,
+ "fileCount": 355,
"fileHashes": {
"core/dist/chunk-23ZV5H5O.mjs": "78817a45fdbda4ffa918a20f40d1de9c1ee2d5787708a29dc770184f14cb61ef",
"core/dist/chunk-2KHWHVTA.js": "cffd7e0e5508a05de0120be545e5b41a61b8fbbab154c09af5533a7b60aeda3f",
"core/dist/chunk-3ANZ3252.mjs": "313983963f640e5395e4039f18b172b82dc7c4d14d3437d8795991ad078a8ecc",
"core/dist/chunk-3COGZG7O.mjs": "24d2978fa2d6df895d0699d2ebe595482006481eb541396f793d745c19d3b647",
- "core/dist/chunk-3HV4UKPW.mjs": "3113cbe81b65c6b1d4a654b576bc7ca072e063cc30fc31f2d5794b30c65f3a53",
"core/dist/chunk-3R7QHNI5.js": "b6e37b7e26d46161777ace1810168bb359e4e5418d6c84d69c77d7445c278e6e",
"core/dist/chunk-3SXJPJSV.mjs": "daf6b7f189549a897cb768b2c3992bcd5d89466fd6109e2b09d42b916fa00927",
"core/dist/chunk-47AG77IC.js": "4ae4f433c591db8ffae59295f29346049bfc12740e03908572c3212ac84bc8b8",
"core/dist/chunk-4DCTKHS5.js": "e18434c5c5462ad42f51899b236f022642fae24e22163ef835149bc52f83c2b8",
"core/dist/chunk-4IQQYC2J.mjs": "68f4dfcf91cd648ca0a9ed6d5c0fc3b2f9a75891bb50eb0b626029ca55b26e1e",
"core/dist/chunk-4VLKSJRK.mjs": "049eff8ce535f064867ecd0fbadc87145ddf8f1b188526425cb7eb9ebe40e670",
- "core/dist/chunk-52DHZAZU.js": "d2a03738adb33366eaec23748edf6b8dc52ce8672db746aa344083c3c44089cf",
"core/dist/chunk-53VKL6CG.js": "8e8cc72e61ff6f6c0c945a976756d112732c499cd64fc5b207bb213fc84a1e40",
"core/dist/chunk-5HYP7TWL.js": "b47dc3a706c460acd7222f204e8e0fcba68760360a9233ce77e5083565ae6d98",
"core/dist/chunk-67LVRQIR.mjs": "f3d36e50a7046c5c3163e9a50de48617c904d7e2d5c5aafd41e8070ede7fbfca",
@@ -42,15 +40,12 @@
"core/dist/chunk-ASDTR7GJ.js": "3635ddaee10984350b8a9e9331f1be70266dcda6fe4f1278178cb9052654aec6",
"core/dist/chunk-B3SVS2AF.js": "e98f29c097843aa3aa4e48216c758373b81e85bbb96b90622f5819f7d9351df9",
"core/dist/chunk-BFQ2Z4OL.mjs": "0ebb5a861d4770e1f7a3d0c62afaef8894b7ca91bbed2cdaec14a43e40ce9333",
- "core/dist/chunk-BGQMKNCR.mjs": "c0bf651fe6d5a02f735e649556c72a712b62a91d459349317aa7f8a43caf6c86",
"core/dist/chunk-BJSG6TEG.js": "833ea426757b778207b41ff3fff6d72a7aa2d6c648d2463023fa4354465f0a31",
"core/dist/chunk-BPZG7KT3.js": "185a16ad6fc31525dbcc22cc07a47700b04438a2621f1f25c83b9fe9a2856646",
"core/dist/chunk-BTBPQSIY.js": "6e5c8a9e1fe21d92c994e637549140aee2767931a94c333f313102538345d447",
"core/dist/chunk-BX5JNYNI.mjs": "e6470bc9bcdb88f59541f9de4375b0767655088f130deb471240761930b6962f",
"core/dist/chunk-C7YXIU2K.js": "fd60d68a68e7ba6975545ba4b4483da17edbb43e0c7360165815b8d2e7f7d04d",
"core/dist/chunk-CKKO4JWE.mjs": "c4c0927cfd794e891cce071baf910beb29a1eb549dd292f4201e61706a241b2c",
- "core/dist/chunk-CLNQHDJD.mjs": "4c3e869156f691f3a9d8a52e77583569d076a3f7f60163ec9d5f2b66b86ca811",
- "core/dist/chunk-CMDN3WCI.mjs": "e3811b8d97f209df8a44245beff6d76f5288487b5d3597ea1517094975790674",
"core/dist/chunk-CVCPLNFN.js": "56e6904670f7cbcc4318b4904a0dee15db626ea54c5c033ec2aed92a25260246",
"core/dist/chunk-CWZKRBRE.mjs": "73eae3e600d9ff47cf0a88a40fbbb6236ecd23f8e462c403bd21e846379820db",
"core/dist/chunk-DROCYKAR.js": "f1a10a7f72e9ac492b0ee245d38412e602221af3b69f78ec2307aaa33e43a44f",
@@ -62,21 +57,16 @@
"core/dist/chunk-FCJWLEKT.mjs": "8c2a3afc2c0c5af34b76b97fee6d0aad33b73b53ac98389ee90653a11d3a4054",
"core/dist/chunk-FDAGHNH2.js": "5c3937362f676cb572477be8d5d1729c91b78cd41c1a20c2b11e4e8de349e5c7",
"core/dist/chunk-FECFXS6U.mjs": "ad0a9e5d45c17f7741a563d5e776a0cc5d4436db3526b1e2660c00c78651e56c",
- "core/dist/chunk-FGKHIVWI.mjs": "8915d7c288dc7123ebc9cc214b898f65af0d0928dd0d782768b20e5f6e8d7e66",
"core/dist/chunk-FWWBQ7WS.js": "a2cc9c7567ebb1e480206565a8a777e37806f5f3e17a262c04faebc1c67b3373",
"core/dist/chunk-FYH2KHXU.js": "01851bb3643f3ecf1defc325ef060290db5008d2f37a4d413a333ec01373a9cb",
"core/dist/chunk-GFIODXSG.js": "bc703e60dbe076a9d956f9f78cf3c2a832f52a5f45261eab178354496c06e39e",
"core/dist/chunk-GGPKPXON.js": "caa10b214052e1df2ea873400bad22ea473dd4787aa2fce4becfff1f4e7eb67e",
"core/dist/chunk-GLPOVLZD.mjs": "9bda5672a7ba83c21b8b810f0bb20ef64a79acfa6ac8f3cae1edf295ce6bfa91",
"core/dist/chunk-GMDBISS3.js": "c5b6bfc6095b5a586e3b8260281a7445859bde74f35fcc21859b53575e949081",
- "core/dist/chunk-GWLM254S.js": "39fd6cebed71b202def48aff960e3ef94f9f8358dabfee5cafd3956438e5321a",
- "core/dist/chunk-GYOKKCOQ.mjs": "13151635c0d2b2356113beca0346b7d953df7af9824bd6d4ce038c94d6cacc52",
"core/dist/chunk-H57LNDBH.js": "cae7ee69f4b81f24a9dda8421b6b92076c4035914a412ce80ef14407c247e010",
"core/dist/chunk-HBIH5VAA.js": "db07ae44e15fcd14c5204531c1f0a662f8883b20ff7c9c909054d0cc7b04c089",
"core/dist/chunk-HBIXLNXV.js": "9d62a67b6067d3144d1427aba7f1884c3d60f54815d201baa8e4a236205c1799",
- "core/dist/chunk-HJCGTPSA.js": "ba0d1001659e24fbd20d4f690ae3d1d34ed0dc03e07d92997f97a1217c92f554",
"core/dist/chunk-HJE46MS3.mjs": "4b7402ea957f438a6c0d8fdaf2d3f7900586d7f4238adabe81f4ddc138831af3",
- "core/dist/chunk-HJIKLTL4.mjs": "b5ef2cff5c2fd151b6f176b2ca3f471344cf5ffa5e1c0e208c62e2c75f06ff38",
"core/dist/chunk-HL7JS6FV.mjs": "99dc4f6282098b4d0f3403e3d7b297681e1ec5e174c62da9eff033e7e285c250",
"core/dist/chunk-HPJBZL37.mjs": "89ed2976cffa899505a33187ffda9402ed27b54468f389314caf778039b0388f",
"core/dist/chunk-HXZTROSY.mjs": "48f94f22a893f3991223e860d0c55f919e8ae7008072955ab1d525f1921f9186",
@@ -90,7 +80,6 @@
"core/dist/chunk-JHIEVXPS.js": "5d52bacc7358333bc004874df35f54f048c6c6044238fa0dd18477c3ffb9d55a",
"core/dist/chunk-JR557W27.mjs": "3ade205f0bac96a38ed4ca61f523c5b9a827c69746d45d5a10d3b1ed0d2c89d2",
"core/dist/chunk-JY5EFI4D.js": "7ac448dbf32c3c241a71b7dfcbda130e6a79b7dde04547ba7fe418303f94ceb4",
- "core/dist/chunk-KEQNZYXE.js": "57e49f0f09b0a662822a1229b1b10422129151b257d71e953f9e9b80aa754087",
"core/dist/chunk-KESNGXGO.js": "aa31285d1db53e7c46065a0ff51161b4cdd3015bba71ab2ce1cebf6edb6cfcd7",
"core/dist/chunk-KGRBI4EM.mjs": "385ed109a48cdec8db60aff406e5276c227a28705237acd7b0267fd0fd7d736f",
"core/dist/chunk-KNY7SXPJ.mjs": "812f64c9e7ca83aa458b77014acf38782df01fbcf3ed2c74afb968cf0f8d012f",
@@ -98,15 +87,12 @@
"core/dist/chunk-LB2OEMYL.js": "8b7700398d587e6d53eb12cdf230c1460c6fa99be04308029bd076dae74bb28e",
"core/dist/chunk-LUPZQCMG.mjs": "d6dab833c0e55eb505a737cdd9282cb2aa788848c23bf2672a54f96e2184727e",
"core/dist/chunk-LVUL7O27.js": "377ea97861d530fcc5e90a322d3ae7aef7eb8c56bf7cba21d9ff09ab684904f0",
- "core/dist/chunk-M5T2IDXM.js": "6d4cb74273893bc79b625f4fe92225341847af4c26dcea91f4f144612fe5eb96",
"core/dist/chunk-MJ5FQX7Q.js": "bf5cfd61359e7956c51139dbbf1b930fed53d8fc117c4fbdcc5b71c9bb29f5b2",
"core/dist/chunk-MKSI2DWW.js": "74fcc87fd3de3f91f56b232df6616c684a9e58709c6509b5871f2803dd2b6202",
- "core/dist/chunk-MM3OP6FN.js": "7a758a92884395329435b597287807017a536838b1463b8a26ed562a1b13f9a3",
"core/dist/chunk-MVZOTKNL.mjs": "328c348c18c4bb1a9afe1d0ff32b6336e86a58e8b18223d47d6956daa96ad96c",
"core/dist/chunk-MXPJ76MM.js": "0e12eb90acaba9b5ce1a00c70b31ca83c8da2c10721ba1ec799891f80ce23cc1",
"core/dist/chunk-MYWHXY7S.mjs": "c775e8eaf0906cc9ed31bc026290cee043381820b36d11df34b5dce1751353cc",
"core/dist/chunk-MZ7LW5CH.js": "1d830bfbe09bacb7121db31f518cd552be830e479ee6dd7002cd55ef47c80fab",
- "core/dist/chunk-NF4RWYIL.js": "4fdc64e1db93a118413d113b1e7e55746f99a927701bf543e2415bf2d8ee5863",
"core/dist/chunk-NRWQ65NX.js": "879c67688f83d188f492c1c8b14f58712ec7d15712857903286ae95d89f25938",
"core/dist/chunk-NXLJVVVE.js": "9eed4cb93e15c167ca4ff37fd21c519f640f482d4970c1f1328fdff1462da72f",
"core/dist/chunk-O2XFH626.js": "d4eb94d09e19bc698ae5b037e5b4fb0b8d810f91f71e34c7bd03315015fd97d4",
@@ -115,11 +101,8 @@
"core/dist/chunk-OATN3RKU.mjs": "68c11fe65a60820f435b1b1a5ffac5afa56181522e5587d044cf2f374904d682",
"core/dist/chunk-OBMN4VSM.mjs": "60a6c2e9824d0599c6bedb959f5c3fe2791f5bc3a18f32f8b252b8b6e7b1b6cf",
"core/dist/chunk-OO6FBGRK.mjs": "fa10941f4d64b6f56402f19c3586878200737b00ac060fcb036c469fee826119",
- "core/dist/chunk-OYA45VXM.js": "f201fca8c147abf860ba99be060f013cae2c36c5143bd3b3a2ca68f78e37d57e",
"core/dist/chunk-OZMNIX6U.mjs": "0400fb28e069fe5f099c34549dffb3cd2d4f020850d9f13a7cbabad86c7e81a2",
"core/dist/chunk-PGWY6UFK.js": "5bba809d47fee9442a6d017d978efb3841229e6d65c8349729910d96a9f2fb22",
- "core/dist/chunk-PN7VQNTW.mjs": "d8283ad624cb164618d4516183dd3a8794f39fcbe903f89bd780c7eef71147c0",
- "core/dist/chunk-PNXUYXSM.mjs": "d7d0b32c86fd7e04379c0471b96311fbc4100194d2dce65ad6eb97c88aed16fe",
"core/dist/chunk-PRRM64JO.mjs": "bfcd73fe80b082df0d691c98a48e89f2f0883fb177a72ad2385af2875af50d81",
"core/dist/chunk-PSZHULFD.mjs": "216dd3eaeb165255f7dfc3ba6984a98d327a912933119fa329474e9473d9dfd9",
"core/dist/chunk-Q3O72CRO.js": "76cdbb5ed9c526132c648054764714e4ab2d3db5bedc7bcacb676d5f1f196240",
@@ -129,12 +112,10 @@
"core/dist/chunk-QKNLMXKB.mjs": "efa8a54a29fa04a4658a0d2030bb21463b9351ddd5124018f075e2ca41cd9178",
"core/dist/chunk-QPFJJPIC.mjs": "cf390832b62f3c846649ee56f9132ab427b9c88fa8429682a92387ad4966b70e",
"core/dist/chunk-R5AWBUSP.js": "2a300034c97574cff7ad53627169460c4fd4735dae2a6f64af0827f006cad6bc",
- "core/dist/chunk-RFBNBWRE.js": "369f83d540aab577b0119db07cff1684444272ea0387c4be7c165a7353bda66f",
"core/dist/chunk-RNAU5NCU.mjs": "38cd5bda7f042495dfafc3c37ec82dbbdb72679e2083513ec2ca412ee2c74a82",
"core/dist/chunk-RNOXNNSD.mjs": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"core/dist/chunk-RPIQG46O.mjs": "602cd4f7ef88d8e0b7754fecc4ef3f122b43bc824e6f725b1f57546d01a33ba6",
"core/dist/chunk-RWAOWRVF.js": "d12ce57ed5210753cd26b1839f81570452ace189eabe3d6fddeced31ed000507",
- "core/dist/chunk-SOBIB675.js": "f396d1e183567b74e0665228695895381d6e6815dc948e6b929128e31802e32e",
"core/dist/chunk-SUXJNWM4.mjs": "eb2a4a6ebb957be056c964442b0a4b9ef16aa5c4d3370c72457e61699256f96e",
"core/dist/chunk-TCNH27CF.mjs": "89d0ac88fab3a9cf4f7ff64fa921a09d333f8199bf4d821ee71e1f59cd6fabb2",
"core/dist/chunk-TGN6VVCD.js": "8222619b8680c27b07ffd119fdcd2a0e43dafdcb64bd93e639b9f9d6ebfcc315",
@@ -158,7 +139,6 @@
"core/dist/chunk-XXU7YQVV.js": "52f5d07fa17ad8eee2f48081b5aeab483dbcec32693e94c8442f029272b69bae",
"core/dist/chunk-Z4UH6E77.mjs": "bebded4cd988320acc9fc8e08d4849cc53d104c24835eac6aa221f64bc5edb47",
"core/dist/chunk-ZJNHN633.mjs": "0e5a0b11f7eef76e8f6db735e61ebe398b5ad4bac5ad72bc1b755bf8c606c91e",
- "core/dist/chunk-ZL6V6FQI.mjs": "164dbc9ffbad1d392c44e7b07d4f6ffa03752f67259d1b1a86a888f315682fb7",
"core/dist/contentControls/index.js": "c52563bdf48770953ab18a8cb2f50a5b4c293112378e9006cf3e6b74cdd7a191",
"core/dist/contentControls/index.mjs": "88fb51bedeb06a011a928276e242e4a54a2cdb2151ff703dce94857fab2f9f45",
"core/dist/core-plugins.js": "9f104b00530d9559496924a0eefdc25765216cc05b20c5f1edbcc3b0aef19a37",