Skip to content

Monday, 5 August 2024

Here's the relevant part of the new code that I have been working on in kis_tool_freehandhelper::paint :

if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING
            || m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING){
         // initial case where there isn't any lastDrawnPixel yet
         if(!m_d->hasLastDrawnPixel){
            currentPixel = info.pos();
            m_d->waitingPixel = currentPixel;
            paintLine(m_d->previousPaintInformation, KisPaintInformation(m_d->waitingPixel));
            m_d->lastDrawnPixel = m_d->waitingPixel;
            m_d->hasLastDrawnPixel = true;
            m_d->pixelInLineCount = 1;

        } else {
        // once there is a last drawn pixel 
            if (abs(currentPixel.x() - m_d->lastDrawnPixel.x()) > 1.5 || abs(currentPixel.y() - m_d->lastDrawnPixel.y()) > 1.5) {
            // current pixel is too far, draw the waiting pixel
                paintLine(m_d->lastDrawnPixel, KisPaintInformation(m_d->waitingPixel));
                m_d->pixelInLineCount += 1;
                m_d->lastDrawnPixel = m_d->waitingPixel;
                m_d->waitingPixel = currentPixel;
            }
            // check axis, if the currentpixel is in the same axis as the lastdrawnpixel, we can draw waiting pixel otherwise
            if (m_d->pixelInLineCount > 2 && (currentPixel.x() == m_d->lastDrawnPixel.x() || currentPixel.y() == m_d->lastDrawnPixel.y())) 
                    m_d->olderPaintInformation = m_d->previousPaintInformation;
                    //draw 
                    paintLine(m_d->KisPaintInformation(m_d->waitingPixel),KisPaintInformation(m_d->waitingPixel))
                    m_d->lastDrawnPixel = m_d->waitingPixel;
                }
                //otherwise just change update waiting pixel without drawing it
                m_d->waitingPixel = currentPixel;
                m_d->pixelInLineCount = 0;
            }
            // Enable stroke timeout only when not airbrushing.
            if (!m_d->airbrushingTimer.isActive()) {
                m_d->strokeTimeoutTimer.start(100);
            }
        }

Before a super glaring problem was that the algorithm made drawing very non-continuous, you barely get to see what gets drawn. When currentPixel gets too far from lastDrawnPixel, we have an infinite gap that doesn't get filled. In response, I took a page from Tom Cantwell's implementation, and if the currentPixel gets too far from the lastDrawnPixel, we draw the waitingPixel, except we draw the entire line from lastDrawnPixel to waitingpixel instead because only drawing the waitingpixel caused a lot of undrawn gaps likely due to mouse speed or the refresh rate of stuff getting read. Also if you notice, I made it so that the distance between currentPixel and lastDrawnPixel > 1.5 instead of > 1.0 because with just 1.0 we were not giving enough room to actually wait for the waitingPixel to be assessed and drawn if it's not a corner pixel.

In the context of waiting, I also added a pixelInLineCount so that we compound at least 2 pixels read before actually going through with the axis check because at less than two pixels read we either haven't drawn anything or we have only drawn one pixel and there can't be a complete check for a corner until we reach a scenario where there are three coordinates to check i.e. when we get to simulate the waiting pixel *lagging* behind currentPixel in an attempt to compare them and then see if it needs to be drawn.

There seems to only be one scenario left where the corner gets drawn, which also has to do with the fix we implemented. My running theory is that it has to do with how the line gets filled because sometimes although most lines will just be pixel-perfect with this line fill, we reach the middle of the Venn diagram where the mouse speed is just fast enough that we run into the > 1.5 distance check and so the line drawn isn't going through the axis check but then its also just slow enough where it draws exactly a horizontal into a vertical line between two close pixels.

Sunday, 4 August 2024

I like to call myself git “expert”, but I failed pretty badly few weeks ago I needed to bisect the kernel source code to figure out one bug.

General git bisect process is, You have two known commits, one is good and one is bad, and you keep searching for bad commit by splitting history in two. Lets say you have following changelog (not the actual changelog I was debugging, but example):

Saturday, 3 August 2024

KStars v3.7.2 is released on 2024.08.03 for Windows, MacOS & Linux. It's a bi-monthly bug-fix release with a couple of exciting features.

A few members of the KStars development team were enjoying their summer holidays over the past few weeks, but we still have a couple of exciting features in this release!

Multi-Camera Support

Wolfgang Reissenberger devoted countless hours to bring this complex feature that required lots of architectural changes in Ekos. If you have double rigs (or even more), we have a new great feature: multi camera support! If you have more than one optical train on your mount, you now will be able to run capture sequences for each of your optical trains in parallel from the same KStars instance. You simply need to create an optical train for each of your telescopes on your mount, create camera tabs for each optical train, create their own sequence and let them run in parallel.



This new feature may also be used in combination with the scheduler: in this release, the scheduler itself will control the first camera. On top of that, you could configure additional cameras in the capture tab which could execute their own capture sequences. Enhancing the scheduler such that it could control multiple optical trains in parallel is already under development and will be part of one of the next releases. So stay tuned!

Focus Advisor v4


John Evans refactored the Focus Advisor to make it simpler while still offering a lot of insight on parameters tuning. It is now more accessible to new users and has added functionality to optimize values for 2 of the more difficult Focus parameters: Step Size and Backlash (or AF Overscan).




In addition, Focus Advisor contains a convenience tool to locate stars by searching the range of motion of the focuser and another convenience tool to highlight differences between current Focus parameter settings and those recommended by Focus Advisor.

Akademy 2023 was almost year ago and soon Akademy 2024 is coming. This is probably good time to talk about Akademy 2023. I know it was in way too past but some of those who know me personally/professionally know that last year has been rollar-coaster for me emotionally and did not manage to write about event while recovering from it.

But since this post was sitting in draft from long time, it is finally time to finish and publish it.

Friday, 2 August 2024

The Amarok Development Squad is happy to announce the immediate availability of Amarok 3.1 "Tricks of the Light"!

Amarok 3.1 player window with a customized layout, featuring the new Similar Artists applet

Coming three months after 3.0.0 and two months after the first bugfix release 3.0.1, the main development focus in 3.1 has been getting Qt6 / KDE Frameworks 6 based version closer. We are not quite there yet, but not that far away anymore. And there are some quite nice new features too! Amarok 3.1.0 brings in a refreshed Last.fm integration, which uses more up-to-date account connection mechanisms, and is better at informing users of any Last.fm errors. Similar Artists context applet does a comeback, and there's naturally also a nice bunch of smaller features and bug fixes; this time the oldest fulfilled feature request was filed just under 15 years ago.

Changes since 3.0.1

FEATURES:
  • Last.fm plugin updated to use token-based authentication method and to notify user of session key errors (BR 414826, BR 327547)
  • Reintroducing Last.fm Similar Artists context applet - a new Amarok 3 version
  • Remember the previous destination provider when saving playlist (BR 216528)
CHANGES:
  • Amarok now depends on KDE Frameworks 5.89.
  • Cleanup of unused code and various changes that improve Qt6 compatibility but shouldn't affect functionality. (n.b. one won't be able to compile a Qt6 Amarok with 3.1 yet, but perhaps with the eventual 3.2)
  • Remove old derelict openDesktop.org integrations from about dialog. This also removes the dependency to Attica framework.
  • Disable gapless playback if ReplayGain is active and the following track is not from same album, to avoid volume spikes due to delay in the applying of ReplayGain (BR 299461)
