mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
🔧 Fix cross-browser text issues
This commit is contained in:
@@ -387,6 +387,26 @@ test("Renders a file with texts with empty lines", async ({
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("Renders a file with texts with breaking words", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = new WasmWorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile("render-wasm/get-file-empty-lines.json");
|
||||
|
||||
await workspace.goToWorkspace({
|
||||
id: "58c5cc60-d124-81bd-8007-0ecbaf9da983",
|
||||
pageId: "15222a7a-d3bc-80f1-8007-0d8e166e650f",
|
||||
});
|
||||
|
||||
await workspace.waitForFirstRender({ hideUI: false });
|
||||
await workspace.clickLeafLayer("text-with-empty-lines-3");
|
||||
await workspace.hideUI();
|
||||
await workspace.page.keyboard.press("Enter");
|
||||
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.skip("Updates text alignment edition - part 1", async ({ page }) => {
|
||||
const workspace = new WasmWorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
@@ -35,11 +35,13 @@
|
||||
}
|
||||
|
||||
[data-itype="inline"] {
|
||||
box-sizing: content-box;
|
||||
display: inline;
|
||||
line-break: auto;
|
||||
line-height: inherit;
|
||||
caret-color: var(--text-editor-caret-color);
|
||||
white-space-collapse: pre;
|
||||
word-break: normal;
|
||||
overflow-wrap: break-word;
|
||||
tab-size: 2;
|
||||
-o-tab-size: 2;
|
||||
}
|
||||
|
||||
@@ -1063,13 +1063,28 @@
|
||||
(set! (.-width canvas) (* dpr (.-clientWidth ^js canvas)))
|
||||
(set! (.-height canvas) (* dpr (.-clientHeight ^js canvas))))
|
||||
|
||||
(defn- get-browser
|
||||
[]
|
||||
(when (exists? js/navigator)
|
||||
(let [user-agent (.-userAgent js/navigator)]
|
||||
(when user-agent
|
||||
(cond
|
||||
(re-find #"(?i)firefox" user-agent) :firefox
|
||||
(re-find #"(?i)chrome" user-agent) :chrome
|
||||
(re-find #"(?i)safari" user-agent) :safari
|
||||
(re-find #"(?i)edge" user-agent) :edge
|
||||
:else :unknown)))))
|
||||
|
||||
|
||||
(defn init-canvas-context
|
||||
[canvas]
|
||||
(let [gl (unchecked-get wasm/internal-module "GL")
|
||||
flags (debug-flags)
|
||||
context-id (if (dbg/enabled? :wasm-gl-context-init-error) "fail" "webgl2")
|
||||
context (.getContext ^js canvas context-id context-options)
|
||||
context-init? (not (nil? context))]
|
||||
context-init? (not (nil? context))
|
||||
browser (get-browser)
|
||||
browser (sr/translate-browser browser)]
|
||||
(when-not (nil? context)
|
||||
(let [handle (.registerContext ^js gl context #js {"majorVersion" 2})]
|
||||
(.makeContextCurrent ^js gl handle)
|
||||
@@ -1081,6 +1096,10 @@
|
||||
(h/call wasm/internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr))
|
||||
(h/call wasm/internal-module "_set_render_options" flags dpr))
|
||||
(set! wasm/context-initialized? true))
|
||||
|
||||
(h/call wasm/internal-module "_set_browser" browser)
|
||||
|
||||
(h/call wasm/internal-module "_set_render_options" flags dpr)
|
||||
(set-canvas-size canvas)
|
||||
context-init?))
|
||||
|
||||
|
||||
@@ -264,3 +264,13 @@
|
||||
"regular" (unchecked-get values "normal")
|
||||
"italic" (unchecked-get values "italic")
|
||||
default)))
|
||||
|
||||
(defn translate-browser
|
||||
[browser]
|
||||
(case browser
|
||||
:firefox 0
|
||||
:chrome 1
|
||||
:safari 2
|
||||
:edge 3
|
||||
:unknown 4
|
||||
4))
|
||||
|
||||
@@ -98,12 +98,18 @@
|
||||
styles (get-styles-from-attrs node txt/text-node-attrs txt/default-text-attrs)]
|
||||
(dissoc styles :line-height)))
|
||||
|
||||
(defn normalize-spaces
|
||||
"Add zero-width spaces after forward slashes to enable word breaking"
|
||||
[text]
|
||||
(when text
|
||||
(.replace text (js/RegExp "/" "g") "/\u200B")))
|
||||
|
||||
(defn get-inline-children
|
||||
[inline paragraph]
|
||||
[(if (and (= "" (:text inline))
|
||||
(= 1 (count (:children paragraph))))
|
||||
(dom/create-element "br")
|
||||
(dom/create-text (:text inline)))])
|
||||
(dom/create-text (normalize-spaces (:text inline))))])
|
||||
|
||||
(defn create-random-key
|
||||
[]
|
||||
|
||||
@@ -92,7 +92,6 @@ macro_rules! with_state_mut_current_shape {
|
||||
};
|
||||
}
|
||||
|
||||
/// This is called from JS after the WebGL context has been created.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn init(width: i32, height: i32) {
|
||||
let state_box = Box::new(State::new(width, height));
|
||||
@@ -101,6 +100,13 @@ pub extern "C" fn init(width: i32, height: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_browser(browser: u8) {
|
||||
with_state_mut!(state, {
|
||||
state.set_browser(browser);
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn clean_up() {
|
||||
unsafe { STATE = None }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
math::{Bounds, Matrix, Rect},
|
||||
render::{default_font, DEFAULT_EMOJI_FONT},
|
||||
utils::Browser,
|
||||
};
|
||||
|
||||
use core::f32;
|
||||
@@ -19,6 +20,7 @@ use crate::math::Point;
|
||||
use crate::shapes::{self, merge_fills, Shape, VerticalAlign};
|
||||
use crate::utils::{get_fallback_fonts, get_font_collection};
|
||||
use crate::Uuid;
|
||||
use crate::STATE;
|
||||
|
||||
// TODO: maybe move this to the wasm module?
|
||||
pub type ParagraphBuilderGroup = Vec<ParagraphBuilder>;
|
||||
@@ -607,6 +609,7 @@ impl Paragraph {
|
||||
style.set_text_align(self.text_align);
|
||||
style.set_text_direction(self.text_direction);
|
||||
style.set_replace_tab_characters(true);
|
||||
style.set_apply_rounding_hack(true);
|
||||
style.set_text_height_behavior(skia::textlayout::TextHeightBehavior::All);
|
||||
style
|
||||
}
|
||||
@@ -711,7 +714,7 @@ impl TextSpan {
|
||||
style.set_font_families(&font_families);
|
||||
style.set_font_size(self.font_size);
|
||||
style.set_letter_spacing(self.letter_spacing);
|
||||
style.set_half_leading(false);
|
||||
style.set_half_leading(true);
|
||||
|
||||
style
|
||||
}
|
||||
@@ -753,15 +756,26 @@ impl TextSpan {
|
||||
format!("{}", self.font_family)
|
||||
}
|
||||
|
||||
fn remove_ignored_chars(text: &str) -> String {
|
||||
fn process_ignored_chars(text: &str, browser: u8) -> String {
|
||||
text.chars()
|
||||
.filter(|&c| c >= '\u{0020}' && c != '\u{2028}' && c != '\u{2029}')
|
||||
.filter_map(|c| {
|
||||
if c < '\u{0020}' || c == '\u{2028}' || c == '\u{2029}' {
|
||||
if browser == Browser::Firefox as u8 {
|
||||
None
|
||||
} else {
|
||||
Some(' ')
|
||||
}
|
||||
} else {
|
||||
Some(c)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn apply_text_transform(&self) -> String {
|
||||
let text = Self::remove_ignored_chars(&self.text);
|
||||
match self.text_transform {
|
||||
let browser = crate::with_state!(state, { state.current_browser });
|
||||
let text = Self::process_ignored_chars(&self.text, browser);
|
||||
let transformed_text = match self.text_transform {
|
||||
Some(TextTransform::Uppercase) => text.to_uppercase(),
|
||||
Some(TextTransform::Lowercase) => text.to_lowercase(),
|
||||
Some(TextTransform::Capitalize) => text
|
||||
@@ -776,7 +790,9 @@ impl TextSpan {
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
None => text,
|
||||
}
|
||||
};
|
||||
|
||||
transformed_text.replace("/", "/\u{200B}")
|
||||
}
|
||||
|
||||
pub fn scale_content(&mut self, value: f32) {
|
||||
|
||||
@@ -22,6 +22,7 @@ pub(crate) struct State<'a> {
|
||||
pub render_state: RenderState,
|
||||
pub text_editor_state: TextEditorState,
|
||||
pub current_id: Option<Uuid>,
|
||||
pub current_browser: u8,
|
||||
pub shapes: ShapesPool<'a>,
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ impl<'a> State<'a> {
|
||||
render_state: RenderState::new(width, height),
|
||||
text_editor_state: TextEditorState::new(),
|
||||
current_id: None,
|
||||
current_browser: 0,
|
||||
shapes: ShapesPool::new(),
|
||||
}
|
||||
}
|
||||
@@ -123,6 +125,10 @@ impl<'a> State<'a> {
|
||||
self.render_state.set_background_color(color);
|
||||
}
|
||||
|
||||
pub fn set_browser(&mut self, browser: u8) {
|
||||
self.current_browser = browser;
|
||||
}
|
||||
|
||||
/// Sets the parent for the current shape and updates the parent's extended rectangle
|
||||
///
|
||||
/// When a shape is assigned a new parent, the parent's extended rectangle needs to be
|
||||
|
||||
@@ -36,3 +36,25 @@ pub fn get_fallback_fonts() -> &'static HashSet<String> {
|
||||
pub fn get_font_collection() -> &'static FontCollection {
|
||||
with_state_mut!(state, { state.font_collection() })
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum Browser {
|
||||
Firefox = 0,
|
||||
Chrome = 1,
|
||||
Safari = 2,
|
||||
Edge = 3,
|
||||
Unknown = 4,
|
||||
}
|
||||
|
||||
impl From<u8> for Browser {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Browser::Firefox,
|
||||
1 => Browser::Chrome,
|
||||
2 => Browser::Safari,
|
||||
3 => Browser::Edge,
|
||||
_ => Browser::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user