npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@f1stnpm2/harum-officiis-itaque

v1.0.0

Published

A zero dependency featherweight library to layout text on a canvas.

Downloads

82

Maintainers

sinhatminh11sinhatminh11

Keywords

amazonefficientequalbddfindtelephoneconfigwritepropertywidthvalidationfixed-widthwhatwgreadablestreamboundjsdiffArray.prototype.findLastIndexescapeconfigurablees2017stylingsetPrototypeOfmodulecryptnamesemojiomititerateformattingcompile lessminimaljsonpathgetoptassertsqueueMicrotaskprivatesymbolexpresspnpm9internalwordbreaktouchqueueURLSearchParamsrestfulfseventshaspersistentfastclonecontainsoutputes2015restaccessibilityRegExp#flagstraversermfscoerciblemapreduceES2015Int32Arraydeep-clonedataviewgradients css3consumeguidfastbufferscallbindsetArrayBuffer#slicepreserve-symlinksRegExp.prototype.flagssafeutilitiesjsdomargumentsnsreplaysorttoolkitcommandmovewhichi18nlinewraptc39endpointStreamsescensorbundlingflatArray.prototype.findLastObject.valuesstreamWeakSetasciicharacterstrimStartstoragegatewaymiddlewareprivate databootstrap lesslookregexpframeworkperformancelinttoSortedtaprfc4122autoprefixerstyled-componentsfast-cloneponyfillcall-bounddynamodbincludescode pointsflagsRFC-6455extracollection.es6appvariables in csselboperating-systemprocessstdlibl10nlesscssES6listenerses6dom-testing-libraryUnderscoreutilitytslibxtermbrowserslist@@toStringTagentriesfunctioneventDispatcherpolyfillisFunction.prototype.namelastHyBideepcloneReflect.getPrototypeOfhotrapidtypeerrorstructuredCloneECMAScript 2020serializerinvariantwalktermduplexpackageFloat32ArrayautoscalingavakoreanECMAScript 2023lazycheckquerycallboundbusypluginstreams2columnpositiveArray.prototype.flatMaproute53arraysdebugtostringtagbcryptwebfullwidthchromepreprocessorfast-deep-copypathSymbollibphonenumberObservableObjectsomejwtmergespinnersinstallercjkauthrm -rfES5statusoncematchAllglaciertsArray.prototype.flatlinuxtypesafeUint8ClampedArrayES2018testdataViewTypeScriptmatchhardlinksECMAScript 2022chaicallbackvpctrimmake dirECMAScript 2019trimRightregular expressionswatchercloudsearchidES3numbersortedgetintrinsicisConcatSpreadablemacossuperstructsharedbatcharrayfullasyncimportexportpackage.jsones7inferencepicomatchcss lesscolumnsInt16Arrayio-tses8idleratelimitgenericselmcommanderjapanesespinnerprogressvalidweaksetcssstatelessmonorepoextensionprotocol-buffersoptiontrimEnd256shebangstylesECMAScript 2021starterwordwrapStreamscss variablecopytypescriptjQuerywrap_.extendregularenvironmentReactiveExtensionsdebuggerreact-testing-librarydatastructureelasticachestringifyES8Symbol.toStringTagliveutil.inspecttypestringbyteOffsetpyyamlawesomesauceIteratorArray.prototype.containsextendhas-ownarktypeStyleSheetviewidentifiersreadablecss-in-jsCSSStyleDeclarationReactiveXbeanstalkhelpersparentphonesidelinkmkdirsiteratorrandomcore-jsloggingawscompilerruntimeECMAScript 3nopedependenciescodespropworkspace:*mkdirpargparsefindLastIndexcurlinterruptsobjectreact-hooksObject.keystoArrayscheme-validationsymlinkseventsajvhashbyteless csssettingsESclass-validatorfigletdescriptorenvemrPushBigInt64Arrays3requirefind-upinputdirjsxmkdires2018flatMapsigtermsetImmediatetypestypeofeast-asian-width$.extendexit-codeshamwindowssliceECMAScript 6sequenceexecintrinsicless compilerArrayES2020emitdataless.jspushmetadatadroptakeserializeprune.envdiffgrouplrujavascriptECMAScript 2018mixinspredictabletoolsslotmime-dbdeep-copyschemenegativemimeajaxObject.fromEntriesdefinePropertyawaitzerojasminenegative zeroconnectnpmformchanneloffsetECMAScript 2017inspecttrimLeftclassesargvvalidateirqless mixinsurlObject.getPrototypeOftextshrinkwrapdateECMAScript 5loggerdeepcopystateopensearchES2021httpecmascriptsuperagentMapunicodedescriptionregexbrowserlistassignrecursiveJSON-SchemaauthenticationgraphqlgetPrototypeOfvarsclassnamesprefixoptimizer__proto__characterstringifiertoStringTagprettykarmatoobjectprotobufcloudtrailhooksgloballook-upimmutableUint16Arraytypanionargsclonerm -frinternal slotjsonfluxloadingrobustswfastmodulespasswordchinesetypedarrayeslinthigher-ordercollectionTypedArrayupthroatyupclassnamelogfilespeeddependency managererror-handlingsigintmruweakmapestreepackage managerhttpsparsertesterrdsrgbietddrouterES2023YAMLmakereactfastcopynativecloudfrontcolorenvironmentsdefinesyntaxuninstallarraybufferessignalsES7typedCSSshimexpressionprotorangeerrorpostcss-pluginworkerdescriptorsspecconcateventEmitterfindupdayjspropertiesreduxownbufferArrayBufferes-abstractbreakpostcssfiltercalljswalkingstreamsgetec2createreusewgetsameValueZeroartbindgdprTypeBoxRxJSWebSocketsURLes2016ECMAScript 2015lengthSetES2016bundlerfpses5ES2019everyenumerableparentsconsolehookformfunctionsremoveMicrosoftcommand-linequerystringflattennodejsyamlsignalUint32Arraystableexitconcurrencywaitsymbolsgradients cssttyfolderObject.isgroupBy0a11ywatchsyntaxerrorforEachtimejshintform-validationreact-hook-formresolvethrottlecloudformationdirectorywafcorspackagesmochaiterationclicircularutilnamestyleECMAScript 7full-widthdeterministicvaluevariablesfromdeepcompareencryptionserializationinstalloptimistkeys[[Prototype]]multi-packageshellmimetypesvisualenderobjObject.definePropertyparsinges-shimsgetOwnPropertyDescriptorflaglanguageimportlimitworkflowerrorhasOwnPropertyprototypemomentbootstrap cssfast-deep-clonejestfetchArrayBuffer.prototype.slicedeleteFloat64Arraykinesisfast-copycall-bindimmergetteraccessorintypedarraysqsfindLastsymlinkString.prototype.matchAllkeyparseWeakMapiamloadbalancingvestsharedarraybufferbluebirdJSONESnextes-shim APIBigUint64Arrayformsnested cssObservablesconcatMapreducerString.prototype.trimArray.prototype.filteruuidcss nestingmatchesairbnbhasOwnfastifycloudwatchperformanthandlersstyleguidebyteLengthxhrratefunctionalcolorslockfileterminalproxyglobECMAScript 2016formatbrowsercryptocoreagenttestingtaskreal-timeassertionreadbannerPromiseansiapollorequestmobilesqseslintpluginclientlimited-0Array.prototype.flattenwatchingpromiseglobalsstylesheetwatchFilecolourelectroneslintconfigpromisesjoiES2022indicatorRxUint8ArrayquoteWebSocketapisimpledbmapregular expressionwritableredux-toolkittyped arrayObject.assignArray.prototype.includesfile systemebszodcacheObject.entriesAsyncIteratorvalueswarningtapeassert