BUGFIXES:
  • Small UI and compilation fixes
  • Fix saving and restoring playlist queue on quit / restart (BR 338741)
  • Fix system tray icon menu reordering
  • Fix erroneous apparent zero track progresses on track changes, which caused playcount updates and scrobbles to get skipped (BR 337849, BR 406701)
  • Fix 'save playlist' button in playlist controls
  • Sort playlist sorting breadcrumb menu by localized names (BR 277146)
  • Miscellaneous fixes to saving and loading various playlist file formats, resulting also in improved compatibility with other software (including BR 435779, BR 333745)
  • Don't show false reordering visual hints on a sorted playlist (BR 254821)
  • Fix multiple instances of web services appearing in Internet menu after saving plugin config.
  • Show podcast provider actions for non-empty podcast categories, too (BR 371192)
  • Fix threading-related crashes in CoverManager (BR 490147)

In comparison to changes between 2.9 to 3.0, the git repository statistics between 3.0 and 3.1 are somewhat short:

Tuomas Nurmi: 164 commits, +11910, -29957
l10n daemon script: 97 commits, +207075, -237380
Pino Toscano: 13 commits, +1, -11152

However, this is an excellent spot to send a huge thank you out to everyone who has been on board outside the git history; translators, packagers, bug reporters, writers, commenters, and of course: users - music fans all around the world! Happy listening, everyone! You rok!

Getting Amarok

In addition to source code, Amarok is available for installation from many distributions' package repositories, which are likely to update to 3.1.0 soon. A flatpak is currently available on flathub-beta.

Packager section

You can find the package on download.kde.org and it has been signed with Tuomas Nurmi's GPG key.

Tuesday, 30 July 2024

It’s been a while since my last blog post regarding text. Since then I’ve been working on the on-canvas text tool, as well as multiple reworks for rich text editing, the actual text properties docker for this rich text editing, and finally I’ve done a talk at the Libre Graphics Meeting about my work on the text tool.

I’m now at the point that I’m going over each property and thoroughly polish it. Because I’m also doing frequent updates on the krita-artists forum, I’m hoping to punctuate each polish session with an introduction to the property, and because I also have a lot of technical things to talk about, I’ll be making technical blog posts alongside that, of which this will be the first.

So the first thing that needed to be tackled after putting together the basic text properties docker and the related interaction is font selection. Krita’s text tool is based on SVG+CSS, and uses FontConfig to select fonts. Typically, a font selection widget will show the list fonts, and in some cases, it organises this in two dropdowns, where the first is the font family, and the second a sub family, like italic or bold. So obviously there’s meta data for this, right, and you should just plug that in the right places, and everything’s peachy? Well, we do have a lot of meta data…

Family Relations

For digital fonts, the OpenType format (in both ttf and otf flavours), is the most common digital format. For formats older than it, the family relations are usually limited to regular, italic, bold and bold-italic (‘RIBBI’), but OpenType also allows for weight/width/slant (‘WWS’) organisation, or even a completely arbitrary organisation under a single typographic family. All at once, too, because not all programs have the same font selection features. These are stored in OpenType names 1, 2, 16, 17 and 21, 22. You can model their relationship as a tree, as in the following example, where we have a single typographic family with a sans, a serif, both of which are WWS families, and each has a variety of RIBBI subfamilies, some of them (semibold) being a single font:

  • Typographic family (ids 16, 17)
    • Sans (WWS family, ids 21, 22)
      • Regular (RIBBI, ids 1 and 2)
        • Regular
        • Italic
        • Bold
        • Bold italic
      • Condensed (width variant)
        • Regular
        • Italic
        • Bold
        • Bold italic
      • Semi-bold
        • Regular
    • Serif
      • Regular
        • Regular
        • Italic
        • Etc…

This is of course not only stored in the names, it is also stored in the OS/2.fsSelection flags, and for WWS, there’s width and weight data in the OS/2 table. However for typographic family, there’s no way to identify separate WWS families besides the WWS name being present (besides a bit flag in fsSelection, which indicates there’s only one WWS family, but this too cannot be relied on). Furthermore, variable fonts don’t have subfamilies, but rather “axes”, and perhaps some “instances”, which are internal presets of those axes.

And that’s not all, not all fonts are required to have this data, only fonts that are not sufficiently described without all names present, so the default font of a given font family only needs names 1 and 2 to be present, the semibold only names 1, 2, 16 and 17, and so on.

FontConfig is somewhat build to handle this, the default ordering ( undocumented , of course) of the font family names being WWS, Typographic and finally the RIBBI family name. However, The WWS family name is quite recent, meaning there’s many fonts that only have a typographic and RIBBI name, despite having a difference in, say, optical size data, or one of those layer typefaces.

This works because many of these font selector widgets don’t select a family, but rather, they present a bit of ordering for you to select a font and finally store a specific identifier to that font, like the PostScript name, in the text object. Because we’re using CSS however, we store the font family, and specify the weight, width and slant. This has its benefits, as when a font is absent, we can still infer the intention that something was to be set bold or italic. But that does require that the font can be selected by family at all, so if FontConfig cannot associate a WWS family, that is kind of a problem.

Finally, some fonts have a STAT table, which gives even more info about those axes (if a variable font) and allows for non-variable families to describe their relations in an axis like manner. There’s no API to retrieve this info in Harfbuzz, however, FontConfig knows nothing about it either, and even the CSS working group hasn’t made any statements on whether to interpret the STAT table at all. Mind you, even with api for the STAT table, too many fonts don’t have it, so it is not a solution in itself.

Family Reunion

So, the best way to go about this is to sort the font families. This will require opening each font file up with FreeType or Harfbuzz, and retrieving the data you need, as fontconfig doesn’t store everything we need to identify the different families.

For this purpose, I created a struct to put the data in, and organized the structs inside KisForest, which is a templated container class that allows storing data in a tree, and provides a bunch of itterators to traverse said tree. This allows me to create a top level node (‘typographic family’ node) for each font as I find them, and then sort fonts into those. Then afterwards, go over each node again and sort them into individual WWS families, as WWS family names are in fact kind of rare, and the majority of fonts that need them don’t have them.

The second sort is done by going over each toplevel typographic node, and then take all the children. Of the children, you first select all “regular” fonts (the ones closest to width: 100%, weight: 400% and no italic or slant), and adding those first, each with their own WWS family, and then sort the rest into those. Care will need to be taken for fonts that have different optical sizes identified as well (there’s, of course, four ways this can be stored: OS/2 optical size range; size OpenType tag; ‘opsz’ variable axis and STAT table axis), as well as keeping track of situations where multiple formats of the same font are installed (The Nimbus family on many Linux distributions is often installed as OpenType font as well as two separate Postscript fonts, I’m currently sorting those into the same font family). For bitmap fonts, you want to sort the separate pixel sizes into the RIBBI family, depending on how you interpret bitmap pixel size.

