Merge pull request #7729 from penpot/ladybenko-12514-fix-font-variants

🐛 Fix downloading wrong font variant
This commit is contained in:
Elena Torró
2025-11-12 15:30:08 +01:00
committed by GitHub
16 changed files with 86 additions and 60 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -23,11 +23,18 @@ export class BasePage {
); );
} }
static async mockFileMediaAsset(page, assetId, assetFilename, options) { static async mockFileMediaAsset(
page,
assetId,
assetFilename,
assetThumbnailFilename,
options,
) {
const ids = Array.isArray(assetId) ? assetId : [assetId]; const ids = Array.isArray(assetId) ? assetId : [assetId];
for (const id of ids) { for (const id of ids) {
const url = `**/assets/by-file-media-id/${id}`; const url = `**/assets/by-file-media-id/${id}`;
const thumbnailUrl = `${url}/thumbnail`;
await page.route(url, (route) => await page.route(url, (route) =>
route.fulfill({ route.fulfill({
@@ -36,6 +43,16 @@ export class BasePage {
...options, ...options,
}), }),
); );
if (assetThumbnailFilename) {
await page.route(thumbnailUrl, (route) =>
route.fulfill({
path: `playwright/data/${assetThumbnailFilename}`,
status: 200,
...options,
}),
);
}
} }
} }
@@ -55,22 +72,6 @@ export class BasePage {
} }
} }
static async mockFileMediaAsset(page, assetId, assetFilename, options) {
const ids = Array.isArray(assetId) ? assetId : [assetId];
for (const id of ids) {
const url = `**/assets/by-file-media-id/${id}`;
await page.route(url, (route) =>
route.fulfill({
path: `playwright/data/${assetFilename}`,
status: 200,
...options,
}),
);
}
}
static async mockConfigFlags(page, flags) { static async mockConfigFlags(page, flags) {
const url = "**/js/config.js?ts=*"; const url = "**/js/config.js?ts=*";
return await page.route(url, (route) => return await page.route(url, (route) =>
@@ -100,11 +101,17 @@ export class BasePage {
return BasePage.mockConfigFlags(this.page, flags); return BasePage.mockConfigFlags(this.page, flags);
} }
async mockFileMediaAsset(assetId, assetFilename, options) { async mockFileMediaAsset(
assetId,
assetFilename,
assetThumbnailFilename,
options,
) {
return BasePage.mockFileMediaAsset( return BasePage.mockFileMediaAsset(
this.page, this.page,
assetId, assetId,
assetFilename, assetFilename,
assetThumbnailFilename,
options, options,
); );
} }

View File

@@ -36,6 +36,7 @@ test("Renders a file with solid, gradient and image fills", async ({
"1ebcea38-f1bf-8101-8006-4c8f579da49c", "1ebcea38-f1bf-8101-8006-4c8f579da49c",
], ],
"render-wasm/assets/penguins.jpg", "render-wasm/assets/penguins.jpg",
"render-wasm/assets/pattern-thumbnail.png", // FIXME: get real thumbnail
); );
await workspace.mockGetFile("render-wasm/get-file-shapes-fills.json"); await workspace.mockGetFile("render-wasm/get-file-shapes-fills.json");
@@ -58,6 +59,7 @@ test("Renders a file with strokes", async ({ page }) => {
"202c1104-9385-81d3-8006-507560ce29e3", "202c1104-9385-81d3-8006-507560ce29e3",
], ],
"render-wasm/assets/penguins.jpg", "render-wasm/assets/penguins.jpg",
"render-wasm/assets/pattern-thumbnail.png", // FIXME: get real thumbnail
); );
await workspace.mockGetFile("render-wasm/get-file-shapes-strokes.json"); await workspace.mockGetFile("render-wasm/get-file-shapes-strokes.json");
@@ -88,6 +90,11 @@ test("Renders a file with shapes with multiple fills", async ({ page }) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-multiple-fills.json"); await workspace.mockGetFile("render-wasm/get-file-multiple-fills.json");
await workspace.mockFileMediaAsset(
["c0939f58-37bc-805d-8006-51cda84a405a"],
"render-wasm/assets/penguins.jpg",
"render-wasm/assets/pattern-thumbnail.png", // FIXME: get real thumbnail
);
await workspace.goToWorkspace({ await workspace.goToWorkspace({
id: "c0939f58-37bc-805d-8006-51cd3a51c255", id: "c0939f58-37bc-805d-8006-51cd3a51c255",
@@ -127,6 +134,7 @@ test("Renders shapes with exif rotated images fills and strokes", async ({
"27270c45-35b4-80f3-8006-63a3ea82557f", "27270c45-35b4-80f3-8006-63a3ea82557f",
], ],
"render-wasm/assets/landscape.jpg", "render-wasm/assets/landscape.jpg",
"render-wasm/assets/pattern-thumbnail.png", // FIXME: get real thumbnail
); );
await workspace.mockGetFile( await workspace.mockGetFile(
"render-wasm/get-file-shapes-exif-rotated-fills.json", "render-wasm/get-file-shapes-exif-rotated-fills.json",
@@ -170,6 +178,15 @@ test("Renders a file with blurs applied to any kind of shape", async ({
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-blurs.json"); await workspace.mockGetFile("render-wasm/get-file-blurs.json");
await workspace.mockFileMediaAsset(
[
"aa0a383a-7553-808a-8006-ae13a3c575eb",
"aa0a383a-7553-808a-8006-ae13c84d6e3a",
"aa0a383a-7553-808a-8006-ae131157fc26",
],
"render-wasm/assets/pattern.png",
"render-wasm/assets/pattern-thumbnail.png", // FIXME: get real thumbnail
);
await workspace.goToWorkspace({ await workspace.goToWorkspace({
id: "aa0a383a-7553-808a-8006-ae1237b52cf9", id: "aa0a383a-7553-808a-8006-ae1237b52cf9",
@@ -212,10 +229,7 @@ test("Renders a file with a closed path shape with multiple segments using strok
await expect(workspace.canvas).toHaveScreenshot(); await expect(workspace.canvas).toHaveScreenshot();
}); });
test("Renders a file with paths and svg attrs", async ({ page }) => {
test("Renders a file with paths and svg attrs", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-svg-attrs.json"); await workspace.mockGetFile("render-wasm/get-file-svg-attrs.json");
@@ -234,7 +248,9 @@ test("Renders a file with nested frames with inherited blur", async ({
}) => { }) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-frame-with-nested-blur.json"); await workspace.mockGetFile(
"render-wasm/get-file-frame-with-nested-blur.json",
);
await workspace.goToWorkspace({ await workspace.goToWorkspace({
id: "58c5cc60-d124-81bd-8007-0ee4e5030609", id: "58c5cc60-d124-81bd-8007-0ee4e5030609",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -141,6 +141,7 @@ test("Renders a file with texts with images", async ({ page }) => {
"4f89252d-ebbc-813e-8006-8699e4170e18", "4f89252d-ebbc-813e-8006-8699e4170e18",
], ],
"render-wasm/assets/pattern.png", "render-wasm/assets/pattern.png",
"render-wasm/assets/pattern-thumbnail.png",
); );
await mockGetEmojiFont(workspace); await mockGetEmojiFont(workspace);
await mockGetJapaneseFont(workspace); await mockGetJapaneseFont(workspace);
@@ -179,6 +180,7 @@ test("Renders a file with text decoration", async ({ page }) => {
await workspace.mockFileMediaAsset( await workspace.mockFileMediaAsset(
["d6c33e7b-7b64-80f3-8006-78509a3a2d21"], ["d6c33e7b-7b64-80f3-8006-78509a3a2d21"],
"render-wasm/assets/pattern.png", "render-wasm/assets/pattern.png",
"render-wasm/assets/pattern-thumbnail.png",
); );
await mockGetEmojiFont(workspace); await mockGetEmojiFont(workspace);
await mockGetJapaneseFont(workspace); await mockGetJapaneseFont(workspace);
@@ -281,14 +283,10 @@ test("Renders a file with different text shadows combinations", async ({
await expect(workspace.canvas).toHaveScreenshot(); await expect(workspace.canvas).toHaveScreenshot();
}); });
test("Renders a file with multiple text shadows in order", async ({ test("Renders a file with multiple text shadows in order", async ({ page }) => {
page,
}) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile( await workspace.mockGetFile("render-wasm/get-file-text-shadows-order.json");
"render-wasm/get-file-text-shadows-order.json",
);
await workspace.goToWorkspace({ await workspace.goToWorkspace({
id: "48ffa82f-6950-81b5-8006-e49a2a39657f", id: "48ffa82f-6950-81b5-8006-e49a2a39657f",
@@ -337,7 +335,9 @@ test("Renders a file with texts with with text spans of different sizes", async
}) => { }) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-text-spans-different-sizes.json"); await workspace.mockGetFile(
"render-wasm/get-file-text-spans-different-sizes.json",
);
await workspace.goToWorkspace({ await workspace.goToWorkspace({
id: "a0b1a70e-0d02-8082-8006-ff6d160f15ce", id: "a0b1a70e-0d02-8082-8006-ff6d160f15ce",
@@ -347,9 +347,8 @@ test("Renders a file with texts with with text spans of different sizes", async
await expect(workspace.canvas).toHaveScreenshot(); await expect(workspace.canvas).toHaveScreenshot();
}); });
test("Renders a file with texts with tabs", async ({ // TODO: enable this test once we use the wasm renderer in the new editor
page, test.skip("Renders a file with texts with tabs", async ({ page }) => {
}) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-text-tabs.json"); await workspace.mockGetFile("render-wasm/get-file-text-tabs.json");
@@ -367,9 +366,8 @@ test("Renders a file with texts with tabs", async ({
await expect(workspace.canvas).toHaveScreenshot(); await expect(workspace.canvas).toHaveScreenshot();
}); });
test("Renders a file with texts with empty lines", async ({ // TODO: enable this test once we use the wasm renderer in the new editor
page, test.skip("Renders a file with texts with empty lines", async ({ page }) => {
}) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-empty-lines.json"); await workspace.mockGetFile("render-wasm/get-file-empty-lines.json");
@@ -387,9 +385,8 @@ test("Renders a file with texts with empty lines", async ({
await expect(workspace.canvas).toHaveScreenshot(); await expect(workspace.canvas).toHaveScreenshot();
}); });
test("Renders a file with texts with breaking words", async ({ // TODO: enable this test once we use the wasm renderer in the new editor
page, test.skip("Renders a file with texts with breaking words", async ({ page }) => {
}) => {
const workspace = new WasmWorkspacePage(page); const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile(); await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-empty-lines.json"); await workspace.mockGetFile("render-wasm/get-file-empty-lines.json");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -912,7 +912,7 @@
(map :id) (map :id)
(run! f/update-text-layout))) (run! f/update-text-layout)))
(defn process-pending (defn process-pending!
[shapes thumbnails full] [shapes thumbnails full]
(let [event (js/CustomEvent. "wasm:set-objects-finished") (let [event (js/CustomEvent. "wasm:set-objects-finished")
pending-thumbnails (-> (d/index-by :key :callback thumbnails) vals) pending-thumbnails (-> (d/index-by :key :callback thumbnails) vals)
@@ -920,11 +920,11 @@
(->> (rx/concat (->> (rx/concat
(->> (rx/from pending-thumbnails) (->> (rx/from pending-thumbnails)
(rx/merge-map (fn [callback] (callback))) (rx/merge-map (fn [callback] (callback)))
(rx/reduce conj []) (rx/reduce conj []))
(rx/tap #(.dispatchEvent ^js js/document event)))
(->> (rx/from pending-full) (->> (rx/from pending-full)
(rx/mapcat (fn [callback] (callback))) (rx/mapcat (fn [callback] (callback)))
(rx/reduce conj []))) (rx/reduce conj [])
(rx/tap #(.dispatchEvent ^js js/document event))))
(rx/subs! (rx/subs!
(fn [_] (fn [_]
(update-text-layouts shapes) (update-text-layouts shapes)
@@ -933,7 +933,7 @@
(defn process-object (defn process-object
[shape] [shape]
(let [{:keys [thumbnails full]} (set-object [] shape)] (let [{:keys [thumbnails full]} (set-object [] shape)]
(process-pending [shape] thumbnails full))) (process-pending! [shape] thumbnails full)))
(defn set-objects (defn set-objects
[objects] [objects]
@@ -951,7 +951,7 @@
(into full-acc full))) (into full-acc full)))
{:thumbnails thumbnails-acc :full full-acc}))] {:thumbnails thumbnails-acc :full full-acc}))]
(perf/end-measure "set-objects") (perf/end-measure "set-objects")
(process-pending shapes thumbnails full))) (process-pending! shapes thumbnails full)))
(defn clear-focus-mode (defn clear-focus-mode
[] []

View File

@@ -66,9 +66,10 @@
:custom :custom
(let [font-uuid (custom-font-id->uuid font-id) (let [font-uuid (custom-font-id->uuid font-id)
matching-font (d/seek (fn [[_ font]] matching-font (d/seek (fn [[_ font]]
(and (= (:font-id font) font-uuid) (let [variant-id (or (:font-variant-id font) (dm/str (:font-style font) "-" (:font-weight font)))]
(or (nil? (:font-variant-id font)) (and (= (:font-id font) font-uuid)
(= (:font-variant-id font) font-variant-id)))) (or (nil? font-variant-id)
(= variant-id font-variant-id)))))
(seq @fonts))] (seq @fonts))]
(when matching-font (when matching-font
(:ttf-file-id (second matching-font)))) (:ttf-file-id (second matching-font))))
@@ -85,7 +86,7 @@
(aget shape-id-buffer 2) (aget shape-id-buffer 2)
(aget shape-id-buffer 3)))) (aget shape-id-buffer 3))))
;; IMPORTANT: It should be noted that only TTF fonts can be stored. ;; IMPORTANT: Only TTF fonts can be stored.
(defn- store-font-buffer (defn- store-font-buffer
[shape-id font-data font-array-buffer emoji? fallback?] [shape-id font-data font-array-buffer emoji? fallback?]
(let [font-id-buffer (:family-id-buffer font-data) (let [font-id-buffer (:family-id-buffer font-data)
@@ -231,6 +232,7 @@
(store-font-id shape-id font-data asset-id emoji? fallback?))) (store-font-id shape-id font-data asset-id emoji? fallback?)))
(defn store-fonts (defn store-fonts
[shape-id fonts] [shape-id fonts]
(keep (fn [font] (store-font shape-id font)) fonts)) (keep (fn [font] (store-font shape-id font)) fonts))

View File

@@ -198,20 +198,20 @@
:coptic #"[\u2C80-\u2CFF]" :coptic #"[\u2C80-\u2CFF]"
:ol-chiki #"[\u1C50-\u1C7F]" :ol-chiki #"[\u1C50-\u1C7F]"
:vai #"[\uA500-\uA63F]" :vai #"[\uA500-\uA63F]"
:shavian #"[\u10450-\u1047F]" :shavian #"\uD801[\uDC50-\uDC7F]"
:osmanya #"[\u10480-\u104AF]" :osmanya #"\uD801[\uDC80-\uDCAF]"
:runic #"[\u16A0-\u16FF]" :runic #"[\u16A0-\u16FF]"
:old-italic #"[\u10300-\u1032F]" :old-italic #"\uD800[\uDF00-\uDF2F]"
:brahmi #"[\u11000-\u1107F]" :brahmi #"\uD804[\uDC00-\uDC7F]"
:modi #"[\u11600-\u1165F]" :modi #"\uD805[\uDE00-\uDE5F]"
:sora-sompeng #"[\u110D0-\u110FF]" :sora-sompeng #"\uD804[\uDCD0-\uDCFF]"
:bamum #"[\uA6A0-\uA6FF]" :bamum #"[\uA6A0-\uA6FF]"
:meroitic #"[\u10980-\u1099F]" :meroitic #"\uD802[\uDD80-\uDD9F]"
;; Arrows, Mathematical Operators, Misc Technical, Geometric Shapes, Misc Symbols, Dingbats, Supplemental Arrows, etc. ;; Arrows, Mathematical Operators, Misc Technical, Geometric Shapes, Misc Symbols, Dingbats, Supplemental Arrows, etc.
:symbols #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]" :symbols #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]"
;; Additional arrows, math, technical, geometric, and symbol blocks ;; Additional arrows, math, technical, geometric, and symbol blocks
:symbols-2 #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]" :symbols-2 #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]"
:music #"[\u2669-\u267B\u1D100-\u1D1FF]"}) :music #"[\u2669-\u267B]|\uD834[\uDD00-\uDD1F]"})
(defn contains-emoji? [text] (defn contains-emoji? [text]
(let [result (re-find emoji-pattern text)] (let [result (re-find emoji-pattern text)]

View File

@@ -187,8 +187,12 @@
(api/set-shape-svg-raw-content (api/get-static-markup shape)) (api/set-shape-svg-raw-content (api/get-static-markup shape))
(= (:type shape) :text) (= (:type shape) :text)
(do (api/set-shape-text-content id v) (let [pending-thumbnails (into [] (concat (api/set-shape-text-content id v)))
(api/set-shape-text-images id v))) pending-full (into [] (concat (api/set-shape-text-images id v)))]
;; FIXME: this is a hack to process the pending tasks asynchronously
;; we should probably modify set-wasm-attr! to return a list of callbacks to be executed in a second pass.
(api/process-pending! [shape] pending-thumbnails pending-full)
nil))
:grow-type :grow-type
(api/set-shape-grow-type v) (api/set-shape-grow-type v)

View File

@@ -229,7 +229,7 @@ fn draw_text(
paragraph.paint(canvas, xy); paragraph.paint(canvas, xy);
if paragraph_index == group_len - 1 { if paragraph_index == group_len - 1 {
group_offset_y += paragraph.height(); group_offset_y += paragraph.ideographic_baseline();
} }
for line_metrics in paragraph.get_line_metrics().iter() { for line_metrics in paragraph.get_line_metrics().iter() {