Is there a way to use textwrap with a font-sepecific line measure #311
-
SVG does not wrap text. (SVG2 does, but the specification is years from being finished.) I'm considering wrapping text for SVG in web browsers.
|
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 5 replies
-
Hi @NeverGivinUp, Great questions! In short, yes, you can use textwrap to wrap more than just terminal text. I made a crazy example here were I use the Put differently, the core wrapping code does not know about "words", it knows about let text = "Hello World!";
println!(
"{:?} -> {:#?}",
text,
textwrap::core::find_words("Hello World!").collect::<Vec<_>>()
); If you run this on the playground, you'll get
The
This simply says that we can break "Hello" as "Hel-" and "lo". The For your purposes, you would need to make your own struct which implements
The best you can do here would be to clone the repository and run
In other words, finding words wrapping them and turning the result into a
All dependencies are optional, so you can simply disable them with: [dependencies]
textwrap = { version = "0.13", default-features = false } This just gives you the core wrapping logic. This code is honestly not rocket science, but it might be an advantage that it provides a battle-tested structure for you to plug your own logic into. What kind of constraints do you have for your application which makes it important if you depend on a few KB more or less? |
Beta Was this translation helpful? Give feedback.
-
Please also see the discussion with @truchi here: #300 where we discussed using textwrap to deal with colored text in a terminal. The theme is the same: wrap some text with more information than just the text. |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot for the detailed explanations and the cute house building example , @mgeisler :) Allow me to clarify some the problem and some resulting requirements: Application sizeMy app runs in the user's web browser, which may be a mobile phone. Application size matters, because the app needs to be downloaded to the browser from the server. Possibly over a 2G or 3G phone connection. If we are taking about a few KB more or less, that is still fine. I was worried that hyphenation might use the order of magnitude of hundreds of KB or even some MB, which would rule out text wrapping in SVG. RuntimeThe app lays out a potentially big graph of tweet-sized text nodes (so 300 characters). To keep the layout visually smooth, the app must lay out the whole graph before it presents it -- even if it starts by presenting only a dozen nodes initially. To lay out the graph, the app must know the sizes of all nodes. The text of all nodes must be wrapped before the graph layout can be computed. My speed question really is about the number of calls to the measurement method: Is it called for each fragment, for each fragment and combination of a word, for each fit-test? Does it differ by language? E.g. for Arabic which, to my very limited knowledge, can combine letters to complex glyphs? So assuming that the measurement method runs in linear time -- it takes x*n seconds to measure a text of n characters -- how long will text wrapping with hyphenation take? MeasurementHyphenation and variable width fonts are necessary for putting the maximum amount of text in a given space -- at any given font size. Using super- and subscripts is essential for clear differentiation of content and notes. For text-fitting it can be considered equal to using smaller font size for some characters than for others. So we are dealing with text, set in variable width fonts, set in variable font sizes and with variable font weights.
The Non-separatablesSuper- and subscripts must not be separated from the words before them. Can you give an example/inspiration on how to communicate that to hyphenator and the textwrapper? |
Beta Was this translation helpful? Give feedback.
-
Hi @NeverGivinUp, thanks for the background information!
This is basically up to you. As an example, the The sketch in
For console text (which is not your use case), the text is not measured differently depending on the language — the
The time complexity of
This is done outside of the main wrapping code, see the
Yes, you're right, this is not super nice for variable width fonts! I've been playing with rendering text on a HTML canvas, and I simply worked around this by multiplying the width of each fragment by 10 or 100. Could you try this as a work-around? You'll of course also need to multiply the line width by 100 in this case. I'll be happy to make this more flexible in the next version, I suppose the type of the widths could be generic. For terminal applications, a Another problem I found by scaling up the fragment widths is that the defaults used by Be sure so select the
You would need to take this into account when you form your fragments. For console text, we simply split on |
Beta Was this translation helpful? Give feedback.
-
This all sounds very good. Thanks a lot :) I will try using multiplied values for a start. But generifying clearly is the superior solution, that does not add artificial inaccuracies to the measurements. Regarding the constants: |
Beta Was this translation helpful? Give feedback.
-
Yes, I agree! As you can tell, Textwrap is born out of the world of terminal text. It was only with version 0.13 that it became possible to wrap other things via custom I've just put up #310, which contain an example of using Textwrap for wrapping text with I'll flesh out #310 a bit more before merging it, but in short, I reimplemented the relevant parts of #[derive(Debug, Copy, Clone, PartialEq)]
pub struct CanvasWord<'a> {
word: &'a str,
width: f64,
whitespace: &'a str,
whitespace_width: f64,
penalty: &'a str,
penalty_width: f64,
}
const PRECISION: usize = 10;
impl core::Fragment for CanvasWord<'_> {
#[inline]
fn width(&self) -> usize {
(self.width * PRECISION as f64) as usize
}
// ...
} and later in let line_lengths = |_| width * PRECISION;
let wrapped_words = core::wrap_first_fit(&canvas_words, line_lengths); It's a hack for sure, but it seems to work okay in the little demo I wrote.
The width of the fragments is computed on the fragment as a whole — not on a per-character basis. Does that help answer your concern? If you're thinking of how kerning changes the width of characters depending on neighboring characters, then you're completely right that this isn't taken into account right now. The assumption is that we can measure the fragments individually and then simply put them into lines. I believe (hope) kerning will only cause minor changes in the widths, so that we can basically ignore it for the purpose of line breaking — though the layout engine which renders the text should still take it into account. As for the penalty, the idea (for terminal text) was to make it so large that overflowing a line by even 1 column is so expensive that it never happens. For text in other contexts, one could actually imagine that one would allow a tiny bit of overflow if this results in much better line breaks on the other lines. Like an overflow of 3pt might be preferably to leaving a gap of 30pt at the end of the line.
Not entirely, it's an additional penalty for hyphens: with the value of You can test this by running $ cargo run --example interactive --all-features 'Linear time complexities.' and then adjusting the width to 16 characters. You should see
If you adjust
Yeah, I want to make the penalties adjustable at runtime — and I should let the |
Beta Was this translation helpful? Give feedback.
-
I've converted the issue to a "discussion" — that should let us continue talking in a more tree-like fashion instead of a long linear string of comments. |
Beta Was this translation helpful? Give feedback.
Yes, I agree! As you can tell, Textwrap is born out of the world of terminal text. It was only with version 0.13 that it became possible to wrap other things via custom
Fragment
implementations.I've just put up #310, which contain an example of using Textwrap for wrapping text with
f64
widths. The text in question is drawn on a HTML canvas using WebAssembly — the result is live at h…