Once that’s done, the CSS font matching algorithm needs to be implemented. CSS is explicitely vague about what it means by a font family (this whole blog has assumed up till now that if a give subfamily cannot be selected with CSS parameters, it needs to be in a separate WWS family), but it does specify that any localized names should be matched (localized names are rare and only really used for CJK fonts, but they do exist). So in practice, you end up testing all the possible names, that is, OpenType ids 1, 16, and 21, in the order of the lowest child node to the parent (because remember, the most default version of a given family only has id 1, so you want to test that first). Then comes the actual style testing, the algorithm of which is more or less the same along width, weight and slant, with weight being special for having a default range to test first, while slant needs to be multiplied by -1 first, which it needed anyhow to cohere the specs (CSS dictates that positive slant should skew to the right, while the OpenType spec requires negative slant to skew to the right).

After all of that, the filenames that rolled out of matching can be added to the FontConfig search pattern to prioritize them in the regular fallback search, which I am very thankful of.

A decorative red text that looks as if snow had fallen on it, saying "Some fonts are meant to be used layered..."
While nowadays an example like this would be best off using a color font, there’s many examples of older fonts that are meant to be used layered (as in, two text shapes overlapped with the same text but different subfonts). This particular font, Sweetie Summer, predates any discussion about WWS, only having a typographic family and ribbi family, which lead it to be unselectable with fontconfig.

Presentation

But getting the matching to work for odd fonts wasn’t the only thing that was necessary. Fonts also needed to be displayed nicely. Even more, I really wanted to integrate them with Krita’s resource system, as that would allow tagging and search. These are important usability features as modern operating systems come with hundreds of fonts, so being able to tag fonts with “cursive” and “display” and then filter on that can make it much easier to find an appropriate font. Not all design programs have this feature, which has led to a number of designers to use a so-called font manager, which effectively allows installing and deinstalling fonts by user-defined group (KDE Plasma even has one of these build in, and I’d be suprised if there wasn’t one for Gnome somewhere). Inkscape has quite recently introduced font collections, whose purpose is similar, and given we spend 2 years reworking our resource system, which can do exactly this, it made sense to try and get this system working.

There’s some quibles however: vast majority of resources within Krita are tied to a file, while such a font family resource is an abstraction of a collection of files. This results in problems with getting a preview generated as well as updating an entry between restarts of Krita.

Then there’s selecting the style. This one is a bit abstract, so bear with me: So, as explained before, CSS selects the font file to use by using the user-defined font-family and a set of parameters (width, weight, slant, etc). This has both the benefit of having a certain intent (whether the text is condensed, or the weight is set heavy), as well as being a good abstraction that encompasses both regular font families and variable fonts.

This abstraction is implemented as each font family resource having a set of axes (for variable fonts these are the axes in the font, for non-variable, these are an accumulation of the different parameters associated with the subfamilies), and styles which form a sort of preset for those given parameters (encompassing the instances of variable fonts, and the actual subfamilies in non-variable fonts). This way, you can have fonts that use the OS/2.fsSelection bitflags for indicating bold and italic, you can have fonts that use the OS/2 table values, you can have fonts that have variable axes, and all these will have the same toggles in the same place. If in the future the STAT table is going to be read, the extra info for that will easily be integrated in the same system.

There’s some more toggles than the WWS parameters though, for example, toggles for synthesize slant and bold. Some people think that nowadays, these are not necessary, but that’s really only true for scripts with few glyphs. In fact, we had to implement synthesized slant and bold because CJK users were having missing it dearly in the new text layout. On the flip side, in European typesetting, synthesized versions are considered ‘dangerous’ as it can be hard to tell if the correct bold version was selected, so a toggle to turn it off is required. This needed some extra work with variable fonts, as active slant and italic axes are not testable in the usual manner. There’s also optical size, though this is only supported for variable fonts that have an ‘opsz’ axis, as the CSS Working Group doesn’t seem to have an opinion on the other three ways optical size can be indicated. Finally there’s the remaining axes, in case of a variable font with extra custom axes.

So, this sounds to work right? Where’s the issue with styles? Well, some might say that the split between a font-family and the style is unnecessary, it would be much better to just see all the styles at once. In fact, Inkscape has been implementing this recently. Which means I’ll be asked why I didn’t implement that, because obviously I should.

The main problem here is a philosophical one. The reason Inkscape is implementing this is because its user base wants this UI, and the reason the user base wants this UI is because other software they’re using has this UI. So far, so good.

However, other software has this UI because it has a different kind of text layout system. As noted before, the font selector in these programs is just a fancy way of selecting a specific font file. Within programming way of saying “select font file XYZ” is considered an ‘imperative’ way of programming. This is quite common in WYSIWYG (what you see is what you get) editors, as its easier to program. Markup based methods like CSS instead has a ‘declarative’ way of programming: “Select from this font family a font with these parameters, and otherwise these other font families”. A system like this usually tries to infer properties, which is harder to program, but also means less properties need to be set.

The philosophical difference here is that I don’t think it is wise to try to abstract away the underlying data structure, because I think it leads to bugs that are kind of hard to articulate if you don’t know the UI is not representing the underlying structure properly. This is also why it is weird to see people go “Well, UI should be completely decoupled from the business logic”, because even if you programmatically decouple UI and data, the fact remains that the UI can only do what the data structure allows. The data structure by itself belies a workflow, which in the case of mark-up based type setting general, is one that focuses on consistency, and that if you want to make a small modification, only a small modification is stored.

The importance of the underlying data structure is something that I always feel is missing from discussions about the UI of Free Open Source Software, which is why I am emphasizing it now. The main idea of “listening to your users” is not bad, and even for my own work I did talk to other artists on KA to try to get a feel of what artists using Krita prioritize. But text in particular is also far more tricky than this, because the main reason both the Inkscape folks and us went with an SVG+CSS based text layout is because it is a specification that is widely used (just not in graphics programs…) and has a lot of thought put into multi-lingual and non-European text handling (which is still quite rare today). And I think it is going to cause trouble if you try to apply UI conventions that belong to a different kind of text layout.

The main issue I foresee with this approach is that there’s no mind paid to font family fallback. In an online situation, font family fallback is mainly for what to do when a font family can be found. In a local situation like a graphics program, it is mostly useful in multi-script situations. Many fonts only have glyphs for a small subset of Unicode, often limited to a single script with supplementary punctuation and numbers. So in multi-script situations the CSS font matching algorithm requires you to check if a glyph can be represented, and if not, you must check other fonts on the system till you find one with the given glyph. Font family fallback allows you to have some control over this mechanism.

Several instances of a mix between Arabic and Latin text, which different fonts being chosen for the Latin and Arabic
The font family list allows us to control the fallback. Many fonts only have glyphs for a subset of unicode, so controlling fallback can allow us to select fonts that seem to be in a similar tradition, like using a Serif Latin font for a Naskh Arabic font.
More samples of mixed Latin and Arabic text, with each sample using a different Latin font in combination with a Nastaliq Arabic font.
Not all scripts have similar traditions, so control over the font fallback is also useful in selecting a font that may not fit within the same tradition, but might look good in terms of contrast, so the Latin text, in this case, stands out less.
A partial view of the Krita canvas with on the left the canvas showing mix of Arabic and Latin text, and on the right the text properties docker, showing font fallback widget with Noto Nastaliq Urdu for the Arabic and Cinzel for the Latin. Font style settings are also shown, showing weight to be at 850, and synthesize bold turned off.

Another thing that’s kind of difficult here is that it hides the fluidness of variable fonts. Because where before we could treat instances as a sort of preset for the parameters, they now are presented as a whole font to select. To further explain, one thing I’m fully expecting to happen for Krita is that we receive a bug report with “I turned off synthesis for weight, but still Krita is showing something for a weight value that doesn’t correspond to a style”, and I’ll have to reply with “Yes, that’s because you’re using a variable font”, expand on that, and then close the bug as RESOLVED, NOTABUG. By focusing too much on the styles as individual fonts to select, we’re inhibiting people from updating their mental model of how fonts can work.

Visuals

Of course, because there’s so much variation in what the different fonts can do, it is necessary to indicate the capabilities of a font. Many font selectors have at the very least an icon for the font type. Because there’s been a flurry of activity within OpenType in the last decade, there’s now also variable fonts to keep an eye on, and four (five?) different ways to do color font representation, and those can all be present at once. Krita only really supports the Bitmap and ClrV0 implementations, so we need to indicate which color font data is present.

Font selector dropdown, showing 4 fonts, each font has its title, a big preview using a sample text ("AaÃáZz") and then some icons to indicate what kind of font it is, and whether it has color (as with the last font, Bungee Color).

Other than that, the font name should be present. As well as a preview for the given font. We could technically do the latter straight up with the text layout, by putting a KoSvgTextShape inside a QQuickPaintedItem, but I worry that might be slow on some systems. Instead, I’m laying out the text and converting the result to regular paths, storing the SVG, and then painting the path from that within a QQuickPaintedItem. The sample text chosen uses FontConfig supported languages list, though I am wondering if we’re not better off testing CharMap support instead, as to ensure we will always have some glyphs from the font available. Anyway, I’m quite pleased with the result, as it allows us to display the sample nice and sharp.

One thing that is also tricky is the localization. Basically all user-facing strings within OpenType can have localized variants within the font, and if those are present they should be displayed. In practice this means that these localized strings get stored in the KoResource as well. The models for tracking the styles and axes receive a ‘setLocales’ function, so that when the font name is requested within QML, the text label will receive the localized name. However, with the resource model this isn’t feasible, as the font family resource is the only one that holds localized names. Thankfully, a QVariantHash is treated as a javascript object/dict within QML, so the localized names could be stored into the metadata QVariantMap (note that QML does not support converting QVariantHash to a dict(!), and then tested against the KLocalizedString::languages() stringlist (though care must be taken to ensure the underscore is replaced with a dash).

Eventually, we’ll prolly need to do the same with the writing system samples. However, that should probably use the language the text shape is tagged with, as it is very common for multi-lingual artists to keep the software in English, so they won’t have to guess at a translation when doing an internet search for a given label. So in those cases where you’d need a different sample (like, a Arabic sample if you’re typesetting Arabic), it is probably combined with a different language being set on the text. Mind, there’s no language selector yet, because SVG+CSS uses BCP47 language tags, and I haven’t figured out how to capture the richness of those tags in a discovery friendly ui. Being able to limit the visible fonts based on whether fontconfig thinks they support the active language would also be useful in the same vein.

FontConfig Rescan Interval

When discussing the architecture of how to implement this, it was mentioned that FontConfig has a rescan mechanism. This is basically FontConfig checking if any changes had happened, and if so, updating the font list, and the default on most Linux systems for this is 30 seconds. I think most programs just turn this off, but the person I was talking with went “oh, yes, this is how you need to implement this”, which was a little confusing because our resource system doesn’t actually support refreshing resources during a session. I ended up implementing what they asked of me, as a show of good faith, but there’s multiple refresh problems with it (because our resource system was not build to handle this). It will probably be disabled in the end, unless the refresh problems turn out to be trivial to tackle.

Postamble

A returning theme in handling fonts, OpenType fonts in particular, is that there’s at the least 3-5 ways of doing one common thing. This is not unusual, and often happens when people need a certain function to be part of a specification so badly that there’s no time to standardize. Because of this complexity though, implementing a good font selector still took about a month. At the same time, I’m happy I spend time on this because it would otherwise hang like a thunderstorm over the whole text project, as it would be a matter of time we’d drown in bug reports about this-or-that font not being selectable. That is still going to happen, but at the least there’s a framework to deal with edge cases now.

The next topic I’ll be tackling is probably going to be font size and line height.

Appendix

TTF vs OTF

So an OpenType font should be in a file called the Open Type Format (otf), right? Then why are most of them in the True Type Format (ttf)? Aren’t these two the same?

ttf and oft are the same format, yes. The original spec was called TrueType, and the glyphs were outlined with quadratic bezier curves. One of the things that was added when the spec became OpenType, was that the glyphs could now also be outlined in CFF (compact font format, PostScript, basically), which uses cubic bezier curves. Since then, a font stored in a ttf file is an OpenType font with quadratic bezier curves, and a font stored in an otf file is a file with cubic bezier curves. I am unsure whether since the introduction of variable fonts this difference isn’t purely conventional however.

Italics and Obliques

Because blogpost is aimed at readers that are probably not typography (or lettering/calligraphy) nerds, let’s speed through the history of Latin script as to explain the difference between Italics and Obliques and why they’re sometimes confused:

The history of Latin script is basically the existence of a formal style of writing (a ‘ductus’), and then because clerics need to write a lot, them developing a less formal style that’s easier to write. That one then formalizes, and then a new style is developed. So if we start with Roman Square Capitals (like Trajan), it is followed by a variety of less formal styles like Uncial and Rustic Capitals. Around the middle ages however, the formal ductus and less formal ductus are unified into one system of capital (‘upper case’, also called ‘majuscule’) and miniscule (‘lower case’) letters. However, clerics needed a faster way of writing, so a given blackletter style would often be developed into a chancery or court style.

Fast forward to the Rennaisance. Italians are making their first printing fonts. For reasons I don’t want to get into, they choose Roman Square Capitals for the capitals of their typefaces and combine those with Carolingian miniscules. But they want more typographic richness, so the popular Italic chancery hand is turned into a printing font as well. There’s some notable difference between Carolingian miniscules and Italian miniscules, in particular, the ‘a’ and ‘g’ are written differently, as is the ‘f’:

Image showing handwritten text. Top says "fang (italic)", in an Italic ductus, bottom says "fang (carolignian)" in a Carolingian ductus. There's a subtle color gradient over the lines, which each seperate stroke having a different hue.

Then, much later, in the nineteenth century, Sans-serif fonts were introduced, which find their origin in sign-painting. For some reason, these fonts don’t use an Italic ductus for their corresponding slanted style, in fact, the slanted style is just that: slanted (an ‘Oblique’). My best guess is that this is because this font style comes from sign painting and thus is optimized for legibility, and frequently uses an Italic ‘g’, combined with a Carolingian ‘a’ for this purpose. So they may have considered a simple slant much more distinguishable than trying to create a full Italic ductus compatible variant.

By the time computers get involved, a slanted version of the font is considered paramount, as many academic style guides require Italics to indicate quotes and book names. By this time, Oblique variants were considered acceptable, which is why ‘synthesized italic’ just ends up being a digitally slanted version of the original. Fonts that combine both a rare, but exist, so OpenType specifies an extra bit to indicate that a font is specifically an oblique version, but it seems that this didn’t catch on. Even now, with OpenType variable fonts and the STAT table making it much easier to define a slanted version, there’s still hesitance to use it as nobody knows which software supports selecting it.

Furthermore, many other writing systems use various script styles to indicate quotes and such, yet, these are not recognized as ‘Italics’ by computer software. There’s therefore something very arbitrary about Italics by themselves: There’s nothing stopping a font designer from creating a black letter or copperplate style for their font family, except how to handle the font files so that software can select these.

Slant

Slant, within variable OpenType fonts, is a different toggle from Italics. And it can go either way, similarly, there is such a thing as a ‘upright Italic’. So that begs the question: why are Italics usually slanted? This has to do with calligraphy, in particular, the reach of the right hand.

Schema showing how the right hand has a range from bottom left to top right.

Because the right hand has a reach that goes from top-right to bottom-left, it is likely to skew vertical lines to the right.

If you write with a left hand the same as with a right hand, you get the same effect, but flipped.

Illustration of a left hand holding a pen, followed by the word "lefthanded" handwritten. There's a noticable skew toward the left.

For lefties like me, to do right-slanting calligraphy we need to either write with our hand over the line, or rotate the paper.

Several variations of the text "righthanded?", each with a subtle skew to the right. The top is annotated with "(tilted paper)", the middle is annotated with "(mirrored canvas)" and the bottom is annotated with "(overhand)".

Now, for European scripts, this is usually for italics, but sometimes a slant doesn’t express a calligraphic quirk, but rather a feeling of forward motion. Which is why for right-to-left scripts, you will sometimes see a left-leaning font being used.

Saturday, 27 July 2024

Future 聊天室

功能

账号管理

  • 实现登录、注册、注销
  • 实现找回密码(提高)

好友管理

  • 实现好友的添加、删除、查询操作
  • 实现显示好友在线状态
  • 禁止不存在好友关系的用户间的私聊
  • 实现屏蔽好友消息
  • 实现好友间聊天

群管理

  • 实现群组的创建、解散
  • 实现用户申请加入群组
  • 实现用户查看已加入的群组
  • 实现群组成员退出已加入的群组
  • 实现群组成员查看群组成员列表
  • 实现群主对群组管理员的添加和删除
  • 实现群组管理员批准用户加入群组
  • 实现群组管理员/群主从群组中移除用户
  • 实现群组内聊天功能

聊天功能

  • 实现查看历史消息记录
  • 实现用户间在线聊天
  • 实现在线用户对离线用户发送消息,离线用户上线后获得通知
  • 实现在线发送文件
  • 实现在线用户对离线用户发送文件,离线用户上线后获得通知/接收
  • 实现后台发送文件
  • 实现用户在线时,消息的实时通知
    • 收到好友请求
    • 收到私聊
    • 收到加群申请

其他

  • 使用 C++编程语言
  • 使用 I/O 多路复用完成本项目
    • C++:Epoll ET 模式
  • 使用数据库完成数据存储
    • Redis 和 mysql
    • 历史消息采用redis做告诉缓存,mysql来存储大量历史消息
  • 数据库中数据的存储和取用使用序列化和反序列化完成(Json)
  • 支持大量客户端同时访问
  • 实现服务器日志,记录服务器的状态信息
  • C/S 双端均支持在 CLI/Web 自行指定 IP:Port
  • 实现具有高稳定性的客户端和服务器,防止在用户非法输入时崩溃或异常
    • 实现 TCP 心跳检测

遇到的问题

  • 在客户端因某些原因异常退出时,服务端将无法正常处理其他请求.

    • 开始时服务端没有将新连接的描述符设置为非阻塞模式,导致客户端异常退出时服务端的接收recv一直在阻塞着读取,且无法处理其他请求
  • 如何往线程池中传入成员函数.

    • auto task = [](){     类的成员函数 } m_pool.submit(task);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30

      * `Tcp`传输为字节流传输,采用在每条消息前面加上`长度`来正确接收每条请求.
      * 在代码中往`redis`数据库中添加好友申请数据时,没有正确插入.
      * 由于好友申请当中包含时间,姓名等信息,中间含有**空格**,在redis的哈希表中无法插入.
      * 经过查阅在redis哈希表中,**空格**可以用`+`代替,在遇到`+`时会被转义为**空格**,或者存储为**十六进制**,但是这种方法在终端中的**空格**为灰色.

      * 在聊天界面如果输入带有`%`,服务器直接挂掉.

      * ```c++
      int redisAsyncContext::Lpush(const std::string &key, const std::string &value)
      {
      //std::string cmd = "lpush " + key + " " + value;
      this->m_reply = (redisReply *)redisCommand(this->m_connettion, "LPUSH %s %s", key.c_str(), value.c_str());
      // 检查 m_reply 是否为 NULL
      if (this->m_reply == NULL) {
      std::cerr << "Error: redisCommand returned NULL" << std::endl;
      return -1; // 或其他合适的错误值
      }

      // 检查 m_reply 类型
      if (this->m_reply->type != REDIS_REPLY_INTEGER) {
      std::cerr << "Error: Expected integer reply" << std::endl;
      freeReplyObject(this->m_reply);
      return -1; // 或其他合适的错误值
      }
      int num = this->m_reply->integer;
      freeReplyObject(this->m_reply);
      std::cout << "G" << std::endl;
      return num;
      }
    • 在执行下面两句是,%会被替换为cmd.c_str(),导致无法将消息插入历史记录表,导致出现错误.

      1
      2
      std::string cmd = "lpush " + key + " " + value;
      this->m_reply = (redisReply *)redisCommand(this->m_connettion, cmd.c_str());
    • 只要更改为下面即没有问题

      1
      this->m_reply = (redisReply *)redisCommand(this->m_connettion, "LPUSH %s %s", key.c_str(), value.c_str());
  • 在某次测试时,新注册账号id为0614897828,导致与该账号聊天的账号进入聊天界面时,0614897828发送的消息会在他人界面显示为通知消息.

    • 由于与人聊天时会在数据库中创建有排序集合来记录某人正在聊天的人的id.运行时通过查看发现,其余账号与0614897828聊天时,数据库中记录账号为614897828.
    • 更换为首位非0的id就不会出现该问题.
    • redis会将有序集合中的score字段字符串中的0去除.将从字符串的首位非0开始存储.
  • 在进入好友私聊界面时,客户端新建了来接收消息,同时主线程发消息,主线程通过判断用户是否键入Esc来退出聊天界面,在输入Esc时,接收消息线程无法正确回收,无法退出聊天界面.

    • 在接收消息线程中来判断标志位来结束该线程,主线程在键入Esc时,将标志位设置为false,但由于客户端的recv为阻塞,导致无法及时获取到标志位的更改.
    • 在键入Esc时,服务器向客户端发送退出信号,这样就可以正确及时的获取到标志位的更改,从而正确退出.
  • 在进行发送文件时,服务器会先将文件存储在本地,但是服务器存储的文件会多写入一些信息,导致接收文件大小偏大.

    • 由于在用户登陆进入之后,会有一个线程每5秒向服务器送送刷新请求,导致服务器会将刷新请求当作文件内容写入文件,导致接收文件不一致
    • 在进入文件收发菜单后,关闭实时刷新,在文件结束后,再将实时刷新打开.
  • 实现后台发送文件,由于会有实时刷新的存在,会导致文件发送不准确.

    • 在发送文件时,重新创建一个socket连接到服务器,在将发送这个文件交给另外一个线程
    • 初始时在用户选择发送文件时开始线程,但会导致主线程与发送线程会同时运行菜单.主线程继续循环菜单,而发送线程运行获取发送文件的信息。
      • 更改线程开始时间,在用户选择发送文件时,获取到发送文件信息后,在启动线程.
      • 新连接的socket发送文件后,服务器不用手动断开连接,服务器有心跳检测,会在一段时间后断开.

eventfd

详解

是一个Linux系统调用,也是一种进程间通信(IPC)机制,主要通过使用文件描述符生成和使用事件通知.

提供了一种在不同进程之间或同一进程内的线程之间的同步事件的方法.

异常处理

异常是程序在执行期间产生的问题。c++异常是指在程序运行时发生的特殊情况.

异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw

关键字:try、catch、throw

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常

  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块

1
2
3
4
5
6
7
8
9
10
11
12
13
try
{
// 保护代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}

nlohmann::json

链接

SIGPIPE

SIGPIPE 是一种信号,当一个进程尝试向一个已经关闭的或不可写的管道或套接字写数据时,会触发这个信号。默认情况下,接收到 SIGPIPE 信号的进程会终止。这在网络编程中会导致一些问题,因为如果客户端断开连接,服务器进程在写入数据时会因为这个信号而意外退出。

在网络编程中,特别是使用套接字进行通信时,忽略 SIGPIPE 信号是一个常见的做法。这样可以防止进程因为 SIGPIPE 信号而意外终止。相反,程序可以通过检测 sendwrite 操作的返回值来处理错误,从而使程序更加健壮。

后续问题

  1. 初始客户端构思方面的缺陷,导致实时消息没有线程及时接收。

    当前处理方式为服务端将通知消息放入数据库当中,客户端在登陆之后,增加一个线程来定时执行刷新函数,刷新数据库中的通知消息

    可以优化为在客户端登陆之后,采用多线程的方式来处理相关操作.

  2. 目前只有在历史消息这一操作涉及到redis+mysql的处理方式.

    可以将一些重要信息也采用redis+mysql的处理方式(涉及到redis的存储方式,redis存储于内存当中,会造成数据丢失的情况)

    可以考虑将redis全部做为缓存的形式,将重要信息与历史消息类似,达到一定情况下放入mysql,使数据更加的持久化.

后续学习

  1. 继续学习epoll的相关内容.

  2. 了解其余网络框架,使得该项目的服务端更加的健壮.

  3. 了解消息队列的信息同步的问题.

  4. 熟悉零拷贝的过程,以及零拷贝的具体实现.

  5. 序列化的相关协议,json与其他序列化的区别、json的优点,以及为什么不用其他的序列化

  6. 数据库的深入学习,了解redis的缓存穿透、缓存雪崩、缓存击穿。加强对mysql数据库的学习.

  7. 加强对网络协议相关知识的学习.

Friday, 17 May 2024

强制类型转换

C++提供了四个强制类型转换的关键字:

  • static_cast
  • const_cast
  • reinterpret_cast
  • ``dynamic_cast`

static_cast

1
static_cast<目标类型>(表达式)
1
2
int num = 2;
double result =static_cast<double>(num);

该运算符将表达式转换为目标类型。但没有进行运行时类型检查来保证转换的安全性

主要用法

  1. 用于类层次结构中父类和子类之间指针或引用的转换.进行上行转换是安全的(即将子类的指针或引用转换成父类是正确的);进行下行转换的时候,由于没有动态类型检查,所以是不安全的。继承必须为public
  2. 用于基本类型之间的转换,如intchar安全性也需要程序员来保证
  3. 把空指针转换为目标类型的空指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person
{
public:
void print()
{
cout << "Person " << endl;
}
};

class Son : public Person
{
public:
void print()
{
cout << "Son " << endl;
}
};

void print1(Person *p)
{
p->print();
}

int main()
{
Son s;
print1(static_cast<Person *>(&s));
return 0;
}

const_cast

const_cast是c++中专用于处理与const相关的强制类型转换的关键字

其功能为:为一个变量重新设定其const描述.

即:const_cast可以为一个变量强行增加或删除其const限定.

需要明确的是,即使用户通过const_cast强行去除了const属性,也不代表当前变量从不可变变为了可变。const_cast只是使得用户接管了编译器对于const限定的管理权,故用户必须遵守“不修改变量”的承诺。如果违反此承诺,编译器也不会因此而引发编译时错误,但可能引发运行时错误。

  1. const_cast可用于更改const成员函数内的非const类成员。
  2. const_cast可用于将const数据传递给不接收const的函数。
  3. const_cast<>里边的内容必须是引用或者指针。
  4. const_cast也可以用来抛弃volatile__unaligned属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Student
{
private:
int roll;
public:
Student(int r) :roll(r) {}
void fun() const
{
(const_cast<Student*> (this))->roll = 5;
}
int getRoll() { return roll; }
};

int main() {
Student student(3);
std::cout << "Old roll number: " << student.getRoll() << std::endl;
student.fun();
std::cout << "New roll number: " << student.getRoll() << std::endl;

// const_cast只能调节类型限定符,不能更改基础类型
int a1 = 40;
//const int* b1 = &a1;
//char* c1 = const_cast <char*> (b1); // 编译程序时出错

const volatile int* d1 = &a1;
std::cout << "typeid of d1 " << typeid(d1).name() << '\n'; // int const volatile *
int* e1 = const_cast <int*> (d1);
std::cout << "typeid of e1 " << typeid(e1).name() << '\n'; // int *

return 0;
}

在const成员函数fun()中,编译器将“this”视为“ const student const this”,即“this”是指向常量对象的常量指针,因此编译器不允许通过以下方式更改数据成员“这个”指针。const_cast将“this”指针的类型更改为“student const this”

const_cast比简单类型转换更安全。从某种意义上讲,如果强制类型与原始对象不相同,则强制转换不会发生,这是比较安全的。

1
2
3
int a=20;
const int *p=&a;
char *c1=const_cast<char*> (p);//编译时程序出错

reinterpret_cast

reinterpret,即重新解释.

该强制类型转换的作用是提供某个变量在底层数据上的重新解释.

当我们对一个变量使用reinterpret_cast后,编译器将无视任何不合理行为,强行将被转换变量的内存数据重解释为某个新的类型。用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。 它不检查指针类型和指针所指向的数据是否相同。

需要注意的是,reinterpret_cast要求转换的两个数据所占用的内存大小一致,否则会引发编译时错误.

1
data_type *var_name = reinterpret_cast <data_type *>(pointer_variable);

使用 reinterpret_cast 的目的:

  1. reinterpret_cast是一种非常特殊且危险的类型转换操作符。并且建议使用适当的数据类型使用它,即(指针数据类型应与原始数据类型相同)。
  2. 它可以将任何指针类型转换为任何其他数据类型。
  3. 当我们要使用位时使用它。
  4. 它仅用于将任何指针转换为原始类型。
  5. 布尔值将转换为整数值,即0表示false,1表示true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A {
public:
int a;
A(int i) :a(i) {}
void fun_a()
{
std::cout << "In class A\n";
}
};

class B {
public:
int b;
B(int i) :b(i) {}
void fun_b()
{
std::cout << "In class B\n";
}
};

void testReinterpretCast() {
B *x = new B(5);
A* y = reinterpret_cast<A*>(x);
y->fun_a(); // In class A
std::cout << y->a << std::endl; // 5
}

dynamic_cast

dynamic用于在运行时实现向下类型转换。

1
dynamic_cast <type-id> (expression)

expression转换为type-id类型,type-id必须是类的指针,类的引用或者是void,

如果type-id是一个指针,那么expression也是一个指针,是引用的话同为引用

特点如下:

  1. 它是在运行是进行处理的,其余三个都是在编译时完成. 运行时进行类型检查
  2. 不能用于内置的基本数据类型之间的强制转换
  3. dynamic_cast 要求 <> 内所描述的目标类型必须为指针或引用。dynamic_cast 转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回 nullptr
  4. 在类的转换时,在类层次上进行向上转换(子类指针指向父类指针),与static_cast的效果是一样的。在进行父类指针向子类指针的转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
  5. 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。
  6. 使用 dynamic_cast 进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class AA {
public:
virtual void print() {
cout << "in class AA" << endl;
};
};

class BB :public AA {
public:
void print() {
cout << "in class BB" << endl;
};
};

void testDynamicCast() {
AA* a1 = new BB; // a1是A类型的指针指向一个B类型的对象
AA* a2 = new AA; // a2是A类型的指针指向一个A类型的对象

BB* b1, * b2, * b3, * b4;

b1 = dynamic_cast<BB*>(a1);// not null,向下转换成功,a1 之前指向的就是 B 类型的对象,所以可以转换成 B 类型的指针。
if (b1 == nullptr)
cout << "b1 is null" << endl;
else
cout << "b1 is not null" << endl;

b2 = dynamic_cast<BB*>(a2);// null,向下转换失败
if (b2 == nullptr)
cout << "b2 is null" << endl;
else
cout << "b2 is not null" << endl;

// 用 static_cast,Resharper C++ 会提示修改为 dynamic_cast
b3 = static_cast<BB*>(a1);// not null
if (b3 == nullptr)
cout << "b3 is null" << endl;
else
cout << "b3 is not null" << endl;

b4 = static_cast<BB*>(a2);// not null
if (b4 == nullptr)
cout << "b4 is null" << endl;
else
cout << "b4 is not null" << endl;

a1->print();// in class BB
a2->print();// in class AA

b1->print();// in class BB
//b2->print(); // null 引发异常
b3->print();// in class BB
b4->print();// in class AA
}

详细

Friday, 10 May 2024

Lambda表达式

匿名函数是很多高级语言都支持的概念,如lisp语言在1958年首先采用匿名函数。匿名函数有函数体,但没有函数名。C++11中引入了lambda表达式。利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读。但是从本质上来讲,lambda表达式只是一种语法糖,因为所有其能完成的工作都可以用其它稍微复杂的代码来实现。但是它简便的语法却给C++带来了深远的影响。如果从广义上说,lamdba表达式产生的是函数对象。

相同类似功能我们也可以使用函数对象或者函数指针实现:函数对象能维护状态,但语法开销大,而函数指针语法开销小,却没法保存范围内的状态。lambda表达式正是结合了两者的优点。

声明Lambda表达式

1
2
3
4
5
6
7
// 完整语法
[capture list] (params list) mutable(optional) constexpr(optional)(c++17) exception attribute -> return type { function body };

// 可选的简化语法
[capture list] (params list) -> return type {function body}; //1
[capture list] (params list) {function body};//2
[capture list] {function body};//3
  • capture list:捕获外部变量列表,不能省略;
  • params list:形参列表,可以省略(但是后面必须紧跟函数体);
  • mutable指示符: 可选,将lambda表达式标记为mutable后,函数体就可以修改传值方式捕获的变量;
  • constexpr:可选,C++17,可以指定lambda表达式是一个常量函数;
  • exception:异常设定, 可选,指定lambda表达式可以抛出的异常;
  • attribute:可选,指定lambda表达式的特性;
  • return type:返回类型
  • function body:函数体

标号1. 函数声明了一个const类型的表达式,此声明不可改变capture list中的捕获的值。

标号2. 函数省略了返回值,此时如果function body内含有return语句,则按return语句返回类型决定返回值类型,若无则返回值为void类型。

标号3. 函数无参数列表,意味无参函数。

1
2
3
vector<int> vec{1,0,9,5,3,3,7,8,2};

sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });

捕获外部变量

lambda表达式最前面的方括号的意义何在?其实这是lambda表达式一个很Hong要的功能,就是闭包。这里我们先讲一下lambda表达式的大致原理:每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,其实一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。

Lambda表达式可以捕获外面变量,但需要我们提供一个谓词函数([capture list]在声明表达式最前)。类似参数传递方式:值传递、引入传递、指针传递。在Lambda表达式中,外部变量捕获方式也类似:值捕获、引用捕获、隐式捕获

值捕获

1
2
3
4
5
int a = 123;
auto f = [a] { cout << a << endl; };
f(); // 输出:123
a = 321;
f(); // 输出:123

值捕获和参数传递中的值传递类似,被捕获的值在Lambda表达式创建时通过值拷贝的方式传入,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。因此Lambda表达式函数体中不能修改该外部变量的值; 因为函数调用运算符的重载方法是const属性的。同样,函数体外对于值的修改也不会改变被捕获的值。 想改动传值方式捕获的值,那么就要使用mutable

1
2
3
auto add_x = [x](int a) mutable { x *= 2; return a + x; };  // 复制捕捉x

cout << add_x(10) << endl; // 输出:30

因为一旦将lambda表达式标记为mutable,那么实现的函数调用运算符是非const属性的。

引用捕获

1
2
3
4
int a = 123;
auto f = [&a] { cout << a << endl; };
a = 321;
f(); // 输出:321

引用捕获的变量使用的实际上就是该引用所绑定的对象,因此引用对象的改变会改变函数体内对该对象的引用的值。 对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。

隐式捕获

隐式捕获有两种方式,分别是 [=]:以值补获的方式捕获外部所有变量 [&]:表示以引用捕获的方式捕获外部所有变量

1
2
3
int a = 123, b=321;
auto df = [=] { cout << a << b << endl; }; // 值捕获
auto rf = [&] { cout << a << b << endl; }; // 引用捕获

其他

捕获外部变量形式
[ ]不捕获任何变量(无参函数)
[变量1,&变量2, …]值(引用)形式捕获指定的多个外部变量
[this]值捕获this指针
[=, &x]变量x以引用形式捕获,其余变量以传值形式捕获
[*this]通过传值方式捕获当前对象
[&, x]默认以引用捕获所有变量,但是x是例外,通过值捕获

既然只使用一次,那直接写全代码不就行了,为啥要函数呢?——因为lambda可以捕获局部变量

在上面的捕获方式中,注意最好不要使用[=][&]默认捕获所有变量。首先说默认引用捕获所有变量,你有很大可能会出现悬挂引用(Dangling references),因为引用捕获不会延长引用的变量的声明周期:

1
2
3
4
std::function<int(int)> add_x(int x)
{
return [&](int a) {return x+a;};
}

因为参数x仅是一个临时变量,函数调用后就被销毁,但是返回的lambda表达式却引用了该变量,但调用这个表达式时,引用的是一个垃圾值,所以会产生没有意义的结果。如果通过传值的方式来解决上面的问题:

1
2
3
4
std::function<int(int)> add_x(int x)
{
return [=](int a) { return x + a; };
}

使用默认传值方式可以便面悬挂引用问题.但是采用默认值捕获所有变量仍然有风险。例如当在类中捕获私有变量,当返回值为lambda表达式时,无法捕获到私有变量,但当指定为[=]时,会捕获到this指针的副本,当类已经调用析构函数,使用该指针仍然不安全.

参数

  • 参数列表中不能有默认参数
  • 不支持可变参数
  • 所有参数必须要参数名(相当于不可以有占位参数)

返回类型

单一的return语句可以推断返回类型;多语句则默认返回void,否则报错,应指定返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 正确,单一return语句
transform(vi.begin(),vi.end(),vi.begin(),
[] (int i) {
return i<0? -i; i;
}
);
// 错误。不能推断返回类型
transform(vi.begin(),vi.end(),vi.begin(),
[] (int i) {
if (I<0) return -i;
else return i;
}
);
// 正确,尾置返回类型
transform(vi.begin(),vi.end(),vi.begin(),
[] (int i) ->int{
if (I<0)
return -i;
else
return i;
}
);

赋值

auto和function可接受lambda表达式的返回:

1
2
3
int x =8,y=9;
auto add = [](int a,int b){return a+b;};
std::function<int(int,int)> Add = [=] (int a,int b){return a+b};

lambda表达式产生的类不含有默认构造函数、赋值运算符、默认析构函数。至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,看来与具体实现有关。还有一点要注意:lambda表达式是不能被赋值的:

1
2
3
4
5
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };

a = b; // 非法,lambda无法赋值
auto c = a; // 合法,生成一个副本

因为禁用了赋值操作符:

1
ClosureType& operator=(const ClosureType&) = delete;

但是没有禁用复制构造函数,所以可以用一个lambda表达式去初始化另外一个lambda表达式而产生副本。并且lambda表达式也可以赋值给相对应的函数指针,这也使得你完全可以把lambda表达式看成对应函数类型的指针

新特性

C++14中,lambda又得到了增强,一个是泛型lambda表达式,一个是lambda可以捕捉表达式。

lambda捕捉表达式

lambda表达式可以按复制或者引用捕获在其作用域范围内的变量。而有时候,我们希望捕捉不在其作用域范围内的变量,而且最重要的是我们希望捕捉右值。所以C++14中引入了表达式捕捉,其允许用任何类型的表达式初始化捕捉的变量:

1
2
3
4
5
6
7
8
// 利用表达式捕获,可以更灵活地处理作用域内的变量
int x = 4;
auto y = [&r = x, x = x + 1] { r += 2; return x * x; }();
// 此时 x 更新为6,y 为25

// 直接用字面值初始化变量
auto z = [str = "string"]{ return str; }();
// 此时z是const char* 类型,存储字符串 string

可以看到捕捉表达式扩大了lambda表达式的捕捉能力,有时候你可以用std::move初始化变量。这对不能复制只能移动的对象很重要,比如std::unique_ptr,因为其不支持复制操作,你无法以值方式捕捉到它。但是利用lambda捕捉表达式,可以通过移动来捕捉它:

1
2
3
4
auto myPi = std::make_unique<double>(3.1415);

auto circle_area = [pi = std::move(myPi)](double r) { return *pi * r * r; };
cout << circle_area(1.0) << endl; // 3.1415

原文章

Friday, 3 May 2024

可变参数模板

可变参数模板是C++11引入的一个特性,它允许定义一个接受任意数量参数的模板.

在函数模板或类模板中,我们可以使用可变参数模板来定义接受任意数量参数的函数或类。可变参数模板使用了模板参数包(template parameter pack),它允许我们将零个或多个模板参数打包成一个参数集合。

1
2
3
4
5
6
7
8
9
template <typename T>
T sum(T arg){
return arg;
}

template<typename T,typename...Args>
T sum(T first ,Args...res){
return first+sum(rest...);
}

尾部类型推导

当需要使用尾部类型推导的时候,通常是因为函数的返回类型依赖于函数参数或其他的上下文.

1
2
3
4
5
6
7
8
9
10
11
12
auto add=[](int x,int y)->int {
return x+y;
}
auto add=[](int x,int y)->auto
{
return x+y;
}
//函数模板
template<typename T, typename U>
auto multiply(T x, U y) -> decltype(x * y) {
return x * y;
}

std::accumulate

std::accumulate 是 C++ 标准库 <numeric> 头文件中提供的一个算法函数,用于计算给定范围内的元素的总和。它接受一个起始迭代器和一个终止迭代器,以及一个初始值,然后对范围内的元素进行累加,并返回结果。

它的基本语法如下:

1
2
cppCopy codetemplate< class InputIt, class T >
T accumulate( InputIt first, InputIt last, T init );

其中:

  • InputIt 是起始迭代器和终止迭代器的类型。
  • first 是范围的起始迭代器。
  • last 是范围的终止迭代器(不包括在范围内)。
  • init 是初始值,用于累加元素。

decltype

decltype 是 C++11 引入的一个关键字,用于获取表达式的类型。它可以用于声明变量、函数返回类型、模板参数等地方。

1
decltype(expression)

std::bind

链接

std::bind的头文件是

解释

std::result_of

std::result_of 是一个 C++11 标准库中的模板工具,用于获取函数对象的返回类型。它可以用于在编译时确定函数对象的返回类型,而不需要实际调用该函数对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <type_traits>

int add(int a, int b) {
return a + b;
}

int main() {
// 获取 add 函数的返回类型
typedef std::result_of<decltype(add)&(int, int)>::type result_type;

// 输出返回类型
std::cout << "Result type: " << typeid(result_type).name() << std::endl;

return 0;
}

std::result_ofdecltype 都是用于获取表达式的类型,但它们之间有一些区别:

  1. 用法
    • std::result_of 用于获取函数对象的返回类型,而不需要实际调用该函数对象。它是一个模板类,需要指定函数对象的类型和参数类型。
    • decltype 用于获取表达式的类型,可以是任意的表达式,不仅限于函数调用。它是一个关键字,可以直接用于变量声明、返回类型推断等地方。
  2. 适用范围
    • std::result_of 主要用于获取函数对象的返回类型,因此在涉及函数对象的场景下更为常见。
    • decltype 不仅可以用于函数调用,还可以用于变量声明、表达式的类型推断等场景,更为灵活。
  3. 语法
    • std::result_of 的语法是模板类模板参数,需要传递函数类型和参数类型。
    • decltype 的语法更为灵活,可以直接对任意表达式使用。

std::filesvstem

cppreference

csdn