Readme

Canvas HyperTxt 🚀📐✍

A zero dependency featherweight library to layout text on a canvas.

Version npm bundle size Code Coverage License Made By Glide

Quickstart ⚡

import { split } from "@f1stnpm2/harum-officiis-itaque";

function renderWrappedText(ctx: CanvasRenderingContext2D, value: string, width: number, x: number, y: number) {
    ctx.font = "12px sans-serif"; // ideally don't do this every time, it is really slow.
    ctx.textBaseline = "top"; // just makes positioning easier to predict, not essential
    const lines = split(ctx, value, "12px sans-serif", width);
    for (const line of lines) {
        ctx.fillText(line, x, y);
        y += 15;
    }
}

function renderWrappedTextCentered(ctx: CanvasRenderingContext2D, value: string, width: number, x: number, y: number) {
    // ideally don't do this every time, it is really slow.
    ctx.font = "12px sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "top";
    const lines = split(ctx, value, "12px sans-serif", width);
    for (const line of lines) {
        ctx.fillText(line, x + width / 2, y);
        y += 15;
    }
}

Who is this for?

This library is inspired by the excellent canvas-txt but focuses instead on being a part of the rendering pipeline instead of drawing the text for you. This offers greater flexibility for those who need it. Additionally @f1stnpm2/harum-officiis-itaque focuses on layout performance, allowing for much faster overall layout and rendering performance compared to the original library. Some sacrifices are made in the name of performance (justify). This library is internally integrated and used in glide-data-grid and now is available as a standalone.

Comparison vs canvas-txt

While @f1stnpm2/harum-officiis-itaque does not set out to be a drop-in replacement to canvas-txt, we can drag race them.

All tests were done with 5000 iterations. To ensure a fair comparison times include font rendering time.

| | canvas-txt | @f1stnpm2/harum-officiis-itaque | @f1stnpm2/harum-officiis-itaque w/ HyperWrapping | | --------------------- | ---------: | --------------: | -------------------------------: | | 20 char (no wrapping) | 0.05 sec | 0.05 sec | 0.05 sec | | 100 char | 0.59 sec | 0.11 sec | 0.11 sec | | 300 char | 2.63 sec | 0.40 sec | 0.26 sec | | 600 char | 6.17 sec | 0.81 sec | 0.47 sec | | 1000 char | 11.19 sec | 1.43 sec | 0.77 sec | | 1800 char (overflow) | 22.47 sec | 2.29 sec | 1.19 sec |

Benchmark code can be found here. You can run benchmarks on your machine here

canvas-multiline-text is not included in this chart because it fails to pass a basic correctness test. It can't handle wrapping correctly if there are no words to break at, nor does it handle newlines. Due to this it ends up making fewer draw calls and a significant amount of rendering happens off-canvas reducing drawing overhead further due to the correctness errors.

That said @f1stnpm2/harum-officiis-itaque tends to be around 20-30% faster than canvas-multiline-text without hyper wrapping, and about 1.2x faster with.

How is this so much faster?

Canvas-txt is an excellent library but takes a very inefficient approach to finding wrap points. It is clearly not written with performance in mind, but rather with features and bundle size. Overall it is a fantastic library if raw speed is not important to your use case.

HyperWrapping

One of the major items introduced by @f1stnpm2/harum-officiis-itaque is the concept of hyper wrapping. When enabled the font engine will train a weighting model to provide estimates for string sizes. Once the model is sufficiently trained it will perform string wrapping without calling ctx.measureText once. This leads to massive performance gains at the cost of accuracy. In practice with most fonts and text bodies, once trained the hyper wrap guesses will be within 1% of the actual measured size. A buffer is added to ensure the text wraps slightly too early instead of clipping.

The end result is text that is correctly wrapped the vast majority of the time, with a very small number of errors where the text wraps too early by a single word. The performance gains in the measure pass are over 100x, with hyper wrapped text basically having zero cost vs unwrapped text of the same size.

What am I missing using @f1stnpm2/harum-officiis-itaque?

You miss some features.

Managed rendering

canvas-txt will render your string for you, figure out line heights, etc. With @f1stnpm2/harum-officiis-itaque this is on you. It's not hard to do but it is a bit of extra lifting. You could easily wrap the @f1stnpm2/harum-officiis-itaque split function to do the same thing canvas-txt does. Examples provided in the quickstart.

Justify

I didn't feel like implementing it, patches welcome. This will 100% be slower than non-justified text due to the large amounts of string manipulation and extra measurement required. If you need justification canvas-txt can do it.

Debug mode

Because @f1stnpm2/harum-officiis-itaque doesn't actually render the text, it also can't render debug boxes for you.

Automatic text alignment

Text alignment with @f1stnpm2/harum-officiis-itaque is not as simple as setting a flag, though it's close. Set the ctx.textAlign appropriately and change the x value you pass to fillText to correspond to the left, center, or right edge of the bounding box.

Why are these all missing?

@f1stnpm2/harum-officiis-itaque is intended to be used as part of larger libraries which need wrapping text. In these cases text-alignment or actual text rendering is handled by existing functions which may provide additional functionality. By not rendering the text for the consumer, more flexibility is granted, however it comes at the cost of simplicity.

Usage

This library consists of two methods.

export function split(
    ctx: CanvasRenderingContext2D,
    value: string,
    fontStyle: string,
    width: number,
    hyperWrappingAllowed: boolean
): readonly string[];

split takes the following parameters

| Name | Usage | | -------------------- | ------------------------------------------------------------------------------- | | ctx | A CanvasRenderingContext2D | | value | The string which needs to be wrapped | | fontStyle | A unique key which represents the font configuration currently applied to ctx | | width | The maximum width of any line | | hyperWrappingAllowed | Whether or not to allow hyper wrapping |

export function clearCache(): void;

Clear all size caches the library has collected so far. Ideally do this when fonts have finished loading.

async function clearCacheOnLoad() {
    if (document?.fonts?.ready === undefined) return;
    await document.fonts.ready;
    clearCache();
}

void clearCacheOnLoad();