Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misaligned RTL Text #27

Closed
wolfmcnally opened this issue Jan 13, 2025 · 4 comments
Closed

Misaligned RTL Text #27

wolfmcnally opened this issue Jan 13, 2025 · 4 comments

Comments

@wolfmcnally
Copy link

I am experimenting with this example of Arabic text:

    const divStyle: flow.DeclaredStyle = {
      direction: 'rtl',
    };

    const h = flow.h;
    const string = 'أية تقنية متقدمة بما فيه الكفاية لا يمكن تمييزها عن السحر.';

    const elem = h('div', { style: divStyle }, string);

    const root = flow.dom(elem);

    const container: flow.BlockContainer = flow.generate(root);
    flow.layout(container, 200, 200);
    let svg = flow.paintToSvg(container);
    console.log(beautifySvg(svg));

Which is outputting the following SVG:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
    <style type="text/css">@font-face {
    font-family: "Noto Sans Arabic";
    font-weight: 400;
    font-style: normal;
    font-stretch: normal;
    src: url("https://fonts.cdnfonts.com/css/noto-sans-arabic") format("opentype");
}</style>
    <defs>
    </defs>
    <text x="3.632000000000005" y="21.984" style="font: normal 400 normal 16px Noto Sans Arabic; white-space: pre; direction: rtl; unicode-bidi: bidi-override" fill="rgba(0, 0, 0, 1)">أية تقنية متقدمة بما فيه الكفاية</text>
    <text x="32.591999999999985" y="55.776" style="font: normal 400 normal 16px Noto Sans Arabic; white-space: pre; direction: rtl; unicode-bidi: bidi-override" fill="rgba(0, 0, 0, 1)">لا يمكن تمييزها عن السحر.</text>
</svg>

Which renders off the left side of the viewbox:

image

If I make the viewbox bigger:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="-200 0 400 200">
    <style type="text/css">@font-face {
    font-family: "Noto Sans Arabic";
    font-weight: 400;
    font-style: normal;
    font-stretch: normal;
    src: url("https://fonts.cdnfonts.com/css/noto-sans-arabic") format("opentype");
}</style>
    <defs>
    </defs>
    <text x="3.632000000000005" y="21.984" style="font: normal 400 normal 16px Noto Sans Arabic; white-space: pre; direction: rtl; unicode-bidi: bidi-override" fill="rgba(0, 0, 0, 1)">أية تقنية متقدمة بما فيه الكفاية</text>
    <text x="32.591999999999985" y="55.776" style="font: normal 400 normal 16px Noto Sans Arabic; white-space: pre; direction: rtl; unicode-bidi: bidi-override" fill="rgba(0, 0, 0, 1)">لا يمكن تمييزها عن السحر.</text>
</svg>

I can see the full text, each line is laid out right to left correctly, but clearly the positioning is wrong.

image

If I remove the white-space: pre; direction: rtl; unicode-bidi: bidi-override directives:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
    <style type="text/css">@font-face {
    font-family: "Noto Sans Arabic";
    font-weight: 400;
    font-style: normal;
    font-stretch: normal;
    src: url("https://fonts.cdnfonts.com/css/noto-sans-arabic") format("opentype");
}</style>
    <defs>
    </defs>
    <text x="3.632000000000005" y="21.984" style="font: normal 400 normal 16px Noto Sans Arabic;" fill="rgba(0, 0, 0, 1)">أية تقنية متقدمة بما فيه الكفاية</text>
    <text x="32.591999999999985" y="55.776" style="font: normal 400 normal 16px Noto Sans Arabic;" fill="rgba(0, 0, 0, 1)">لا يمكن تمييزها عن السحر.</text>
</svg>

Then the text is properly right-aligned, but the second line's punctuation is on the wrong end.

image

Is this a bug or am I doing something wrong?

@wolfmcnally
Copy link
Author

I also note that if I put this in the playground:

  <div style="direction: rtl; width: 200px;">أية تقنية متقدمة بما فيه الكفاية لا يمكن تمييزها عن السحر.</div>

It is laid out visible, but the punctuation is still on the incorrect end:

image

If I put direction: rtl; in the html tag to try to reverse the entire document, the Arabic text moves to the right side of the window, but the punctuation is still incorrect:

image

@chearon
Copy link
Owner

chearon commented Jan 13, 2025

Thanks for the debugging info. As you found out by looking at the playground, this is specific to the SVG painter, which I haven't tested as much as the canvas one. I think the direction property is putting the text to the left side of the paint coordinate. The direction and unicode-bidi properties are there only to force SVG to turn the text into glyphs that have the same direction as dropflow's internal glyphs. I can have that fixed soon.

The punctuation problem is similar, but for canvas. It needs to set ctx.direction before calling fillText. I don't know how I never noticed that before, so thank you for pointing it out! I have a way of painting glyphs to the canvas directly that will mitigate that entire class of problems in the long run.

(I hope it wasn't too much trouble to get a dropflow example running. I faced several pain points when reproing that; there's some rough edges to clean up with registering fonts at least.)

@wolfmcnally
Copy link
Author

Thanks for your response!

Yes, I had some trouble getting DropFlow to load in a browser environment with respect to loading fonts. I discovered I couldn't load the fonts and import DropFlow in the same file, so I had to break it up into a loader, which is called when my app starts up:

// loadDropFlow.ts

// LoadBuffer is implemented differently for my Jest/Node-based testing.
import { loadBuffer } from "./resourceUtils";

const dropFlowPrefix = import.meta.env.DEV
  ? ['node_modules', 'dropflow', 'dist']
  : []; // Assumes dropflow is in the dist directory in production

import setBundleLocator from "dropflow/wasm-locator.js";

setBundleLocator(async () => {
  console.log('Loading dropflow.wasm');

  return await loadBuffer(dropFlowPrefix, 'dropflow.wasm')
    .then((buffer) => new Uint8Array(buffer));
});

In my main startup resource loader, I import the above file, then do everything else.

// Resources.ts

// This must be imported before the DropFlow API.
import "./loadDropFlow";

import * as flow from 'dropflow';
import { loadCanvasKit } from "./canvasKit";
import { fontsData, loadFontsData } from "./fontsData";

export async function loadResources(): Promise<void> {
  await loadFontsData(['fonts']);
  await Promise.all(fontsData.map(fontData => flow.registerFont(fontData.buffer, fontData.placeholderUrl)));

  const canvasKitPrefix =
    import.meta.env.DEV
      ? ['node_modules', 'canvaskit-wasm', 'bin']
      : []; // Assumes canvaskit.wasm is in the dist directory in production

  await loadCanvasKit(canvasKitPrefix);
}

@chearon
Copy link
Owner

chearon commented Jan 15, 2025

The punctuation problem is similar, but for canvas. It needs to set ctx.direction before calling fillText. I don't know how I never noticed that before, so thank you for pointing it out!

Follow-up: I didn't notice because I mostly test in node-canvas, which auto-detects the direction, which is usually right. Browsers use the CSS direction on <canvas> instead of auto-detecting. Added ctx.direction to canvas here.

Yes, I had some trouble getting DropFlow to load in a browser environment with respect to loading fonts. I discovered I couldn't load the fonts and import DropFlow in the same file, so I had to break it up into a loader, which is called when my app starts up:

Yeah, sorry about that. Having WASM dependencies is a bit of a pain still, and that's the best way I could find so people could just (mostly) import * as flow from 'dropflow'. More documentation coming hopefully soon...

chearon added a commit that referenced this issue Jan 22, 2025
going to use `direction` and `textAlign` to address #27
chearon added a commit that referenced this issue Jan 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants